diff --git a/app/api/auth/route.ts b/app/api/auth/route.ts index c8e55ef..d7f3816 100644 --- a/app/api/auth/route.ts +++ b/app/api/auth/route.ts @@ -4,16 +4,17 @@ import { generateMemorableToken, isValidToken } from '@/lib/auth/tokens'; import { setTokenCookie, getTokenCookie } from '@/lib/auth/cookies'; import { eq } from 'drizzle-orm'; -export async function GET(request: NextRequest) { +export async function GET() { try { // Check if user already has a token const existingToken = await getTokenCookie(); if (existingToken) { // Verify token exists in database - const [user] = await db.select().from(users).where(eq(users.token, existingToken)); + const userRows = await db.select().from(users).where(eq(users.token, existingToken)); - if (user) { + if (userRows.length > 0) { + const user = userRows[0]; return NextResponse.json({ authenticated: true, token: existingToken, @@ -31,20 +32,25 @@ export async function GET(request: NextRequest) { export async function POST(request: NextRequest) { try { - const body = await request.json(); + const body = (await request.json()) as { action: string; token?: string }; const { action, token } = body; if (action === 'create') { // Generate new token and create user const newToken = generateMemorableToken(); - const [newUser] = await db + const newUserRows = await db .insert(users) .values({ token: newToken, }) .returning(); + if (newUserRows.length === 0) { + throw new Error('Failed to create user'); + } + + const newUser = newUserRows[0]; await setTokenCookie(newToken); return NextResponse.json({ @@ -67,9 +73,9 @@ export async function POST(request: NextRequest) { } // Check if token exists - const [user] = await db.select().from(users).where(eq(users.token, token)); + const userRows = await db.select().from(users).where(eq(users.token, token)); - if (!user) { + if (userRows.length === 0) { return NextResponse.json( { success: false, @@ -79,6 +85,7 @@ export async function POST(request: NextRequest) { ); } + const user = userRows[0]; await setTokenCookie(token); return NextResponse.json({ diff --git a/app/api/habits/[id]/log/route.ts b/app/api/habits/[id]/log/route.ts index a540b0d..3db92d8 100644 --- a/app/api/habits/[id]/log/route.ts +++ b/app/api/habits/[id]/log/route.ts @@ -7,8 +7,8 @@ async function getUserFromToken() { const token = await getTokenCookie(); if (!token) return null; - const [user] = await db.select().from(users).where(eq(users.token, token)); - return user; + const userRows = await db.select().from(users).where(eq(users.token, token)); + return userRows.length > 0 ? userRows[0] : null; } export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { @@ -26,20 +26,20 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ } // Verify habit belongs to user - const [habit] = await db + const habitRows = await db .select() .from(habits) .where(and(eq(habits.id, habitId), eq(habits.userId, user.id))); - if (!habit) { + if (habitRows.length === 0) { return NextResponse.json({ error: 'Habit not found' }, { status: 404 }); } - const body = await request.json(); + const body = (await request.json()) as { note?: string }; const { note } = body; // Create log entry - const [log] = await db + const logRows = await db .insert(habitLogs) .values({ habitId, @@ -47,6 +47,11 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ }) .returning(); + if (logRows.length === 0) { + throw new Error('Failed to create log entry'); + } + + const log = logRows[0]; return NextResponse.json({ log }); } catch (error) { console.error('Log habit error:', error); diff --git a/app/api/habits/route.ts b/app/api/habits/route.ts index 27d911e..f047716 100644 --- a/app/api/habits/route.ts +++ b/app/api/habits/route.ts @@ -7,11 +7,11 @@ async function getUserFromToken() { const token = await getTokenCookie(); if (!token) return null; - const [user] = await db.select().from(users).where(eq(users.token, token)); - return user; + const userRows = await db.select().from(users).where(eq(users.token, token)); + return userRows.length > 0 ? userRows[0] : null; } -export async function GET(request: NextRequest) { +export async function GET() { try { const user = await getUserFromToken(); if (!user) { @@ -83,7 +83,13 @@ export async function POST(request: NextRequest) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } - const body = await request.json(); + const body = (await request.json()) as { + name: string; + type: string; + targetFrequency?: { value: number; period: 'day' | 'week' | 'month' }; + color?: string; + icon?: string; + }; const { name, type, targetFrequency, color, icon } = body; if (!name || !type) { @@ -95,18 +101,33 @@ export async function POST(request: NextRequest) { ); } - const [newHabit] = await db + // Validate type is one of the allowed enum values + if (!['positive', 'neutral', 'negative'].includes(type)) { + return NextResponse.json( + { + error: 'Type must be one of: positive, neutral, negative', + }, + { status: 400 }, + ); + } + + const newHabitRows = await db .insert(habits) .values({ userId: user.id, name, - type, + type: type as 'positive' | 'neutral' | 'negative', targetFrequency, color, icon, }) .returning(); + if (newHabitRows.length === 0) { + throw new Error('Failed to create habit'); + } + + const newHabit = newHabitRows[0]; return NextResponse.json({ habit: newHabit }); } catch (error) { console.error('Create habit error:', error); diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 8a3f274..5ddc73f 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -36,6 +36,12 @@ import { Alert, AlertDescription } from '@/components/ui/alert'; import { Separator } from '@/components/ui/separator'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +interface AuthData { + authenticated: boolean; + token?: string; + userId?: string; +} + interface Habit { id: number; name: string; @@ -47,6 +53,23 @@ interface Habit { createdAt: string; } +interface HabitsResponse { + habits: Habit[]; +} + +interface LogResponse { + log: { + id: number; + habitId: number; + loggedAt: string; + note?: string; + }; +} + +interface HabitResponse { + habit: Habit; +} + export default function Dashboard() { const router = useRouter(); const queryClient = useQueryClient(); @@ -57,11 +80,11 @@ export default function Dashboard() { const [copiedToken, setCopiedToken] = useState(false); // Check authentication - const { data: authData, isLoading: authLoading } = useQuery({ + const { data: authData, isLoading: authLoading } = useQuery({ queryKey: ['auth'], - queryFn: async () => { + queryFn: async (): Promise => { const res = await fetch('/api/auth'); - const data = await res.json(); + const data = (await res.json()) as AuthData; if (!data.authenticated) { router.push('/'); } @@ -76,45 +99,45 @@ export default function Dashboard() { }, [authData]); // Fetch habits - const { data: habitsData, isLoading: habitsLoading } = useQuery({ + const { data: habitsData, isLoading: habitsLoading } = useQuery({ queryKey: ['habits'], - queryFn: async () => { + queryFn: async (): Promise => { const res = await fetch('/api/habits'); if (!res.ok) throw new Error('Failed to fetch habits'); - return res.json(); + return res.json() as Promise; }, enabled: !!authData?.authenticated, }); // Log habit mutation - const logHabitMutation = useMutation({ - mutationFn: async (habitId: number) => { - const res = await fetch(`/api/habits/${habitId}/log`, { + const logHabitMutation = useMutation({ + mutationFn: async (habitId: number): Promise => { + const res = await fetch(`/api/habits/${String(habitId)}/log`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}), }); if (!res.ok) throw new Error('Failed to log habit'); - return res.json(); + return res.json() as Promise; }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['habits'] }); + void queryClient.invalidateQueries({ queryKey: ['habits'] }); }, }); // Create habit mutation - const createHabitMutation = useMutation({ - mutationFn: async (data: { name: string; type: string }) => { + const createHabitMutation = useMutation({ + mutationFn: async (data: { name: string; type: string }): Promise => { const res = await fetch('/api/habits', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); if (!res.ok) throw new Error('Failed to create habit'); - return res.json(); + return res.json() as Promise; }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['habits'] }); + void queryClient.invalidateQueries({ queryKey: ['habits'] }); setShowNewHabitDialog(false); setNewHabitName(''); setNewHabitType('neutral'); @@ -132,9 +155,11 @@ export default function Dashboard() { const copyToken = () => { if (userToken) { - navigator.clipboard.writeText(userToken); + void navigator.clipboard.writeText(userToken); setCopiedToken(true); - setTimeout(() => setCopiedToken(false), 2000); + setTimeout(() => { + setCopiedToken(false); + }, 2000); } }; @@ -199,7 +224,7 @@ export default function Dashboard() { ); } - const habits = habitsData?.habits || []; + const habits = habitsData?.habits ?? []; return (
@@ -233,13 +258,20 @@ export default function Dashboard() { id="name" placeholder="e.g., Exercise, Read, Meditate..." value={newHabitName} - onChange={(e) => setNewHabitName(e.target.value)} + onChange={(e) => { + setNewHabitName(e.target.value); + }} className="border-zinc-800 bg-zinc-900" />
- { + setNewHabitType(value); + }} + > @@ -267,7 +299,12 @@ export default function Dashboard() {
-