Set up project configuration and base UI components

Initializes the project with necessary configurations, including Replit settings and a .gitignore file. It also introduces foundational UI components for the application, such as buttons, dialogs, and layout elements, likely for the Pomodoro timer application.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 59a5ae27-3c71-459b-b42f-fe14121bf9c3
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3007b6f6-d03b-45e1-9ed1-7ce8de18ea24/59a5ae27-3c71-459b-b42f-fe14121bf9c3/Uupe4F4
This commit is contained in:
ingecarlosgutie
2025-08-31 20:11:52 +00:00
parent 64aa1e69a0
commit 80595e7002
89 changed files with 17597 additions and 0 deletions

View File

@@ -0,0 +1,126 @@
import { useState } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/atoms/Button";
interface Preset {
name: string;
description: string;
focus: number;
break: number;
}
interface DurationSelectorProps {
selectedFocus: number;
selectedBreak: number;
onDurationChange: (focus: number, breakDuration: number) => void;
className?: string;
}
const presets: Preset[] = [
{ name: "Classic", description: "25 min focus / 5 min break", focus: 25, break: 5 },
{ name: "Extended", description: "50 min focus / 10 min break", focus: 50, break: 10 },
];
export function DurationSelector({
selectedFocus,
selectedBreak,
onDurationChange,
className = ""
}: DurationSelectorProps) {
const [customFocus, setCustomFocus] = useState(selectedFocus);
const [customBreak, setCustomBreak] = useState(selectedBreak);
const [selectedPreset, setSelectedPreset] = useState<string | null>(
presets.find(p => p.focus === selectedFocus && p.break === selectedBreak)?.name || null
);
const handlePresetSelect = (preset: Preset) => {
setSelectedPreset(preset.name);
setCustomFocus(preset.focus);
setCustomBreak(preset.break);
onDurationChange(preset.focus, preset.break);
};
const handleCustomChange = (focus: number, breakDuration: number) => {
setSelectedPreset(null);
setCustomFocus(focus);
setCustomBreak(breakDuration);
onDurationChange(focus, breakDuration);
};
return (
<Card className={className}>
<CardHeader>
<CardTitle>Duration</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
{presets.map((preset) => (
<Button
key={preset.name}
variant="outline"
className={`w-full p-3 h-auto text-left group ${
selectedPreset === preset.name ? 'border-primary bg-primary/5' : ''
}`}
onClick={() => handlePresetSelect(preset)}
data-testid={`button-preset-${preset.name.toLowerCase()}`}
>
<div className="flex items-center justify-between w-full">
<div>
<div className={`font-medium ${
selectedPreset === preset.name ? 'text-primary' : 'group-hover:text-primary'
} transition-colors`}>
{preset.name}
</div>
<div className="text-sm text-muted-foreground">
{preset.description}
</div>
</div>
<div className={`w-4 h-4 rounded-full border-2 ${
selectedPreset === preset.name
? 'border-primary bg-primary'
: 'border-muted-foreground'
}`} />
</div>
</Button>
))}
<div className="border border-border rounded-lg p-3">
<div className="font-medium mb-3">Custom</div>
<div className="space-y-3">
<div>
<Label htmlFor="custom-focus" className="text-sm text-muted-foreground">
Focus (minutes)
</Label>
<Input
id="custom-focus"
type="number"
value={customFocus}
min="1"
max="120"
onChange={(e) => handleCustomChange(parseInt(e.target.value) || 1, customBreak)}
className="mt-1"
data-testid="input-custom-focus"
/>
</div>
<div>
<Label htmlFor="custom-break" className="text-sm text-muted-foreground">
Break (minutes)
</Label>
<Input
id="custom-break"
type="number"
value={customBreak}
min="1"
max="30"
onChange={(e) => handleCustomChange(customFocus, parseInt(e.target.value) || 1)}
className="mt-1"
data-testid="input-custom-break"
/>
</div>
</div>
</div>
</CardContent>
</Card>
);
}

View File

