feat: Sniffies-style map+profiles, guest age-verify without ID, limited access

This commit is contained in:
Ryan Salazar
2026-06-26 21:41:50 -04:00
parent 448035ad60
commit 052cd12aca
18 changed files with 1268 additions and 251 deletions

View File

@@ -1,6 +1,6 @@
"use client";
import { useState, useEffect, createContext, useContext, useCallback } from "react";
import { useState, useEffect, createContext, useContext, useCallback, useMemo } from "react";
import { AgeGate } from "@/components/AgeGate";
import { Nav } from "@/components/Nav";
import { MobileHeader } from "@/components/MobileHeader";
@@ -8,17 +8,30 @@ import { IdVerificationModal } from "@/components/IdVerificationModal";
import { SiteFooter } from "@/components/SiteFooter";
import { AuthProvider, useAuth } from "@/lib/auth/context";
import {
mustBlurExplicit,
requiresIdVerification,
getUserState,
isIdVerified,
} from "@/lib/id-verification";
import {
getAccessCapabilities,
shouldBlurExplicit,
type AccessCapabilities,
} from "@/lib/access-tier";
const ActiveCountContext = createContext<(n: number) => void>(() => {});
const VanillaModeContext = createContext(false);
const IdBlurContext = createContext(false);
const OpenIdVerificationContext = createContext<() => void>(() => {});
interface AccessContextValue {
capabilities: AccessCapabilities;
blurExplicit: boolean;
}
const AccessContext = createContext<AccessContextValue>({
capabilities: getAccessCapabilities(false),
blurExplicit: true,
});
export function useSetActiveCount() {
return useContext(ActiveCountContext);
}
@@ -27,39 +40,53 @@ export function useVanillaMode() {
return useContext(VanillaModeContext);
}
export function useIdBlur() {
return useContext(IdBlurContext);
}
export function useOpenIdVerification() {
return useContext(OpenIdVerificationContext);
}
export function useAccess() {
return useContext(AccessContext);
}
/** @deprecated use useAccess().blurExplicit */
export function useIdBlur() {
return useContext(AccessContext).blurExplicit;
}
interface ProvidersProps {
children: React.ReactNode;
}
function InnerProviders({ children }: ProvidersProps) {
const { profile } = useAuth();
const { user, profile } = useAuth();
const [vanillaMode, setVanillaMode] = useState(false);
const [activeCount, setActiveCount] = useState(0);
const [idBlur, setIdBlur] = useState(false);
const [idModalOpen, setIdModalOpen] = useState(false);
const isLoggedIn = !!user;
const capabilities = useMemo(
() => getAccessCapabilities(isLoggedIn, profile?.id_verified),
[isLoggedIn, profile?.id_verified]
);
const blurExplicit = useMemo(
() => shouldBlurExplicit(isLoggedIn, profile?.id_verified) || vanillaMode,
[isLoggedIn, profile?.id_verified, vanillaMode]
);
useEffect(() => {
setVanillaMode(localStorage.getItem("eg_vanilla_mode") === "true");
setIdBlur(mustBlurExplicit(profile?.id_verified));
const state = getUserState();
if (
isLoggedIn &&
requiresIdVerification(state) &&
!isIdVerified(profile?.id_verified) &&
localStorage.getItem("eg_id_verification_prompted") !== "true"
) {
const timer = setTimeout(() => setIdModalOpen(true), 1500);
const timer = setTimeout(() => setIdModalOpen(true), 2000);
return () => clearTimeout(timer);
}
}, [profile?.id_verified]);
}, [isLoggedIn, profile?.id_verified]);
const handleVanillaToggle = (v: boolean) => {
setVanillaMode(v);
@@ -67,7 +94,6 @@ function InnerProviders({ children }: ProvidersProps) {
};
const handleIdVerified = useCallback(() => {
setIdBlur(false);
localStorage.setItem("eg_id_verification_prompted", "true");
}, []);
@@ -77,36 +103,38 @@ function InnerProviders({ children }: ProvidersProps) {
return (
<VanillaModeContext.Provider value={vanillaMode}>
<IdBlurContext.Provider value={idBlur}>
<AccessContext.Provider value={{ capabilities, blurExplicit }}>
<OpenIdVerificationContext.Provider value={openIdVerification}>
<Nav
vanillaMode={vanillaMode}
onVanillaToggle={handleVanillaToggle}
activeCount={activeCount}
onIdVerifyClick={openIdVerification}
idBlur={idBlur}
/>
<MobileHeader
vanillaMode={vanillaMode}
onVanillaToggle={handleVanillaToggle}
activeCount={activeCount}
/>
<main className="md:pt-14 pt-12 pb-16 md:pb-0 min-h-screen min-h-[100dvh] flex flex-col">
<ActiveCountContext.Provider value={setActiveCount}>
<div className="flex-1 flex flex-col">{children}</div>
<SiteFooter />
</ActiveCountContext.Provider>
</main>
<IdVerificationModal
open={idModalOpen}
onClose={() => {
setIdModalOpen(false);
localStorage.setItem("eg_id_verification_prompted", "true");
}}
onVerified={handleIdVerified}
/>
<Nav
vanillaMode={vanillaMode}
onVanillaToggle={handleVanillaToggle}
activeCount={activeCount}
onIdVerifyClick={openIdVerification}
idBlur={blurExplicit && isLoggedIn}
isGuest={!isLoggedIn}
/>
<MobileHeader
vanillaMode={vanillaMode}
onVanillaToggle={handleVanillaToggle}
activeCount={activeCount}
isGuest={!isLoggedIn}
/>
<main className="md:pt-14 pt-12 pb-16 md:pb-0 min-h-screen min-h-[100dvh] flex flex-col">
<ActiveCountContext.Provider value={setActiveCount}>
<div className="flex-1 flex flex-col">{children}</div>
<SiteFooter />
</ActiveCountContext.Provider>
</main>
<IdVerificationModal
open={idModalOpen}
onClose={() => {
setIdModalOpen(false);
localStorage.setItem("eg_id_verification_prompted", "true");
}}
onVerified={handleIdVerified}
/>
</OpenIdVerificationContext.Provider>
</IdBlurContext.Provider>
</AccessContext.Provider>
</VanillaModeContext.Provider>
);
}