feat: Add Focus Tools (ADHD-friendly productivity features)
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
- Add Focus Tools dashboard with collapsible help section - Implement Quick Wins page for tasks under 15 minutes - Add Single Task Focus mode to reduce overwhelm - Create Body Doubling page for virtual co-working - Add visual timer, break reminders, and energy tracking - Implement hyperfocus protection alerts - Add ADHD settings panel with customizable options - Include full English and German translations - Fix larger touch targets CSS to not break button layouts - Add Playwright tests for Focus Tools features
This commit is contained in:
@@ -0,0 +1,392 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useADHDMode, ADHDSettingsPanel, EnergyCheckIn } from '@/components/adhd';
|
||||
import { User, Task } from '@shared/schema';
|
||||
import { Brain, Zap, Focus, Users, Battery, TrendingUp, ArrowLeft, HelpCircle, Clock, Coffee, Sparkles, Timer, Split } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
import { useLocation } from 'wouter';
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function ADHDDashboardPage() {
|
||||
const { t } = useTranslation();
|
||||
const [, setLocation] = useLocation();
|
||||
const { isEnabled, settings } = useADHDMode();
|
||||
const [showEnergyCheckIn, setShowEnergyCheckIn] = useState(false);
|
||||
|
||||
const { data: user } = useQuery<User>({
|
||||
queryKey: ['/api/user'],
|
||||
});
|
||||
|
||||
const { data: tasks = [] } = useQuery<Task[]>({
|
||||
queryKey: ['/api/tasks'],
|
||||
});
|
||||
|
||||
const { data: breakStats } = useQuery({
|
||||
queryKey: ['/api/user/break-stats'],
|
||||
enabled: isEnabled,
|
||||
});
|
||||
|
||||
const { data: energyHistory } = useQuery({
|
||||
queryKey: ['/api/energy/history'],
|
||||
enabled: isEnabled,
|
||||
});
|
||||
|
||||
const completedToday = tasks.filter(t => {
|
||||
if (t.status !== 'done' || !t.updatedAt) return false;
|
||||
const today = new Date();
|
||||
const updated = new Date(t.updatedAt);
|
||||
return updated.toDateString() === today.toDateString();
|
||||
}).length;
|
||||
|
||||
const quickWins = tasks.filter(t =>
|
||||
t.status !== 'done' &&
|
||||
t.estimatedDuration &&
|
||||
t.estimatedDuration <= 15
|
||||
).length;
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: Zap,
|
||||
title: t('adhd.quickWins.title'),
|
||||
description: t('adhd.quickWins.description'),
|
||||
path: '/adhd/quick-wins',
|
||||
color: 'from-yellow-400 to-orange-500',
|
||||
stat: `${quickWins} ${t('adhd.dashboard.available', 'available')}`,
|
||||
},
|
||||
{
|
||||
icon: Focus,
|
||||
title: t('adhd.singleTask.title'),
|
||||
description: t('adhd.singleTask.description'),
|
||||
path: '/adhd/single-task',
|
||||
color: 'from-blue-400 to-indigo-500',
|
||||
stat: t('adhd.dashboard.focusMode', 'Focus mode'),
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: t('adhd.bodyDoubling.title'),
|
||||
description: t('adhd.bodyDoubling.description'),
|
||||
path: '/adhd/body-doubling',
|
||||
color: 'from-green-400 to-teal-500',
|
||||
stat: t('adhd.dashboard.coworking', 'Co-working'),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto space-y-6">
|
||||
<div className="flex items-center gap-4 mb-6">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setLocation('/')}
|
||||
>
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-3 rounded-xl bg-gradient-to-br from-purple-400 to-pink-500 text-white">
|
||||
<Brain className="h-6 w-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">{t('adhd.dashboard.title', 'ADHD Dashboard')}</h1>
|
||||
<p className="text-muted-foreground">{t('adhd.dashboard.subtitle', 'Tools designed for how your brain works')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Row */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 rounded-lg bg-green-100 dark:bg-green-900/30">
|
||||
<TrendingUp className="h-5 w-5 text-green-600 dark:text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-2xl font-bold">{completedToday}</p>
|
||||
<p className="text-xs text-muted-foreground">{t('adhd.dashboard.completedToday', 'Done today')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 rounded-lg bg-blue-100 dark:bg-blue-900/30">
|
||||
<Battery className="h-5 w-5 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-2xl font-bold capitalize">{user?.currentEnergyLevel || '-'}</p>
|
||||
<p className="text-xs text-muted-foreground">{t('adhd.dashboard.energyLevel', 'Energy')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 rounded-lg bg-yellow-100 dark:bg-yellow-900/30">
|
||||
<Zap className="h-5 w-5 text-yellow-600 dark:text-yellow-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-2xl font-bold">{quickWins}</p>
|
||||
<p className="text-xs text-muted-foreground">{t('adhd.dashboard.quickWins', 'Quick wins')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 rounded-lg bg-purple-100 dark:bg-purple-900/30">
|
||||
<Brain className="h-5 w-5 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-2xl font-bold">{user?.currentStreak || 0}</p>
|
||||
<p className="text-xs text-muted-foreground">{t('adhd.dashboard.streak', 'Day streak')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Energy Check-in Button */}
|
||||
{isEnabled && settings.energyCheckInsEnabled && !user?.todayEnergyCheckedIn && (
|
||||
<Card className="border-2 border-dashed border-primary/50 bg-primary/5">
|
||||
<CardContent className="p-4 flex items-center justify-between">
|
||||
<div>
|
||||
<p className="font-medium">{t('adhd.energy.checkInReminder', 'How are you feeling today?')}</p>
|
||||
<p className="text-sm text-muted-foreground">{t('adhd.energy.checkInBenefit', 'Track your energy to get better task suggestions')}</p>
|
||||
</div>
|
||||
<Button onClick={() => setShowEnergyCheckIn(true)}>
|
||||
{t('adhd.energy.checkIn', 'Check In')}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Feature Cards */}
|
||||
<div className="grid md:grid-cols-3 gap-4">
|
||||
{features.map((feature) => (
|
||||
<Card
|
||||
key={feature.path}
|
||||
className="cursor-pointer hover:shadow-lg transition-all hover:-translate-y-1"
|
||||
onClick={() => setLocation(feature.path)}
|
||||
>
|
||||
<CardHeader>
|
||||
<div className={`w-12 h-12 rounded-xl bg-gradient-to-br ${feature.color} flex items-center justify-center mb-2`}>
|
||||
<feature.icon className="h-6 w-6 text-white" />
|
||||
</div>
|
||||
<CardTitle className="text-lg">{feature.title}</CardTitle>
|
||||
<CardDescription>{feature.description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm font-medium text-primary">{feature.stat}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Settings Panel */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('adhd.settings.title', 'ADHD Settings')}</CardTitle>
|
||||
<CardDescription>{t('adhd.settings.description', 'Customize your ADHD-friendly experience')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ADHDSettingsPanel />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Collapsible Help Section */}
|
||||
<Card>
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
<AccordionItem value="help" className="border-none">
|
||||
<AccordionTrigger className="px-6 py-4 hover:no-underline">
|
||||
<div className="flex items-center gap-3">
|
||||
<HelpCircle className="h-5 w-5 text-primary" />
|
||||
<span className="font-semibold">{t('adhd.dashboard.helpTitle', 'How Focus Tools Help You')}</span>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="px-6 pb-6">
|
||||
<p className="text-muted-foreground mb-6">
|
||||
{t('adhd.dashboard.helpIntro', 'These tools are designed around how your brain works, not against it. Each feature addresses a specific challenge and provides practical strategies to help you get things done.')}
|
||||
</p>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{/* Quick Wins */}
|
||||
<div className="p-4 rounded-lg bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Zap className="h-4 w-4 text-yellow-600 dark:text-yellow-400" />
|
||||
<h4 className="font-medium">{t('adhd.dashboard.help.quickWins.title', 'Quick Wins')}</h4>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
<strong>{t('adhd.dashboard.help.why', 'Why')}:</strong> {t('adhd.dashboard.help.quickWins.why', 'Starting tasks feels overwhelming when everything seems big.')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
<strong>{t('adhd.dashboard.help.how', 'How')}:</strong> {t('adhd.dashboard.help.quickWins.how', 'Shows only tasks under 15 minutes. Small wins build momentum and dopamine.')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<strong>{t('adhd.dashboard.help.usage', 'Use it')}:</strong> {t('adhd.dashboard.help.quickWins.usage', 'Open Quick Wins when you feel stuck or need to build momentum.')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Single Task Focus */}
|
||||
<div className="p-4 rounded-lg bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Focus className="h-4 w-4 text-blue-600 dark:text-blue-400" />
|
||||
<h4 className="font-medium">{t('adhd.dashboard.help.singleTask.title', 'Single Task Focus')}</h4>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
<strong>{t('adhd.dashboard.help.why', 'Why')}:</strong> {t('adhd.dashboard.help.singleTask.why', 'Task lists are overwhelming. Seeing everything makes it hard to start anything.')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
<strong>{t('adhd.dashboard.help.how', 'How')}:</strong> {t('adhd.dashboard.help.singleTask.how', 'Shows only ONE task at a time. No distractions, no choices, just focus.')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<strong>{t('adhd.dashboard.help.usage', 'Use it')}:</strong> {t('adhd.dashboard.help.singleTask.usage', 'When you have many tasks and feel paralyzed by choice.')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 5-Minute Starter */}
|
||||
<div className="p-4 rounded-lg bg-orange-50 dark:bg-orange-900/20 border border-orange-200 dark:border-orange-800">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Clock className="h-4 w-4 text-orange-600 dark:text-orange-400" />
|
||||
<h4 className="font-medium">{t('adhd.dashboard.help.fiveMinute.title', '5-Minute Starter')}</h4>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
<strong>{t('adhd.dashboard.help.why', 'Why')}:</strong> {t('adhd.dashboard.help.fiveMinute.why', 'Getting started is the hardest part. Commitment feels impossible.')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
<strong>{t('adhd.dashboard.help.how', 'How')}:</strong> {t('adhd.dashboard.help.fiveMinute.how', 'Commit to just 5 minutes. Often, starting leads to continuing naturally.')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<strong>{t('adhd.dashboard.help.usage', 'Use it')}:</strong> {t('adhd.dashboard.help.fiveMinute.usage', 'For tasks you keep avoiding. Just 5 minutes, then decide if you continue.')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Visual Timer */}
|
||||
<div className="p-4 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Timer className="h-4 w-4 text-red-600 dark:text-red-400" />
|
||||
<h4 className="font-medium">{t('adhd.dashboard.help.visualTimer.title', 'Visual Timer')}</h4>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
<strong>{t('adhd.dashboard.help.why', 'Why')}:</strong> {t('adhd.dashboard.help.visualTimer.why', 'Time blindness makes it hard to feel time passing.')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
<strong>{t('adhd.dashboard.help.how', 'How')}:</strong> {t('adhd.dashboard.help.visualTimer.how', 'A visual countdown shows time disappearing, making it tangible.')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<strong>{t('adhd.dashboard.help.usage', 'Use it')}:</strong> {t('adhd.dashboard.help.visualTimer.usage', 'Set timers for work sessions. The visual helps maintain awareness.')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Break Reminders */}
|
||||
<div className="p-4 rounded-lg bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Coffee className="h-4 w-4 text-green-600 dark:text-green-400" />
|
||||
<h4 className="font-medium">{t('adhd.dashboard.help.breaks.title', 'Break Reminders')}</h4>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
<strong>{t('adhd.dashboard.help.why', 'Why')}:</strong> {t('adhd.dashboard.help.breaks.why', 'Hyperfocus makes you forget basic needs like rest, food, water.')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
<strong>{t('adhd.dashboard.help.how', 'How')}:</strong> {t('adhd.dashboard.help.breaks.how', 'Gentle reminders interrupt hyperfocus before burnout.')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<strong>{t('adhd.dashboard.help.usage', 'Use it')}:</strong> {t('adhd.dashboard.help.breaks.usage', 'Enable in settings. Take the breaks even when you feel productive.')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Energy Tracking */}
|
||||
<div className="p-4 rounded-lg bg-purple-50 dark:bg-purple-900/20 border border-purple-200 dark:border-purple-800">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Battery className="h-4 w-4 text-purple-600 dark:text-purple-400" />
|
||||
<h4 className="font-medium">{t('adhd.dashboard.help.energy.title', 'Energy Tracking')}</h4>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
<strong>{t('adhd.dashboard.help.why', 'Why')}:</strong> {t('adhd.dashboard.help.energy.why', 'Energy fluctuates unpredictably. Fighting low energy wastes time.')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
<strong>{t('adhd.dashboard.help.how', 'How')}:</strong> {t('adhd.dashboard.help.energy.how', 'Track your energy to match tasks to your current state.')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<strong>{t('adhd.dashboard.help.usage', 'Use it')}:</strong> {t('adhd.dashboard.help.energy.usage', 'Check in daily. Do hard tasks when high, easy tasks when low.')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Body Doubling */}
|
||||
<div className="p-4 rounded-lg bg-teal-50 dark:bg-teal-900/20 border border-teal-200 dark:border-teal-800">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Users className="h-4 w-4 text-teal-600 dark:text-teal-400" />
|
||||
<h4 className="font-medium">{t('adhd.dashboard.help.bodyDoubling.title', 'Body Doubling')}</h4>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
<strong>{t('adhd.dashboard.help.why', 'Why')}:</strong> {t('adhd.dashboard.help.bodyDoubling.why', 'Working alone feels impossible. Presence of others helps focus.')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
<strong>{t('adhd.dashboard.help.how', 'How')}:</strong> {t('adhd.dashboard.help.bodyDoubling.how', 'Virtual co-working sessions simulate having someone nearby.')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<strong>{t('adhd.dashboard.help.usage', 'Use it')}:</strong> {t('adhd.dashboard.help.bodyDoubling.usage', 'Join a session when working from home feels isolating.')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Task Breakdown */}
|
||||
<div className="p-4 rounded-lg bg-indigo-50 dark:bg-indigo-900/20 border border-indigo-200 dark:border-indigo-800">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Split className="h-4 w-4 text-indigo-600 dark:text-indigo-400" />
|
||||
<h4 className="font-medium">{t('adhd.dashboard.help.taskBreakdown.title', 'AI Task Breakdown')}</h4>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
<strong>{t('adhd.dashboard.help.why', 'Why')}:</strong> {t('adhd.dashboard.help.taskBreakdown.why', 'Big tasks feel impossible to start when you cannot see the steps.')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
<strong>{t('adhd.dashboard.help.how', 'How')}:</strong> {t('adhd.dashboard.help.taskBreakdown.how', 'AI breaks large tasks into small, actionable subtasks automatically.')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<strong>{t('adhd.dashboard.help.usage', 'Use it')}:</strong> {t('adhd.dashboard.help.taskBreakdown.usage', 'Click breakdown on any overwhelming task to get clear next steps.')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Hyperfocus Protection */}
|
||||
<div className="p-4 rounded-lg bg-pink-50 dark:bg-pink-900/20 border border-pink-200 dark:border-pink-800">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Sparkles className="h-4 w-4 text-pink-600 dark:text-pink-400" />
|
||||
<h4 className="font-medium">{t('adhd.dashboard.help.hyperfocus.title', 'Hyperfocus Protection')}</h4>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
<strong>{t('adhd.dashboard.help.why', 'Why')}:</strong> {t('adhd.dashboard.help.hyperfocus.why', 'Hyperfocus can make you lose hours on one thing while neglecting others.')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
<strong>{t('adhd.dashboard.help.how', 'How')}:</strong> {t('adhd.dashboard.help.hyperfocus.how', 'Alerts when you spend too long on a single task.')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<strong>{t('adhd.dashboard.help.usage', 'Use it')}:</strong> {t('adhd.dashboard.help.hyperfocus.usage', 'Enable in settings. When alerted, save your progress and decide consciously.')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</Card>
|
||||
|
||||
{/* Energy Check-in Modal */}
|
||||
{showEnergyCheckIn && (
|
||||
<EnergyCheckIn
|
||||
onClose={() => setShowEnergyCheckIn(false)}
|
||||
onSubmit={() => setShowEnergyCheckIn(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { BodyDoublingLobby } from '@/components/adhd';
|
||||
import { User } from '@shared/schema';
|
||||
import { Users, ArrowLeft } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useLocation } from 'wouter';
|
||||
|
||||
export default function BodyDoublingPage() {
|
||||
const { t } = useTranslation();
|
||||
const [, setLocation] = useLocation();
|
||||
|
||||
const { data: user } = useQuery<User>({
|
||||
queryKey: ['/api/user'],
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[400px]">
|
||||
<p className="text-muted-foreground">{t('common.loading', 'Loading...')}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto space-y-6">
|
||||
<div className="flex items-center gap-4 mb-6">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setLocation('/')}
|
||||
>
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-3 rounded-xl bg-gradient-to-br from-green-400 to-teal-500 text-white">
|
||||
<Users className="h-6 w-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">{t('adhd.bodyDoubling.title')}</h1>
|
||||
<p className="text-muted-foreground">{t('adhd.bodyDoubling.description')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gradient-to-br from-green-50 to-teal-50 dark:from-green-950/20 dark:to-teal-950/20 rounded-2xl p-6 border border-green-200 dark:border-green-800">
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
{t('adhd.bodyDoubling.tip', 'Working alongside others helps maintain focus. Join or create a session to stay accountable.')}
|
||||
</p>
|
||||
|
||||
<BodyDoublingLobby currentUserId={user.id} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { QuickWinList } from '@/components/adhd';
|
||||
import { Task } from '@shared/schema';
|
||||
import { Zap, ArrowLeft } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useLocation } from 'wouter';
|
||||
|
||||
export default function QuickWinsPage() {
|
||||
const { t } = useTranslation();
|
||||
const [, setLocation] = useLocation();
|
||||
|
||||
const { data: tasks = [] } = useQuery<Task[]>({
|
||||
queryKey: ['/api/tasks'],
|
||||
});
|
||||
|
||||
const handleTaskSelect = (taskId: string) => {
|
||||
setLocation(`/tasks?selected=${taskId}`);
|
||||
};
|
||||
|
||||
const handleTaskComplete = async (taskId: string) => {
|
||||
await fetch(`/api/tasks/${taskId}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ status: 'done' }),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto space-y-6">
|
||||
<div className="flex items-center gap-4 mb-6">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setLocation('/')}
|
||||
>
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-3 rounded-xl bg-gradient-to-br from-yellow-400 to-orange-500 text-white">
|
||||
<Zap className="h-6 w-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">{t('adhd.quickWins.title')}</h1>
|
||||
<p className="text-muted-foreground">{t('adhd.quickWins.description')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gradient-to-br from-yellow-50 to-orange-50 dark:from-yellow-950/20 dark:to-orange-950/20 rounded-2xl p-6 border border-yellow-200 dark:border-yellow-800">
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
{t('adhd.quickWins.tip', 'Small wins build momentum! Complete these quick tasks to boost your dopamine and build confidence.')}
|
||||
</p>
|
||||
|
||||
<QuickWinList
|
||||
tasks={tasks.filter(t => t.status !== 'done')}
|
||||
maxDuration={15}
|
||||
onTaskSelect={handleTaskSelect}
|
||||
onTaskComplete={handleTaskComplete}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { SingleTaskView, FiveMinuteStarter } from '@/components/adhd';
|
||||
import { Task } from '@shared/schema';
|
||||
import { Focus, ArrowLeft, Timer } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useLocation } from 'wouter';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
|
||||
export default function SingleTaskPage() {
|
||||
const { t } = useTranslation();
|
||||
const [, setLocation] = useLocation();
|
||||
const queryClient = useQueryClient();
|
||||
const [selectedTaskId, setSelectedTaskId] = useState<string | null>(null);
|
||||
const [showFiveMinStarter, setShowFiveMinStarter] = useState(false);
|
||||
|
||||
const { data: tasks = [] } = useQuery<Task[]>({
|
||||
queryKey: ['/api/tasks'],
|
||||
});
|
||||
|
||||
const activeTasks = tasks.filter(t => t.status !== 'done');
|
||||
const selectedTask = selectedTaskId ? tasks.find(t => t.id === selectedTaskId) : null;
|
||||
|
||||
const handleTaskComplete = async (taskId: string) => {
|
||||
await fetch(`/api/tasks/${taskId}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ status: 'done' }),
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: ['/api/tasks'] });
|
||||
setSelectedTaskId(null);
|
||||
};
|
||||
|
||||
const handleTaskUpdate = async (taskId: string, updates: Partial<Task>) => {
|
||||
await fetch(`/api/tasks/${taskId}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(updates),
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: ['/api/tasks'] });
|
||||
};
|
||||
|
||||
const handleFiveMinComplete = () => {
|
||||
setShowFiveMinStarter(false);
|
||||
// Continue with the task
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto space-y-6">
|
||||
<div className="flex items-center gap-4 mb-6">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setLocation('/')}
|
||||
>
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-3 rounded-xl bg-gradient-to-br from-blue-400 to-indigo-500 text-white">
|
||||
<Focus className="h-6 w-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">{t('adhd.singleTask.title')}</h1>
|
||||
<p className="text-muted-foreground">{t('adhd.singleTask.description')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!selectedTask ? (
|
||||
<div className="bg-gradient-to-br from-blue-50 to-indigo-50 dark:from-blue-950/20 dark:to-indigo-950/20 rounded-2xl p-8 border border-blue-200 dark:border-blue-800">
|
||||
<h2 className="text-lg font-semibold mb-4">{t('adhd.singleTask.selectTask', 'Select a task to focus on')}</h2>
|
||||
<Select value={selectedTaskId || ''} onValueChange={setSelectedTaskId}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder={t('adhd.singleTask.choosePlaceholder', 'Choose a task...')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{activeTasks.map(task => (
|
||||
<SelectItem key={task.id} value={task.id}>
|
||||
{task.title}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<div className="mt-6 flex gap-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowFiveMinStarter(true)}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Timer className="h-4 w-4" />
|
||||
{t('adhd.fiveMin.title')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<SingleTaskView
|
||||
task={selectedTask}
|
||||
onComplete={handleTaskComplete}
|
||||
onExit={() => setSelectedTaskId(null)}
|
||||
onUpdateTime={(taskId, seconds) => handleTaskUpdate(taskId, { timeTracked: seconds })}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showFiveMinStarter && selectedTask && (
|
||||
<FiveMinuteStarter
|
||||
task={selectedTask}
|
||||
onComplete={handleFiveMinComplete}
|
||||
onCancel={() => setShowFiveMinStarter(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user