feat: add social features, leaderboard, auth enhancements, and admin fixes
continuous-integration/drone/push Build is passing
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:
@@ -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"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user