This commit is contained in:
@@ -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<AuthData>({
|
||||
queryKey: ['auth'],
|
||||
queryFn: async () => {
|
||||
queryFn: async (): Promise<AuthData> => {
|
||||
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<HabitsResponse>({
|
||||
queryKey: ['habits'],
|
||||
queryFn: async () => {
|
||||
queryFn: async (): Promise<HabitsResponse> => {
|
||||
const res = await fetch('/api/habits');
|
||||
if (!res.ok) throw new Error('Failed to fetch habits');
|
||||
return res.json();
|
||||
return res.json() as Promise<HabitsResponse>;
|
||||
},
|
||||
enabled: !!authData?.authenticated,
|
||||
});
|
||||
|
||||
// Log habit mutation
|
||||
const logHabitMutation = useMutation({
|
||||
mutationFn: async (habitId: number) => {
|
||||
const res = await fetch(`/api/habits/${habitId}/log`, {
|
||||
const logHabitMutation = useMutation<LogResponse, Error, number>({
|
||||
mutationFn: async (habitId: number): Promise<LogResponse> => {
|
||||
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<LogResponse>;
|
||||
},
|
||||
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<HabitResponse, Error, { name: string; type: string }>({
|
||||
mutationFn: async (data: { name: string; type: string }): Promise<HabitResponse> => {
|
||||
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<HabitResponse>;
|
||||
},
|
||||
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 (
|
||||
<div className="min-h-screen bg-black">
|
||||
@@ -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"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="type">Habit Type</Label>
|
||||
<Select value={newHabitType} onValueChange={(value: any) => setNewHabitType(value)}>
|
||||
<Select
|
||||
value={newHabitType}
|
||||
onValueChange={(value: 'positive' | 'neutral' | 'negative') => {
|
||||
setNewHabitType(value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="border-zinc-800 bg-zinc-900">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
@@ -267,7 +299,12 @@ export default function Dashboard() {
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setShowNewHabitDialog(false)}>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setShowNewHabitDialog(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
@@ -317,7 +354,9 @@ export default function Dashboard() {
|
||||
className={`transform cursor-pointer transition-all duration-200 hover:scale-[1.02] ${getHabitCardClass(
|
||||
habit.type,
|
||||
)} ${logHabitMutation.isPending ? 'opacity-75' : ''}`}
|
||||
onClick={() => logHabitMutation.mutate(habit.id)}
|
||||
onClick={() => {
|
||||
logHabitMutation.mutate(habit.id);
|
||||
}}
|
||||
>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-start justify-between">
|
||||
@@ -384,7 +423,9 @@ export default function Dashboard() {
|
||||
<h3 className="mb-2 text-lg font-semibold">No habits yet</h3>
|
||||
<p className="mb-4 text-sm text-zinc-500">Start building better habits today</p>
|
||||
<Button
|
||||
onClick={() => setShowNewHabitDialog(true)}
|
||||
onClick={() => {
|
||||
setShowNewHabitDialog(true);
|
||||
}}
|
||||
className="bg-emerald-600 hover:bg-emerald-700"
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
|
Reference in New Issue
Block a user