HydroFlux 0.0.2
This commit is contained in:
536
Hydroflux/REPLICATION_GUIDE.md
Normal file
536
Hydroflux/REPLICATION_GUIDE.md
Normal file
@@ -0,0 +1,536 @@
|
||||
# Hydro Flux Dashboard - Complete Replication Guide
|
||||
|
||||
## Overview
|
||||
A mobile-first health tracking dashboard called "Hydro Flux" that tracks water intake, steps, sleep, Strava integration, goals, and notes. Built with React, TypeScript, Tailwind CSS, and Motion (Framer Motion).
|
||||
|
||||
## Design Specifications
|
||||
|
||||
### Color Palette
|
||||
- Primary Blue: #60A5FA (blue-400)
|
||||
- Secondary Blue: #3B82F6 (blue-500)
|
||||
- Light Blue: #DBEAFE (blue-50)
|
||||
- Orange (Strava): #EA580C (orange-600)
|
||||
- Gray Background: Linear gradient from gray-100 to gray-200
|
||||
- White: #FFFFFF
|
||||
- Text Primary: #1F2937 (gray-800)
|
||||
- Text Secondary: #6B7280 (gray-500)
|
||||
|
||||
### Layout
|
||||
- Mobile-first design (max-width: 28rem / 448px)
|
||||
- Rounded container with 3rem border radius
|
||||
- White background with shadow-2xl
|
||||
- Padding: 1.5rem (6 units)
|
||||
- Component spacing: 1rem gap (4 units)
|
||||
|
||||
### Typography
|
||||
- Headers: font-bold
|
||||
- Body: font-medium
|
||||
- Small text: text-sm, text-xs
|
||||
|
||||
## Dependencies Required
|
||||
|
||||
```json
|
||||
{
|
||||
"recharts": "2.15.2",
|
||||
"motion": "12.23.24",
|
||||
"lucide-react": "0.487.0"
|
||||
}
|
||||
```
|
||||
|
||||
Import Motion using: `import { motion } from 'motion/react'`
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
/src/app/
|
||||
├── App.tsx
|
||||
└── components/
|
||||
├── Header.tsx
|
||||
├── WaterTracker.tsx
|
||||
├── StepsTracker.tsx
|
||||
├── StravaIntegration.tsx
|
||||
├── SleepTracker.tsx
|
||||
├── Goals.tsx
|
||||
└── Notes.tsx
|
||||
```
|
||||
|
||||
## Complete Component Code
|
||||
|
||||
### 1. /src/app/App.tsx
|
||||
|
||||
```tsx
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Header } from './components/Header';
|
||||
import { WaterTracker } from './components/WaterTracker';
|
||||
import { StepsTracker } from './components/StepsTracker';
|
||||
import { StravaIntegration } from './components/StravaIntegration';
|
||||
import { SleepTracker } from './components/SleepTracker';
|
||||
import { Goals } from './components/Goals';
|
||||
import { Notes } from './components/Notes';
|
||||
|
||||
function App() {
|
||||
const [waterAmount, setWaterAmount] = useState(1.2);
|
||||
const [currentDate, setCurrentDate] = useState('');
|
||||
const [currentTime, setCurrentTime] = useState('');
|
||||
const [note, setNote] = useState('Feeling hydrated today. Remember to add those gym sessions!');
|
||||
const [goals, setGoals] = useState([
|
||||
{ id: '1', text: 'Drink 3L of water', completed: true },
|
||||
{ id: '2', text: 'Walk 10K steps', completed: false },
|
||||
{ id: '3', text: 'Sleep 8 hours', completed: false },
|
||||
]);
|
||||
|
||||
// Sleep data for the chart
|
||||
const sleepData = [
|
||||
{ value: 30 }, { value: 35 }, { value: 25 }, { value: 40 },
|
||||
{ value: 55 }, { value: 45 }, { value: 60 }, { value: 75 },
|
||||
{ value: 70 }, { value: 80 }, { value: 65 }, { value: 55 },
|
||||
{ value: 50 }, { value: 45 }, { value: 40 },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const updateDateTime = () => {
|
||||
const now = new Date();
|
||||
const dateStr = now.toLocaleDateString('en-US', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
const timeStr = now.toLocaleTimeString('en-US', {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
}) + ' AEDT';
|
||||
setCurrentDate(dateStr);
|
||||
setCurrentTime(timeStr);
|
||||
};
|
||||
|
||||
updateDateTime();
|
||||
const interval = setInterval(updateDateTime, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
const handleAddWater = () => {
|
||||
setWaterAmount((prev) => Math.min(prev + 0.25, 3.0));
|
||||
};
|
||||
|
||||
const handleToggleGoal = (id: string) => {
|
||||
setGoals((prev) =>
|
||||
prev.map((goal) =>
|
||||
goal.id === id ? { ...goal, completed: !goal.completed } : goal
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-gray-100 to-gray-200 flex items-center justify-center p-4">
|
||||
<div className="w-full max-w-md bg-white rounded-[3rem] shadow-2xl p-6 relative overflow-hidden">
|
||||
{/* Subtle background decoration */}
|
||||
<div className="absolute top-0 right-0 w-64 h-64 bg-blue-200/20 rounded-full blur-3xl -z-10" />
|
||||
<div className="absolute bottom-0 left-0 w-64 h-64 bg-purple-200/20 rounded-full blur-3xl -z-10" />
|
||||
|
||||
<Header
|
||||
level={14}
|
||||
streakDays={14}
|
||||
currentDate={currentDate}
|
||||
currentTime={currentTime}
|
||||
/>
|
||||
|
||||
<WaterTracker
|
||||
currentAmount={waterAmount}
|
||||
targetAmount={3.0}
|
||||
onAddWater={handleAddWater}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 mb-4">
|
||||
<StepsTracker steps={8432} goal={10000} />
|
||||
<StravaIntegration distance={5.2} lastSync="2h ago" />
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<SleepTracker
|
||||
hours={7}
|
||||
minutes={20}
|
||||
sleepType="Light Sleep"
|
||||
data={sleepData}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Goals goals={goals} onToggleGoal={handleToggleGoal} />
|
||||
<Notes note={note} onNoteChange={setNote} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
### 2. /src/app/components/Header.tsx
|
||||
|
||||
```tsx
|
||||
import { Droplet } from 'lucide-react';
|
||||
|
||||
interface HeaderProps {
|
||||
level: number;
|
||||
streakDays: number;
|
||||
currentDate: string;
|
||||
currentTime: string;
|
||||
}
|
||||
|
||||
export function Header({ level, streakDays, currentDate, currentTime }: HeaderProps) {
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-2 mb-6">
|
||||
<div className="flex items-center justify-between w-full px-4">
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="w-14 h-14 rounded-full bg-gradient-to-br from-blue-400 to-blue-600 flex items-center justify-center mb-1 shadow-lg">
|
||||
<div className="w-12 h-12 rounded-full bg-white/90 flex items-center justify-center text-sm font-semibold text-gray-700">
|
||||
JD
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-xs text-blue-600 font-medium">Level {level}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center flex-1">
|
||||
<h1 className="text-2xl font-bold text-gray-800 mb-1">Hydro Flux</h1>
|
||||
<div className="text-sm text-gray-600">{currentDate}</div>
|
||||
<div className="text-sm text-gray-500">{currentTime}</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<Droplet className="w-5 h-5 fill-blue-500 text-blue-500" />
|
||||
<Droplet className="w-5 h-5 fill-blue-400 text-blue-400" />
|
||||
<span className="text-sm font-semibold text-gray-700 ml-1">{streakDays} Days</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. /src/app/components/WaterTracker.tsx
|
||||
|
||||
```tsx
|
||||
import { Plus } from 'lucide-react';
|
||||
import { motion } from 'motion/react';
|
||||
|
||||
interface WaterTrackerProps {
|
||||
currentAmount: number;
|
||||
targetAmount: number;
|
||||
onAddWater: () => void;
|
||||
}
|
||||
|
||||
export function WaterTracker({ currentAmount, targetAmount, onAddWater }: WaterTrackerProps) {
|
||||
const percentage = (currentAmount / targetAmount) * 100;
|
||||
|
||||
return (
|
||||
<div className="relative h-64 bg-gradient-to-b from-blue-50 to-blue-100 rounded-3xl overflow-hidden shadow-lg mb-6">
|
||||
{/* Water wave */}
|
||||
<motion.div
|
||||
className="absolute bottom-0 left-0 right-0"
|
||||
style={{
|
||||
height: `${percentage}%`,
|
||||
background: 'linear-gradient(180deg, rgba(96, 165, 250, 0.8) 0%, rgba(59, 130, 246, 0.9) 100%)',
|
||||
}}
|
||||
animate={{
|
||||
y: [0, -10, 0],
|
||||
}}
|
||||
transition={{
|
||||
duration: 3,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
>
|
||||
{/* Wave effect */}
|
||||
<svg
|
||||
className="absolute top-0 left-0 w-full"
|
||||
viewBox="0 0 1200 120"
|
||||
preserveAspectRatio="none"
|
||||
style={{ height: '60px', transform: 'translateY(-50%)' }}
|
||||
>
|
||||
<motion.path
|
||||
d="M0,50 Q300,10 600,50 T1200,50 L1200,120 L0,120 Z"
|
||||
fill="rgba(96, 165, 250, 0.5)"
|
||||
animate={{
|
||||
d: [
|
||||
"M0,50 Q300,10 600,50 T1200,50 L1200,120 L0,120 Z",
|
||||
"M0,50 Q300,90 600,50 T1200,50 L1200,120 L0,120 Z",
|
||||
"M0,50 Q300,10 600,50 T1200,50 L1200,120 L0,120 Z",
|
||||
],
|
||||
}}
|
||||
transition={{
|
||||
duration: 4,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
/>
|
||||
</svg>
|
||||
</motion.div>
|
||||
|
||||
{/* Add button */}
|
||||
<button
|
||||
onClick={onAddWater}
|
||||
className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-16 h-16 bg-blue-400/50 hover:bg-blue-400/70 backdrop-blur-sm rounded-full flex items-center justify-center shadow-xl transition-all hover:scale-110 z-10"
|
||||
>
|
||||
<Plus className="w-8 h-8 text-white" />
|
||||
</button>
|
||||
|
||||
{/* Amount display */}
|
||||
<div className="absolute bottom-6 left-0 right-0 text-center z-10">
|
||||
<span className="text-2xl font-bold text-gray-700">
|
||||
{currentAmount.toFixed(1)}L / {targetAmount.toFixed(1)}L
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. /src/app/components/StepsTracker.tsx
|
||||
|
||||
```tsx
|
||||
interface StepsTrackerProps {
|
||||
steps: number;
|
||||
goal: number;
|
||||
}
|
||||
|
||||
export function StepsTracker({ steps, goal }: StepsTrackerProps) {
|
||||
const percentage = Math.min((steps / goal) * 100, 100);
|
||||
const circumference = 2 * Math.PI * 45;
|
||||
const strokeDashoffset = circumference - (percentage / 100) * circumference;
|
||||
|
||||
return (
|
||||
<div className="bg-gradient-to-br from-blue-50 to-white rounded-2xl p-6 shadow-md">
|
||||
<h3 className="text-sm font-semibold text-gray-700 mb-4">Steps Tracker</h3>
|
||||
<div className="flex items-center justify-center">
|
||||
<div className="relative w-32 h-32">
|
||||
<svg className="transform -rotate-90 w-32 h-32">
|
||||
<circle
|
||||
cx="64"
|
||||
cy="64"
|
||||
r="45"
|
||||
stroke="#E5E7EB"
|
||||
strokeWidth="8"
|
||||
fill="none"
|
||||
/>
|
||||
<circle
|
||||
cx="64"
|
||||
cy="64"
|
||||
r="45"
|
||||
stroke="#60A5FA"
|
||||
strokeWidth="8"
|
||||
fill="none"
|
||||
strokeDasharray={circumference}
|
||||
strokeDashoffset={strokeDashoffset}
|
||||
strokeLinecap="round"
|
||||
className="transition-all duration-500"
|
||||
/>
|
||||
</svg>
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center">
|
||||
<span className="text-2xl font-bold text-gray-800">{steps.toLocaleString()}</span>
|
||||
<span className="text-xs text-gray-500">steps</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 5. /src/app/components/StravaIntegration.tsx
|
||||
|
||||
```tsx
|
||||
interface StravaIntegrationProps {
|
||||
distance: number;
|
||||
lastSync: string;
|
||||
}
|
||||
|
||||
export function StravaIntegration({ distance, lastSync }: StravaIntegrationProps) {
|
||||
return (
|
||||
<div className="bg-gradient-to-br from-orange-50 to-white rounded-2xl p-6 shadow-md">
|
||||
<h3 className="text-sm font-semibold text-gray-700 mb-4">Strava Integration</h3>
|
||||
<div className="flex flex-col items-center justify-center py-4">
|
||||
<div className="w-16 h-16 bg-gradient-to-br from-orange-500 to-orange-600 rounded-2xl flex items-center justify-center mb-3 shadow-lg transform rotate-12">
|
||||
<span className="text-3xl font-bold text-white transform -rotate-12">S</span>
|
||||
</div>
|
||||
<span className="text-lg font-bold text-gray-800">{distance}km Run</span>
|
||||
<span className="text-xs text-gray-500 mt-1">Last Sync: {lastSync}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 6. /src/app/components/SleepTracker.tsx
|
||||
|
||||
```tsx
|
||||
import { LineChart, Line, ResponsiveContainer, YAxis } from 'recharts';
|
||||
|
||||
interface SleepTrackerProps {
|
||||
hours: number;
|
||||
minutes: number;
|
||||
sleepType: string;
|
||||
data: { value: number }[];
|
||||
}
|
||||
|
||||
export function SleepTracker({ hours, minutes, sleepType, data }: SleepTrackerProps) {
|
||||
return (
|
||||
<div className="bg-gradient-to-br from-blue-50 to-white rounded-2xl p-6 shadow-md">
|
||||
<h3 className="text-sm font-semibold text-gray-700 mb-4">Sleep Tracker</h3>
|
||||
<div className="h-24 mb-3">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={data}>
|
||||
<YAxis hide domain={[0, 100]} />
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="value"
|
||||
stroke="#60A5FA"
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xl font-bold text-gray-800">
|
||||
{hours}h {minutes}m
|
||||
</span>
|
||||
<span className="text-sm text-blue-600 font-medium">{sleepType}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 7. /src/app/components/Goals.tsx
|
||||
|
||||
```tsx
|
||||
import { Check } from 'lucide-react';
|
||||
|
||||
interface Goal {
|
||||
id: string;
|
||||
text: string;
|
||||
completed: boolean;
|
||||
}
|
||||
|
||||
interface GoalsProps {
|
||||
goals: Goal[];
|
||||
onToggleGoal: (id: string) => void;
|
||||
}
|
||||
|
||||
export function Goals({ goals, onToggleGoal }: GoalsProps) {
|
||||
return (
|
||||
<div className="bg-gradient-to-br from-blue-50 to-white rounded-2xl p-6 shadow-md">
|
||||
<h3 className="text-sm font-semibold text-gray-700 mb-4">Goals</h3>
|
||||
<div className="space-y-3">
|
||||
{goals.map((goal) => (
|
||||
<button
|
||||
key={goal.id}
|
||||
onClick={() => onToggleGoal(goal.id)}
|
||||
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all ${
|
||||
goal.completed
|
||||
? 'bg-blue-500 text-white shadow-lg'
|
||||
: 'bg-blue-100 text-gray-700 hover:bg-blue-200'
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`w-5 h-5 rounded-full flex items-center justify-center ${
|
||||
goal.completed ? 'bg-white/30' : 'bg-white'
|
||||
}`}
|
||||
>
|
||||
{goal.completed && <Check className="w-4 h-4 text-blue-500" />}
|
||||
</div>
|
||||
<span className="text-sm font-medium">{goal.text}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 8. /src/app/components/Notes.tsx
|
||||
|
||||
```tsx
|
||||
interface NotesProps {
|
||||
note: string;
|
||||
onNoteChange: (note: string) => void;
|
||||
}
|
||||
|
||||
export function Notes({ note, onNoteChange }: NotesProps) {
|
||||
return (
|
||||
<div className="bg-gradient-to-br from-blue-50 to-white rounded-2xl p-6 shadow-md">
|
||||
<h3 className="text-sm font-semibold text-gray-700 mb-4">Notes</h3>
|
||||
<textarea
|
||||
value={note}
|
||||
onChange={(e) => onNoteChange(e.target.value)}
|
||||
placeholder="Add your notes here..."
|
||||
className="w-full h-24 bg-white/50 rounded-lg px-4 py-3 text-sm text-gray-700 placeholder-gray-400 border-none outline-none resize-none"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Key Features & Interactions
|
||||
|
||||
1. **Water Tracker**
|
||||
- Animated wave visualization using SVG and Motion
|
||||
- Click + button to add 0.25L (max 3.0L)
|
||||
- Displays current/target amount
|
||||
|
||||
2. **Steps Tracker**
|
||||
- Circular progress indicator using SVG circles
|
||||
- Shows 8,432 / 10,000 steps
|
||||
- Animated stroke-dashoffset for progress
|
||||
|
||||
3. **Strava Integration**
|
||||
- Orange gradient background
|
||||
- Rotated "S" logo
|
||||
- Displays distance and last sync time
|
||||
|
||||
4. **Sleep Tracker**
|
||||
- Recharts line chart visualization
|
||||
- Shows hours and minutes
|
||||
- Sleep type label (Light Sleep)
|
||||
|
||||
5. **Goals**
|
||||
- Three clickable goal items
|
||||
- Toggle completed state
|
||||
- Visual feedback with color change
|
||||
|
||||
6. **Notes**
|
||||
- Textarea for user input
|
||||
- Placeholder text
|
||||
- Auto-updating state
|
||||
|
||||
7. **Header**
|
||||
- User avatar with initials "JD"
|
||||
- Level indicator
|
||||
- Streak counter with droplet icons
|
||||
- Live date/time that updates every second
|
||||
|
||||
## Important Implementation Notes
|
||||
|
||||
- Use Tailwind CSS v4 (no config file needed)
|
||||
- Motion package is imported as `import { motion } from 'motion/react'`
|
||||
- All components are TypeScript with proper interfaces
|
||||
- State management uses React useState hooks
|
||||
- DateTime updates every 1000ms using setInterval
|
||||
- Responsive design with max-width container
|
||||
- All interactive elements have hover states
|
||||
- Glassmorphism effects using backdrop-blur and opacity
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Water tracker + button increases amount
|
||||
- [ ] Water amount doesn't exceed 3.0L
|
||||
- [ ] Goals toggle between completed/incomplete
|
||||
- [ ] Notes textarea updates on input
|
||||
- [ ] Date and time update in real-time
|
||||
- [ ] All animations are smooth
|
||||
- [ ] Mobile responsive (320px - 448px width)
|
||||
- [ ] All icons from lucide-react render correctly
|
||||
- [ ] Sleep chart displays properly with recharts
|
||||
Reference in New Issue
Block a user