From b44b93e811e8b5b3e3034099d66eb78bb4198a08 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 14:36:31 +0000 Subject: [PATCH 1/4] Initial plan From 7b254b142f19b4ea29cf9d1326711388a3c6cddd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 14:39:30 +0000 Subject: [PATCH 2/4] Add complete LifeStock dashboard implementation Co-authored-by: kyzen-dev7 <256251449+kyzen-dev7@users.noreply.github.com> --- .gitignore | 36 ++++++ README.md | 119 ++++++++++++++++- app/globals.css | 20 +++ app/layout.tsx | 21 +++ app/page.tsx | 297 +++++++++++++++++++++++++++++++++++++++++++ app/weekly/page.tsx | 222 ++++++++++++++++++++++++++++++++ components/Chart.tsx | 94 ++++++++++++++ lib/lifestock.ts | 145 +++++++++++++++++++++ next.config.js | 4 + package.json | 28 ++++ postcss.config.js | 6 + tailwind.config.ts | 14 ++ tsconfig.json | 26 ++++ 13 files changed, 1031 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 app/globals.css create mode 100644 app/layout.tsx create mode 100644 app/page.tsx create mode 100644 app/weekly/page.tsx create mode 100644 components/Chart.tsx create mode 100644 lib/lifestock.ts create mode 100644 next.config.js create mode 100644 package.json create mode 100644 postcss.config.js create mode 100644 tailwind.config.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd3dbb5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/README.md b/README.md index ba38467..f2446a0 100644 --- a/README.md +++ b/README.md @@ -1 +1,118 @@ -# Lifestock \ No newline at end of file +# LifeStock: KIRAN LTD. (KRNX) + +A Next.js 14 dashboard that tracks your life pillars (Knowledge, Skills, Health, Discipline, Mood) as if they were moving a stock price. Features real-time candlestick charts, daily entries, and weekly performance reports. + +## Features + +- 📊 Real-time candlestick chart visualization +- 📝 Daily pillar tracking (0-10 scale for each pillar) +- 📈 Automatic price calculations based on pillar averages +- 📅 Weekly earnings reports with streak tracking +- 💾 Local data persistence using localStorage +- 🌙 Dark theme UI with responsive design +- 📱 Mobile and desktop friendly + +## Tech Stack + +- **Next.js 14** with App Router +- **TypeScript** for type safety +- **Tailwind CSS** for styling +- **lightweight-charts** for candlestick visualization +- **localStorage** for data persistence + +## Installation + +1. Clone the repository: +```bash +git clone https://github.com/kyzen-dev7/Lifestock.git +cd Lifestock +``` + +2. Install dependencies: +```bash +npm install +``` + +3. Run the development server: +```bash +npm run dev +``` + +4. Open [http://localhost:3000](http://localhost:3000) in your browser. + +## Building for Production + +Build the application: +```bash +npm run build +``` + +Start the production server: +```bash +npm start +``` + +## How It Works + +### The Formula + +1. **Average Calculation**: `Avg = (Knowledge + Skills + Health + Discipline + Mood) / 5` +2. **Daily Change**: `DailyChange% = (Avg - 5) * 4` + - When Avg = 10 → +20% + - When Avg = 5 → 0% + - When Avg = 0 → -20% +3. **Price Calculation**: `NewClose = OldClose * (1 + DailyChange%/100)` +4. **Candlestick Wicks**: Discipline affects volatility + - Higher discipline = smaller wicks + - Lower discipline = larger wicks + +### Market Trends + +- **Bull Market**: Average ≥ 6.5 +- **Stable Market**: 4.8 ≤ Average ≤ 6.4 +- **Bear Market**: Average < 4.8 + +## Pages + +### Dashboard (/) +- Real-time candlestick chart +- Daily entry form with 5 pillar sliders +- Preview of projected price changes +- Recent entries table +- Current market trend indicator + +### Weekly Report (/weekly) +- Last 7 days performance summary +- Weekly % change +- Best and weakest pillar analysis +- Green streak counter (consecutive days with Avg ≥ 5) +- Biggest up/down days + +## Data Storage + +All data is stored locally in your browser's localStorage. No backend or external API required. + +## Project Structure + +``` +Lifestock/ +├── app/ +│ ├── layout.tsx # Root layout +│ ├── page.tsx # Dashboard page +│ ├── weekly/ +│ │ └── page.tsx # Weekly report page +│ └── globals.css # Global styles +├── components/ +│ └── Chart.tsx # Candlestick chart component +├── lib/ +│ └── lifestock.ts # Utility functions and formulas +├── package.json +├── tsconfig.json +├── tailwind.config.ts +├── postcss.config.js +└── next.config.js +``` + +## License + +MIT \ No newline at end of file diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..1181442 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,20 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --background: #000000; + --foreground: #ffffff; +} + +body { + color: var(--foreground); + background: var(--background); + font-family: Arial, Helvetica, sans-serif; +} + +@layer utilities { + .text-balance { + text-wrap: balance; + } +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..a18528b --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,21 @@ +import type { Metadata } from "next"; +import "./globals.css"; + +export const metadata: Metadata = { + title: "LifeStock: KIRAN LTD. (KRNX)", + description: "Track your life pillars as a stock portfolio", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + ); +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..84e039b --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,297 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import Link from 'next/link'; +import Chart from '@/components/Chart'; +import { + DailyEntry, + Pillars, + loadEntries, + saveEntries, + todayISO, + createEntry, + getMarketTrend, + getTrendColor, + round2, + calculateAvg, + calculateDailyChangePct, + calculateNewClose, +} from '@/lib/lifestock'; + +export default function Dashboard() { + const [entries, setEntries] = useState([]); + const [selectedDate, setSelectedDate] = useState(todayISO()); + const [pillars, setPillars] = useState({ + knowledge: 5, + skills: 5, + health: 5, + discipline: 5, + mood: 5, + }); + const [mounted, setMounted] = useState(false); + + // Load entries on mount + useEffect(() => { + setMounted(true); + const loaded = loadEntries(); + setEntries(loaded); + + // If there's an entry for the selected date, load its values + const existingEntry = loaded.find(e => e.dateISO === selectedDate); + if (existingEntry) { + setPillars(existingEntry.pillars); + } + }, []); + + // Update pillars when date changes + useEffect(() => { + if (!mounted) return; + const existingEntry = entries.find(e => e.dateISO === selectedDate); + if (existingEntry) { + setPillars(existingEntry.pillars); + } else { + // Reset to default + setPillars({ + knowledge: 5, + skills: 5, + health: 5, + discipline: 5, + mood: 5, + }); + } + }, [selectedDate, entries, mounted]); + + const handleSaveDay = () => { + // Get previous close + const sortedEntries = [...entries].sort((a, b) => a.dateISO.localeCompare(b.dateISO)); + + // Find the entry just before the selected date + const beforeEntries = sortedEntries.filter(e => e.dateISO < selectedDate); + const previousClose = beforeEntries.length > 0 + ? beforeEntries[beforeEntries.length - 1].priceClose + : 100; + + // Create new entry + const newEntry = createEntry(selectedDate, pillars, previousClose); + + // Remove existing entry with same date + const filteredEntries = entries.filter(e => e.dateISO !== selectedDate); + + // Add new entry + const updatedEntries = [...filteredEntries, newEntry]; + + // Recalculate all entries after this date + const sorted = updatedEntries.sort((a, b) => a.dateISO.localeCompare(b.dateISO)); + const recalculated: DailyEntry[] = []; + + for (let i = 0; i < sorted.length; i++) { + if (i === 0) { + // First entry + const entry = createEntry(sorted[i].dateISO, sorted[i].pillars, 100); + recalculated.push(entry); + } else { + // Use previous close + const entry = createEntry(sorted[i].dateISO, sorted[i].pillars, recalculated[i - 1].priceClose); + recalculated.push(entry); + } + } + + setEntries(recalculated); + saveEntries(recalculated); + }; + + const handleReset = () => { + if (confirm('Are you sure you want to clear all data? This cannot be undone.')) { + setEntries([]); + saveEntries([]); + } + }; + + // Get latest entry for display + const latestEntry = entries.length > 0 + ? [...entries].sort((a, b) => b.dateISO.localeCompare(a.dateISO))[0] + : null; + + // Calculate preview values + const previewAvg = round2(calculateAvg(pillars)); + const previewDailyChangePct = round2(calculateDailyChangePct(previewAvg)); + + const sortedEntries = [...entries].sort((a, b) => a.dateISO.localeCompare(b.dateISO)); + const beforeEntries = sortedEntries.filter(e => e.dateISO < selectedDate); + const previewOpen = beforeEntries.length > 0 + ? beforeEntries[beforeEntries.length - 1].priceClose + : 100; + const previewClose = round2(calculateNewClose(previewOpen, previewDailyChangePct)); + + // Recent entries (last 10-12) + const recentEntries = [...entries] + .sort((a, b) => b.dateISO.localeCompare(a.dateISO)) + .slice(0, 12); + + if (!mounted) { + return ( +
+
Loading...
+
+ ); + } + + return ( +
+
+ {/* Header */} +
+

+ LifeStock: KIRAN LTD. (KRNX) +

+
+
+ ${latestEntry ? latestEntry.priceClose.toFixed(2) : '100.00'} +
+ {latestEntry && ( +
+ {getMarketTrend(latestEntry.avg)} +
+ )} +
+
+ + {/* Chart */} +
+ +
+ + {/* Main content grid */} +
+ {/* Daily Entry Card */} +
+

Daily Entry

+ + {/* Date picker */} +
+ + setSelectedDate(e.target.value)} + className="w-full bg-gray-800 border border-gray-700 rounded px-3 py-2 text-white" + /> +
+ + {/* Pillar sliders */} +
+ {(['knowledge', 'skills', 'health', 'discipline', 'mood'] as const).map((pillar) => ( +
+
+ + {pillars[pillar]} +
+ setPillars({ ...pillars, [pillar]: Number(e.target.value) })} + className="w-full accent-blue-500" + /> +
+ ))} +
+ + {/* Save button */} + +
+ + {/* Preview Card */} +
+

