Audit: Fix FamilyProvider, add signout, remove hardcoded defaults

This commit is contained in:
Manohar Gupta 2026-05-10 23:54:56 +05:30
parent 35895d226f
commit a95f55967d
7 changed files with 44 additions and 15 deletions

View file

@ -2,6 +2,7 @@
import { useState, useEffect, createContext, useContext } from "react"; import { useState, useEffect, createContext, useContext } from "react";
import { ReactNode } from "react"; import { ReactNode } from "react";
import { useRouter } from "next/navigation";
interface Child { interface Child {
id: string; id: string;
@ -35,6 +36,7 @@ export function useFamily() {
} }
export function FamilyProvider({ children: providerChildren }: { children: ReactNode }) { export function FamilyProvider({ children: providerChildren }: { children: ReactNode }) {
const router = useRouter();
const [familyId, setFamilyId] = useState<string | null>(null); const [familyId, setFamilyId] = useState<string | null>(null);
const [childId, setChildId] = useState<string | null>(null); const [childId, setChildId] = useState<string | null>(null);
const [child, setChild] = useState<Child | null>(null); const [child, setChild] = useState<Child | null>(null);
@ -50,17 +52,22 @@ export function FamilyProvider({ children: providerChildren }: { children: React
const sessionRes = await fetch("/api/auth/signin"); const sessionRes = await fetch("/api/auth/signin");
const sessionData = await sessionRes.json(); const sessionData = await sessionRes.json();
// Not authenticated - redirect to login
if (!sessionData.authenticated) { if (!sessionData.authenticated) {
// Not logged in, use default router.push("/login");
setFamilyId("default");
setLoading(false); setLoading(false);
return; return;
} }
const sessionFamilyId = sessionData.familyId || "default"; // Authenticated but no family - go to onboarding
if (!sessionData.familyId) {
router.push("/onboarding");
setLoading(false);
return;
}
// Fetch children for this family // Fetch children for this family
const res = await fetch(`/api/children?familyId=${sessionFamilyId}`); const res = await fetch(`/api/children?familyId=${sessionData.familyId}`);
const data = await res.json(); const data = await res.json();
if (data.children?.length > 0) { if (data.children?.length > 0) {
@ -74,9 +81,12 @@ export function FamilyProvider({ children: providerChildren }: { children: React
setChildren(childList); setChildren(childList);
setChild(childList[0]); setChild(childList[0]);
setChildId(childList[0].id); setChildId(childList[0].id);
} else {
// No children - go to onboarding
router.push("/onboarding");
} }
setFamilyId(sessionFamilyId); setFamilyId(sessionData.familyId);
setTier(sessionData.tier || "free"); setTier(sessionData.tier || "free");
setMemberCount(2); setMemberCount(2);
} catch (err) { } catch (err) {
@ -87,7 +97,7 @@ export function FamilyProvider({ children: providerChildren }: { children: React
} }
fetchFamilyData(); fetchFamilyData();
}, []); }, [router]);
return ( return (
<FamilyContext.Provider <FamilyContext.Provider

View file

@ -5,18 +5,16 @@ import { cookies } from "next/headers";
export async function POST() { export async function POST() {
try { try {
const cookieStore = await cookies(); const cookieStore = await cookies();
const sessionToken = cookieStore.get("session")?.value; const sessionToken = cookieStore.get("tia_session")?.value;
if (sessionToken) { if (sessionToken) {
// Delete session from database
await sql` await sql`
DELETE FROM sessions WHERE session_token = ${sessionToken} DELETE FROM sessions WHERE session_token = ${sessionToken}
`; `;
} }
// Clear cookie
const response = NextResponse.json({ success: true }); const response = NextResponse.json({ success: true });
response.cookies.set("session", "", { response.cookies.set("tia_session", "", {
httpOnly: true, httpOnly: true,
secure: process.env.NODE_ENV === "production", secure: process.env.NODE_ENV === "production",
sameSite: "lax", sameSite: "lax",

View file

@ -4,7 +4,7 @@ import { sql } from "@/db";
// GET - list children // GET - list children
export async function GET(request: Request) { export async function GET(request: Request) {
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const familyId = searchParams.get("familyId") || "default"; const familyId = searchParams.get("familyId") || null;
try { try {
const children = await sql.unsafe( const children = await sql.unsafe(

View file

@ -4,7 +4,7 @@ import { sql } from "@/db";
// GET - list family members // GET - list family members
export async function GET(request: Request) { export async function GET(request: Request) {
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const familyId = searchParams.get("familyId") || "default"; const familyId = searchParams.get("familyId") || null;
try { try {
const members = await sql.unsafe( const members = await sql.unsafe(

View file

@ -4,7 +4,7 @@ import { sql } from "@/db";
// GET - get family details // GET - get family details
export async function GET(request: Request) { export async function GET(request: Request) {
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const familyId = searchParams.get("familyId") || "default"; const familyId = searchParams.get("familyId");
try { try {
const family = await sql.unsafe( const family = await sql.unsafe(

View file

@ -5,7 +5,7 @@ import { randomBytes } from "crypto";
// GET - list invites for a family // GET - list invites for a family
export async function GET(request: Request) { export async function GET(request: Request) {
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const familyId = searchParams.get("familyId") || "default"; const familyId = searchParams.get("familyId") || null;
try { try {
const invites = await sql.unsafe( const invites = await sql.unsafe(

View file

@ -38,11 +38,23 @@ export default function SettingsPage() {
const [inviteRole, setInviteRole] = useState("caregiver"); const [inviteRole, setInviteRole] = useState("caregiver");
const [inviteLoading, setInviteLoading] = useState(false); const [inviteLoading, setInviteLoading] = useState(false);
const [familyName, setFamilyName] = useState(""); const [familyName, setFamilyName] = useState("");
const [pediatricianPhone, setPediatricianPhone] = useState(""); const [signingOut, setSigningOut] = useState(false);
// Check if can invite more members // Check if can invite more members
const canInvite = tier === "pro" || memberCount < 2; const canInvite = tier === "pro" || memberCount < 2;
const handleSignOut = async () => {
if (!confirm("Are you sure you want to sign out?")) return;
setSigningOut(true);
try {
await fetch("/api/auth/signout", { method: "POST" });
router.push("/login");
} catch (err) {
console.error("Sign out failed:", err);
}
setSigningOut(false);
};
const themeOptions = [ const themeOptions = [
{ value: "light", label: "Light" }, { value: "light", label: "Light" },
{ value: "dark", label: "Dark" }, { value: "dark", label: "Dark" },
@ -317,6 +329,15 @@ export default function SettingsPage() {
<div className="font-medium">App Version</div> <div className="font-medium">App Version</div>
<div className="text-sm text-gray-500">Tia v1.0.0</div> <div className="text-sm text-gray-500">Tia v1.0.0</div>
</div> </div>
{/* Sign Out */}
<button
onClick={handleSignOut}
disabled={signingOut}
className="w-full p-4 mt-4 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 rounded-xl font-medium disabled:opacity-50"
>
{signingOut ? "Signing out..." : "Sign Out"}
</button>
</div> </div>
</div> </div>
); );