ExposedGays: map, shop, auth, Supabase schema, Coolify Dockerfile

This commit is contained in:
Ryan Salazar
2026-06-26 17:32:15 -04:00
commit 3eb12c8b34
54 changed files with 4424 additions and 0 deletions

150
src/components/AgeGate.tsx Normal file
View File

@@ -0,0 +1,150 @@
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { ShieldAlert } from "lucide-react";
interface AgeGateProps {
onVerified: () => void;
}
export function AgeGate({ onVerified }: AgeGateProps) {
const [month, setMonth] = useState("");
const [day, setDay] = useState("");
const [year, setYear] = useState("");
const [error, setError] = useState("");
const [agreed, setAgreed] = useState(false);
const handleVerify = () => {
const m = parseInt(month, 10);
const d = parseInt(day, 10);
const y = parseInt(year, 10);
if (!m || !d || !y) {
setError("Please enter your full date of birth.");
return;
}
const dob = new Date(y, m - 1, d);
const today = new Date();
let age = today.getFullYear() - dob.getFullYear();
const monthDiff = today.getMonth() - dob.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < dob.getDate())) {
age--;
}
if (age < 18) {
setError("You must be 18 or older to access this site.");
return;
}
if (!agreed) {
setError("You must confirm you are 18+ and agree to the disclaimer.");
return;
}
localStorage.setItem("eg_age_verified", "true");
localStorage.setItem("eg_age_verified_at", new Date().toISOString());
onVerified();
};
return (
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/95 p-4">
<div className="w-full max-w-md rounded-2xl border border-horny-pink/30 bg-horny-surface p-8 shadow-2xl shadow-horny-pink/20">
<div className="mb-6 text-center">
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-horny-gradient">
<ShieldAlert className="h-8 w-8 text-white" />
</div>
<h1 className="text-2xl font-bold bg-horny-gradient bg-clip-text text-transparent">
ExposedGays
</h1>
<p className="mt-2 text-sm text-muted-foreground">
Adults only. Explicit content ahead.
</p>
</div>
<div className="space-y-4">
<div>
<Label className="mb-2 block">Date of Birth</Label>
<div className="grid grid-cols-3 gap-2">
<select
value={month}
onChange={(e) => setMonth(e.target.value)}
className="h-10 rounded-md border border-input bg-background px-2 text-sm"
>
<option value="">Month</option>
{Array.from({ length: 12 }, (_, i) => (
<option key={i + 1} value={i + 1}>
{new Date(2000, i).toLocaleString("en", { month: "long" })}
</option>
))}
</select>
<select
value={day}
onChange={(e) => setDay(e.target.value)}
className="h-10 rounded-md border border-input bg-background px-2 text-sm"
>
<option value="">Day</option>
{Array.from({ length: 31 }, (_, i) => (
<option key={i + 1} value={i + 1}>
{i + 1}
</option>
))}
</select>
<select
value={year}
onChange={(e) => setYear(e.target.value)}
className="h-10 rounded-md border border-input bg-background px-2 text-sm"
>
<option value="">Year</option>
{Array.from({ length: 80 }, (_, i) => {
const y = new Date().getFullYear() - 18 - i;
return (
<option key={y} value={y}>
{y}
</option>
);
})}
</select>
</div>
</div>
<label className="flex items-start gap-3 cursor-pointer">
<input
type="checkbox"
checked={agreed}
onChange={(e) => setAgreed(e.target.checked)}
className="mt-1 h-4 w-4 rounded border-input accent-horny-pink"
/>
<span className="text-xs text-muted-foreground leading-relaxed">
I confirm I am 18 years of age or older. I understand this site contains
explicit adult content including nudity and sexual material. I agree to the{" "}
<a href="/terms" className="text-horny-pink underline">
Terms of Service
</a>{" "}
and{" "}
<a href="/privacy" className="text-horny-pink underline">
Privacy Policy
</a>
. Pursuant to Florida Statute § 847.013, I acknowledge this material may be
harmful to minors. Third-party age verification may be required in the future.
</span>
</label>
{error && (
<p className="text-sm text-destructive text-center">{error}</p>
)}
<Button variant="horny" className="w-full" size="lg" onClick={handleVerify}>
Confirm I am 18+
</Button>
<p className="text-center text-xs text-muted-foreground">
100% free forever. No subscriptions. No credit card required.
</p>
</div>
</div>
</div>
);
}