Preview

+
+
+ Open + ${previewOpen.toFixed(2)} +
+
+ Close (Projected) + ${previewClose.toFixed(2)} +
+
+ Average + {previewAvg.toFixed(2)} +
+
+ Daily Change + = 0 ? 'text-green-500' : 'text-red-500'}`}> + {previewDailyChangePct >= 0 ? '+' : ''}{previewDailyChangePct.toFixed(2)}% + +
+
+
+ {getMarketTrend(previewAvg)} +
+
+
+
+
+ + {/* Recent entries */} +
+

Recent Days

+ {recentEntries.length === 0 ? ( +
No entries yet
+ ) : ( +
+ + + + + + + + + + + + {recentEntries.map((entry) => ( + + + + + + + + ))} + +
DateCloseChangeAvgTrend
{entry.dateISO}${entry.priceClose.toFixed(2)}= 0 ? 'text-green-500' : 'text-red-500'}`}> + {entry.dailyChangePct >= 0 ? '+' : ''}{entry.dailyChangePct.toFixed(2)}% + {entry.avg.toFixed(2)}
+
+ )} +
+ + {/* Actions */} +
+ + View Weekly Report + + +
+
+
+ ); +} diff --git a/app/weekly/page.tsx b/app/weekly/page.tsx new file mode 100644 index 0000000..0dc16a0 --- /dev/null +++ b/app/weekly/page.tsx @@ -0,0 +1,222 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import Link from 'next/link'; +import { DailyEntry, loadEntries, round2 } from '@/lib/lifestock'; + +export default function WeeklyReport() { + const [entries, setEntries] = useState([]); + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + const loaded = loadEntries(); + setEntries(loaded); + }, []); + + if (!mounted) { + return ( +
+
Loading...
+
+ ); + } + + // Get last 7 days (or fewer if not available) + const sortedEntries = [...entries].sort((a, b) => b.dateISO.localeCompare(a.dateISO)); + const last7Days = sortedEntries.slice(0, 7); + + if (last7Days.length === 0) { + return ( +
+
+

