feat: add social features, leaderboard, auth enhancements, and admin fixes
continuous-integration/drone/push Build is passing

- Implement Social Features: Shared Tasks, Global Access, Privacy Settings (Leaderboard/Searchable).
- Add Leaderboard Page and API.
- Enhance Auth: Support Email/Username login, explicit duplicate registration errors.
- Fix: Admin login password hash regression.
- Refactor: Move to wouter for routing, add Admin Dashboard and User Management.
- Add Setup Wizard.
- Update UI with Sidebar and Gamification elements.
This commit is contained in:
2025-12-10 14:04:26 +01:00
parent d5b045158a
commit ccfb674318
52 changed files with 9206 additions and 1505 deletions
+74 -20
View File
@@ -10,6 +10,8 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover
import { CalendarIcon, Plus, Tag } from 'lucide-react';
import { Task, Label } from '@shared/schema';
import { useQuery } from '@tanstack/react-query';
import { parseTaskInput } from '../lib/nlp';
import { Sparkles } from 'lucide-react';
interface TaskCreationModalProps {
isOpen: boolean;
@@ -22,9 +24,13 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [priority, setPriority] = useState<'low' | 'medium' | 'high'>('medium');
const [energyLevel, setEnergyLevel] = useState<'low' | 'medium' | 'high'>('medium');
const [estimatedDuration, setEstimatedDuration] = useState<number | undefined>();
const [dueDate, setDueDate] = useState<Date | undefined>();
const [labelId, setLabelId] = useState<string | undefined>();
const [isCalendarOpen, setIsCalendarOpen] = useState(false);
const [error, setError] = useState<string | null>(null);
// Fetch labels
const { data: labels = [] } = useQuery<Label[]>({
@@ -32,28 +38,34 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
});
const handleSave = () => {
if (!title.trim()) return;
if (!title.trim()) {
setError(t('taskCreation.titleRequired') || 'Title is required');
return;
}
const newTask: Partial<Task> = {
title: title.trim(),
description: description.trim() || undefined,
priority,
energyLevel,
estimatedDuration,
dueDate,
labelId,
status: 'todo',
timeTracked: 0,
isTracking: false
};
onSave(newTask);
console.log('New task created:', newTask);
// Reset form
setTitle('');
setDescription('');
setPriority('medium');
setDueDate(undefined);
setLabelId(undefined);
setError(null);
onClose();
};
@@ -70,6 +82,7 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
setPriority('medium');
setDueDate(undefined);
setLabelId(undefined);
setError(null);
onClose();
};
@@ -82,20 +95,39 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
{t('taskCreation.title')}
</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<Input
placeholder={t('taskCreation.titlePlaceholder')}
value={title}
onChange={(e) => setTitle(e.target.value)}
onChange={(e) => {
setTitle(e.target.value);
const parsed = parseTaskInput(e.target.value);
if (parsed.priority) setPriority(parsed.priority);
if (parsed.dueDate) setDueDate(parsed.dueDate);
if (parsed.labelName) {
const foundLabel = labels.find(l => l.name.toLowerCase() === parsed.labelName?.toLowerCase());
if (foundLabel) setLabelId(foundLabel.id);
}
if (error) setError(null);
}}
onKeyDown={handleKeyDown}
className="text-base"
data-testid="input-task-title"
autoFocus
/>
<div className="flex items-center gap-1 mt-1">
{title.includes('!') || title.includes('#') || title.toLowerCase().includes('tomorrow') ? (
<div className="bg-violet-100 dark:bg-violet-900/30 text-violet-600 dark:text-violet-300 text-xs px-2 py-0.5 rounded-full flex items-center gap-1 animate-pulse">
<Sparkles className="w-3 h-3" />
Smart Input Active
</div>
) : null}
{error && <p className="text-sm text-destructive">{error}</p>}
</div>
</div>
<div>
<Textarea
placeholder={t('taskCreation.descriptionPlaceholder')}
@@ -106,8 +138,8 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
data-testid="input-task-description"
/>
</div>
<div className="flex gap-3">
<div className="flex flex-col sm:flex-row gap-3">
<div className="flex-1">
<Select value={priority} onValueChange={(value: 'low' | 'medium' | 'high') => setPriority(value)}>
<SelectTrigger data-testid="select-task-priority">
@@ -120,7 +152,30 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
</SelectContent>
</Select>
</div>
<div className="flex-1">
<Select value={energyLevel} onValueChange={(value: 'low' | 'medium' | 'high') => setEnergyLevel(value)}>
<SelectTrigger>
<SelectValue placeholder="Energy" />
</SelectTrigger>
<SelectContent>
<SelectItem value="low"> Low Energy</SelectItem>
<SelectItem value="medium"> Medium Energy</SelectItem>
<SelectItem value="high"> High Energy</SelectItem>
</SelectContent>
</Select>
</div>
<div className="w-24">
<Input
type="number"
placeholder="Min"
value={estimatedDuration || ''}
onChange={(e) => setEstimatedDuration(e.target.value ? parseInt(e.target.value) : undefined)}
className="text-sm"
/>
</div>
<div className="flex-1">
<Select value={labelId || 'none'} onValueChange={(value) => setLabelId(value === 'none' ? undefined : value)}>
<SelectTrigger data-testid="select-task-label">
@@ -131,8 +186,8 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
{labels.map((label) => (
<SelectItem key={label.id} value={label.id}>
<div className="flex items-center gap-2">
<div
className="w-3 h-3 rounded"
<div
className="w-3 h-3 rounded"
style={{ backgroundColor: label.color }}
/>
{label.name}
@@ -142,11 +197,11 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
</SelectContent>
</Select>
</div>
<Popover open={isCalendarOpen} onOpenChange={setIsCalendarOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
<Button
variant="outline"
className="flex items-center gap-2"
data-testid="button-due-date"
>
@@ -168,19 +223,18 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
</PopoverContent>
</Popover>
</div>
<div className="flex gap-3 pt-2">
<Button
variant="outline"
<Button
variant="outline"
onClick={handleClose}
className="flex-1"
data-testid="button-cancel"
>
{t('taskCreation.cancel')}
</Button>
<Button
<Button
onClick={handleSave}
disabled={!title.trim()}
className="flex-1"
data-testid="button-save-task"
>