feat: Phase 0 walking skeleton — auth, Todoist integration, tip page
- Google OAuth2/PKCE flow via openid-client v6; session cookie (30-day) - Next.js middleware auth guard — redirects before any client render - Todoist OAuth2 connect/disconnect; REST v1 task fetch (today|overdue) - RandomPolicy recommender behind stable POST /recommend contract - Feedback endpoint (done/dismiss/snooze); marks task complete in Todoist - 30s in-memory task cache per user (~1ms recommend on cache hit) - Tip page: pure opacity fade-in (3.5s), fast fade-out (0.3s), no motion - "reading you…" loading text with breathe animation - PWA icons + manifest - Ports pinned: API=3078, web=3079; Caddy at o.alogins.net Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,25 +1,22 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { getSession, getIntegrations, disconnectIntegration } from '@/lib/api';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { getIntegrations, disconnectIntegration } from '@/lib/api';
|
||||
import type { Integration } from '@oo/shared-types';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
function ConnectPageInner() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const [integrations, setIntegrations] = useState<Integration[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [disconnecting, setDisconnecting] = useState<string | null>(null);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
const { user } = await getSession();
|
||||
if (!user) { router.replace('/sign-in'); return; }
|
||||
const { integrations: list } = await getIntegrations();
|
||||
setIntegrations(list);
|
||||
setLoading(false);
|
||||
}, [router]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => { load(); }, [load]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user