Weekly Earnings Report

+
+

No data available for weekly report

+ + Back to Dashboard + +
+
+
+ ); + } + + // Sort by date ascending for calculations + const weekData = [...last7Days].sort((a, b) => a.dateISO.localeCompare(b.dateISO)); + + // Weekly % change from first open to last close + const firstOpen = weekData[0].priceOpen; + const lastClose = weekData[weekData.length - 1].priceClose; + const weeklyChangePct = round2(((lastClose - firstOpen) / firstOpen) * 100); + + // Best and weakest pillar + const pillarAverages: Record = { + knowledge: 0, + skills: 0, + health: 0, + discipline: 0, + mood: 0, + }; + + weekData.forEach((entry) => { + pillarAverages.knowledge += entry.pillars.knowledge; + pillarAverages.skills += entry.pillars.skills; + pillarAverages.health += entry.pillars.health; + pillarAverages.discipline += entry.pillars.discipline; + pillarAverages.mood += entry.pillars.mood; + }); + + Object.keys(pillarAverages).forEach((key) => { + pillarAverages[key] = round2(pillarAverages[key] / weekData.length); + }); + + const sortedPillars = Object.entries(pillarAverages).sort((a, b) => b[1] - a[1]); + const bestPillar = sortedPillars[0]; + const weakestPillar = sortedPillars[sortedPillars.length - 1]; + + // Streak count: consecutive days with Avg >= 5 + let currentStreak = 0; + for (let i = weekData.length - 1; i >= 0; i--) { + if (weekData[i].avg >= 5) { + currentStreak++; + } else { + break; + } + } + + // Biggest up day and down day + const sortedByChange = [...weekData].sort((a, b) => b.dailyChangePct - a.dailyChangePct); + const biggestUpDay = sortedByChange[0]; + const biggestDownDay = sortedByChange[sortedByChange.length - 1]; + + return ( +
+
+ {/* Header */} +
+