@@ -0,0 +1,89 @@
import { useQuery } from "@tanstack/react-query";
import { Lightbulb, ArrowRight } from "lucide-react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/atoms/Button";
import { useStore } from "@/lib/store";
interface Suggestion {
minutes: number;
reason: string;
}
interface SuggestionChipsProps {
onSuggestionSelect: (minutes: number) => void;
className?: string;
}
export function SuggestionChips({ onSuggestionSelect, className = "" }: SuggestionChipsProps) {
const deviceId = useStore((state) => state.deviceId);
const { data: suggestions, isLoading } = useQuery<{ suggestions: Suggestion[] }>({
queryKey: ['/api/suggestions', { deviceId }],
enabled: !!deviceId,
});
if (isLoading) {
return (
<Card className={className}>
<CardHeader>
<CardTitle className="flex items-center">
<Lightbulb className="text-accent mr-3" />
Smart Suggestions
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{[1, 2, 3].map((i) => (
<div key={i} className="p-4 border border-border rounded-lg animate-pulse">
<div className="h-4 bg-muted rounded mb-2"></div>
<div className="h-3 bg-muted rounded w-3/4"></div>
</div>
))}
</div>
</CardContent>
</Card>
);
}
const suggestionList = suggestions?.suggestions || [
{ minutes: 25, reason: "Classic Pomodoro technique - great for starting out." },
{ minutes: 15, reason: "Short sprints help build focus habits." },
{ minutes: 40, reason: "Extended sessions for deep work." }
];
return (
<Card className={className}>
<CardHeader>
<CardTitle className="flex items-center">
<Lightbulb className="text-accent mr-3" />
Smart Suggestions
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{suggestionList.map((suggestion, index) => (
<Button
key={index}
variant="outline"
className="p-4 h-auto text-left group hover:border-primary hover:bg-primary/5 transition-all"
onClick={() => onSuggestionSelect(suggestion.minutes)}
data-testid={`button-suggestion-${suggestion.minutes}`}
>
<div className="w-full">
<div className="flex items-center justify-between mb-2">
<span className="font-medium group-hover:text-primary transition-colors">
{suggestion.minutes} min {suggestion.minutes >= 30 ? 'Deep Work' : suggestion.minutes <= 15 ? 'Sprint' : 'Focus'}
</span>
<ArrowRight className="w-4 h-4 text-muted-foreground group-hover:text-primary transition-colors" />
</div>
<p className="text-sm text-muted-foreground">
{suggestion.reason}
</p>
</div>
</Button>
))}
</div>
</CardContent>
</Card>
);
}

View File

@@ -0,0 +1,61 @@
import { Play, Pause, RotateCcw, SkipForward } from "lucide-react";
import { Button } from "@/components/atoms/Button";
interface TimerControlsProps {
isRunning: boolean;
onPlayPause: () => void;
onReset: () => void;
onSkip: () => void;
className?: string;
}
export function TimerControls({
isRunning,
onPlayPause,
onReset,
onSkip,
className = ""
}: TimerControlsProps) {
return (
<div className={`flex items-center justify-center space-x-4 ${className}`}>
<Button
variant="secondary"
size="icon"
onClick={onReset}
className="p-3 rounded-full"
title="Reset (R)"
data-testid="button-reset"
>
<RotateCcw className="w-5 h-5" />
</Button>
<Button
variant="default"
size="lg"
onClick={onPlayPause}
className={`p-6 rounded-full transition-all hover:scale-105 ${
isRunning ? 'pulse-effect' : ''
}`}
title="Start/Pause (Space)"
data-testid="button-play-pause"
>
{isRunning ? (
<Pause className="w-6 h-6" />
) : (
<Play className="w-6 h-6" />
)}
</Button>
<Button
variant="secondary"
size="icon"
onClick={onSkip}
className="p-3 rounded-full"
title="Skip Break (S)"
data-testid="button-skip"
>
<SkipForward className="w-5 h-5" />
</Button>
</div>
);
}