Weekly Earnings Report

+

+ Last {weekData.length} day{weekData.length !== 1 ? 's' : ''} • {weekData[0].dateISO} to {weekData[weekData.length - 1].dateISO} +

+
+ + {/* Summary Cards */} +
+ {/* Weekly Change */} +
+

Weekly Change

+
= 0 ? 'text-green-500' : 'text-red-500'}`}> + {weeklyChangePct >= 0 ? '+' : ''}{weeklyChangePct.toFixed(2)}% +
+
+ ${firstOpen.toFixed(2)} → ${lastClose.toFixed(2)} +
+
+ + {/* Streak */} +
+

Green Streak

+
+ {currentStreak} day{currentStreak !== 1 ? 's' : ''} +
+
+ Consecutive days with Avg ≥ 5 +
+
+
+ + {/* Pillar Performance */} +
+

Pillar Performance

+
+
+
+ Best Pillar + {bestPillar[0]} +
+
+
+
+
+ {bestPillar[1].toFixed(1)} +
+
+ +
+
+ Weakest Pillar + {weakestPillar[0]} +
+
+
+
+
+ {weakestPillar[1].toFixed(1)} +
+
+
+ + {/* All pillars */} +
+

All Pillars (Weekly Avg)

+
+ {sortedPillars.map(([name, avg]) => ( +
+ {name} +
+
+
+ {avg.toFixed(1)} +
+ ))} +
+
+
+ + {/* Best and Worst Days */} +
+ {/* Biggest Up Day */} +
+

Biggest Up Day

+
+ +{biggestUpDay.dailyChangePct.toFixed(2)}% +
+
{biggestUpDay.dateISO}
+
Avg: {biggestUpDay.avg.toFixed(2)}
+
+ + {/* Biggest Down Day */} +
+

Biggest Down Day

+
+ {biggestDownDay.dailyChangePct.toFixed(2)}% +
+
{biggestDownDay.dateISO}
+
Avg: {biggestDownDay.avg.toFixed(2)}
+
+
+ + {/* Back button */} + + ← Back to Dashboard + +
+
+ ); +} diff --git a/components/Chart.tsx b/components/Chart.tsx new file mode 100644 index 0000000..9362b80 --- /dev/null +++ b/components/Chart.tsx @@ -0,0 +1,94 @@ +'use client'; + +import { useEffect, useRef } from 'react'; +import { createChart, IChartApi, ISeriesApi, CandlestickData } from 'lightweight-charts'; +import { DailyEntry } from '@/lib/lifestock'; + +interface ChartProps { + entries: DailyEntry[]; +} + +export default function Chart({ entries }: ChartProps) { + const chartContainerRef = useRef(null); + const chartRef = useRef(null); + const seriesRef = useRef | null>(null); + + useEffect(() => { + if (!chartContainerRef.current) return; + + // Create chart + const chart = createChart(chartContainerRef.current, { + width: chartContainerRef.current.clientWidth, + height: 400, + layout: { + background: { color: '#000000' }, + textColor: '#d1d5db', + }, + grid: { + vertLines: { color: '#1f2937' }, + horzLines: { color: '#1f2937' }, + }, + timeScale: { + borderColor: '#374151', + }, + rightPriceScale: { + borderColor: '#374151', + }, + }); + + chartRef.current = chart; + + // Create candlestick series + const candlestickSeries = chart.addCandlestickSeries({ + upColor: '#10b981', + downColor: '#ef4444', + borderUpColor: '#10b981', + borderDownColor: '#ef4444', + wickUpColor: '#10b981', + wickDownColor: '#ef4444', + }); + + seriesRef.current = candlestickSeries; + + // Handle resize + const handleResize = () => { + if (chartContainerRef.current && chartRef.current) { + chartRef.current.applyOptions({ + width: chartContainerRef.current.clientWidth, + }); + } + }; + + window.addEventListener('resize', handleResize); + + return () => { + window.removeEventListener('resize', handleResize); + chart.remove(); + }; + }, []); + + useEffect(() => { + if (!seriesRef.current) return; + + // Convert entries to candlestick data + const data: CandlestickData[] = entries.map((entry) => { + // Convert date to timestamp (seconds since epoch) + const timestamp = new Date(entry.dateISO).getTime() / 1000; + return { + time: timestamp as any, + open: entry.priceOpen, + high: entry.priceHigh, + low: entry.priceLow, + close: entry.priceClose, + }; + }); + + seriesRef.current.setData(data); + }, [entries]); + + return ( +
+
+
+ ); +} diff --git a/lib/lifestock.ts b/lib/lifestock.ts new file mode 100644 index 0000000..c4bbc44 --- /dev/null +++ b/lib/lifestock.ts @@ -0,0 +1,145 @@ +// Utility functions for LifeStock dashboard + +export interface Pillars { + knowledge: number; + skills: number; + health: number; + discipline: number; + mood: number; +} + +export interface DailyEntry { + id: string; + dateISO: string; + pillars: Pillars; + avg: number; + dailyChangePct: number; + priceOpen: number; + priceHigh: number; + priceLow: number; + priceClose: number; +} + +// Clamp a value between min and max +export function clamp(value: number, min: number, max: number): number { + return Math.max(min, Math.min(max, value)); +} + +// Round to 2 decimal places +export function round2(value: number): number { + return Math.round(value * 100) / 100; +} + +// Get today's date in ISO format (YYYY-MM-DD) +export function todayISO(): string { + const today = new Date(); + return today.toISOString().split('T')[0]; +} + +// Load entries from localStorage (guards against SSR) +export function loadEntries(): DailyEntry[] { + if (typeof window === 'undefined') return []; + try { + const data = localStorage.getItem('lifestock_entries'); + if (!data) return []; + const entries = JSON.parse(data) as DailyEntry[]; + return entries.sort((a, b) => a.dateISO.localeCompare(b.dateISO)); + } catch { + return []; + } +} + +// Save entries to localStorage +export function saveEntries(entries: DailyEntry[]): void { + if (typeof window === 'undefined') return; + const sorted = [...entries].sort((a, b) => a.dateISO.localeCompare(b.dateISO)); + localStorage.setItem('lifestock_entries', JSON.stringify(sorted)); +} + +// Generate a simple UUID +export function generateId(): string { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + const r = Math.random() * 16 | 0; + const v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); +} + +// Calculate average of pillars +export function calculateAvg(pillars: Pillars): number { + const { knowledge, skills, health, discipline, mood } = pillars; + return (knowledge + skills + health + discipline + mood) / 5; +} + +// Calculate daily change percentage +// Formula: (Avg - 5) * 4 +// Avg=10 => +20%, Avg=0 => -20% +export function calculateDailyChangePct(avg: number): number { + return (avg - 5) * 4; +} + +// Calculate new close price +// Formula: OldClose * (1 + DailyChange%/100) +export function calculateNewClose(oldClose: number, dailyChangePct: number): number { + const newClose = oldClose * (1 + dailyChangePct / 100); + return Math.max(0, newClose); // Cannot go below 0 +} + +// Calculate candlestick high and low based on discipline +export function calculateHighLow( + open: number, + close: number, + dailyChangePct: number, + discipline: number +): { high: number; low: number } { + // Higher discipline = smaller wicks, lower discipline = larger wicks + const volFactor = 1.8 - (discipline / 10) * 1.4; + const wickPct = Math.abs(dailyChangePct) * 0.35 * volFactor + 0.6 * volFactor; + + const high = Math.max(open, close) * (1 + wickPct / 100); + const low = Math.min(open, close) * (1 - wickPct / 100); + + return { + high: Math.max(0, round2(high)), + low: Math.max(0, round2(low)) + }; +} + +// Create a new entry +export function createEntry( + dateISO: string, + pillars: Pillars, + previousClose: number = 100 +): DailyEntry { + const avg = round2(calculateAvg(pillars)); + const dailyChangePct = round2(calculateDailyChangePct(avg)); + const priceOpen = round2(previousClose); + const priceClose = round2(calculateNewClose(priceOpen, dailyChangePct)); + const { high, low } = calculateHighLow(priceOpen, priceClose, dailyChangePct, pillars.discipline); + + return { + id: generateId(), + dateISO, + pillars, + avg, + dailyChangePct, + priceOpen, + priceHigh: high, + priceLow: low, + priceClose + }; +} + +// Get market trend label based on average +export function getMarketTrend(avg: number): string { + if (avg >= 6.5) return 'Bull Market'; + if (avg >= 4.8) return 'Stable Market'; + return 'Bear Market'; +} + +// Get trend color +export function getTrendColor(avg: number): string { + if (avg >= 6.5) return 'text-green-500'; + if (avg >= 4.8) return 'text-blue-400'; + return 'text-red-500'; +} diff --git a/next.config.js b/next.config.js new file mode 100644 index 0000000..767719f --- /dev/null +++ b/next.config.js @@ -0,0 +1,4 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = {} + +module.exports = nextConfig diff --git a/package.json b/package.json new file mode 100644 index 0000000..77f2d90 --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "lifestock", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "next": "14.2.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "lightweight-charts": "^4.1.3" + }, + "devDependencies": { + "@types/node": "^20.12.7", + "@types/react": "^18.2.79", + "@types/react-dom": "^18.2.25", + "autoprefixer": "^10.4.19", + "eslint": "^8.57.0", + "eslint-config-next": "14.2.3", + "postcss": "^8.4.38", + "tailwindcss": "^3.4.3", + "typescript": "^5.4.5" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/tailwind.config.ts b/tailwind.config.ts new file mode 100644 index 0000000..35ba2d0 --- /dev/null +++ b/tailwind.config.ts @@ -0,0 +1,14 @@ +import type { Config } from "tailwindcss"; + +const config: Config = { + content: [ + "./pages/**/*.{js,ts,jsx,tsx,mdx}", + "./components/**/*.{js,ts,jsx,tsx,mdx}", + "./app/**/*.{js,ts,jsx,tsx,mdx}", + ], + theme: { + extend: {}, + }, + plugins: [], +}; +export default config; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e7ff90f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} From 7061931a6771e00376d54003525830e5a1989c52 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 14:46:27 +0000 Subject: [PATCH 3/4] Add personalized name input feature and fix TypeScript types Co-authored-by: kyzen-dev7 <256251449+kyzen-dev7@users.noreply.github.com> --- README.md | 3 +- app/layout.tsx | 2 +- app/page.tsx | 56 +- components/Chart.tsx | 8 +- lib/lifestock.ts | 19 +- package-lock.json | 6050 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 6129 insertions(+), 9 deletions(-) create mode 100644 package-lock.json diff --git a/README.md b/README.md index f2446a0..2b3e915 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ -# LifeStock: KIRAN LTD. (KRNX) +# LifeStock Dashboard A Next.js 14 dashboard that tracks your life pillars (Knowledge, Skills, Health, Discipline, Mood) as if they were moving a stock price. Features real-time candlestick charts, daily entries, and weekly performance reports. ## Features +- 👤 Personalized user name on first launch - 📊 Real-time candlestick chart visualization - 📝 Daily pillar tracking (0-10 scale for each pillar) - 📈 Automatic price calculations based on pillar averages diff --git a/app/layout.tsx b/app/layout.tsx index a18528b..6e5cf90 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,7 +2,7 @@ import type { Metadata } from "next"; import "./globals.css"; export const metadata: Metadata = { - title: "LifeStock: KIRAN LTD. (KRNX)", + title: "LifeStock Dashboard", description: "Track your life pillars as a stock portfolio", }; diff --git a/app/page.tsx b/app/page.tsx index 84e039b..e679f7a 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -16,6 +16,8 @@ import { calculateAvg, calculateDailyChangePct, calculateNewClose, + getUserName, + saveUserName, } from '@/lib/lifestock'; export default function Dashboard() { @@ -29,12 +31,17 @@ export default function Dashboard() { mood: 5, }); const [mounted, setMounted] = useState(false); + const [userName, setUserName] = useState(null); + const [nameInput, setNameInput] = useState(''); - // Load entries on mount + // Load entries and user name on mount useEffect(() => { setMounted(true); const loaded = loadEntries(); setEntries(loaded); + + const savedName = getUserName(); + setUserName(savedName); // If there's an entry for the selected date, load its values const existingEntry = loaded.find(e => e.dateISO === selectedDate); @@ -107,6 +114,14 @@ export default function Dashboard() { } }; + const handleNameSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (nameInput.trim()) { + saveUserName(nameInput.trim()); + setUserName(nameInput.trim()); + } + }; + // Get latest entry for display const latestEntry = entries.length > 0 ? [...entries].sort((a, b) => b.dateISO.localeCompare(a.dateISO))[0] @@ -136,13 +151,50 @@ export default function Dashboard() { ); } + // Show name input if no name is saved + if (!userName) { + return ( +
+
+
+

Welcome to LifeStock

+

+ Track your life pillars as a stock portfolio +

+
+ + setNameInput(e.target.value)} + placeholder="Your Name" + className="w-full bg-gray-800 border border-gray-700 rounded px-4 py-3 text-white mb-4 focus:outline-none focus:border-blue-500" + autoFocus + required + /> + +
+
+
+
+ ); + } + return (
{/* Header */}

- LifeStock: KIRAN LTD. (KRNX) + LifeStock: {userName.toUpperCase()} LTD. ( + {userName.substring(0, 4).toUpperCase()}X)

diff --git a/components/Chart.tsx b/components/Chart.tsx index 9362b80..07cd888 100644 --- a/components/Chart.tsx +++ b/components/Chart.tsx @@ -1,7 +1,7 @@ 'use client'; import { useEffect, useRef } from 'react'; -import { createChart, IChartApi, ISeriesApi, CandlestickData } from 'lightweight-charts'; +import { createChart, IChartApi, ISeriesApi, CandlestickData, Time } from 'lightweight-charts'; import { DailyEntry } from '@/lib/lifestock'; interface ChartProps { @@ -71,11 +71,11 @@ export default function Chart({ entries }: ChartProps) { if (!seriesRef.current) return; // Convert entries to candlestick data - const data: CandlestickData[] = entries.map((entry) => { + const data: CandlestickData