diff --git a/src/app/callback/route.ts b/src/app/callback/route.ts new file mode 100644 index 0000000..4cf8f06 --- /dev/null +++ b/src/app/callback/route.ts @@ -0,0 +1,4 @@ +import { handleAuth } from "@workos-inc/authkit-nextjs"; + +// Handle the WorkOS OAuth callback +export const GET = handleAuth({ returnPathname: "/" }); diff --git a/src/app/globals 2.css b/src/app/globals 2.css new file mode 100644 index 0000000..ebe8555 --- /dev/null +++ b/src/app/globals 2.css @@ -0,0 +1,177 @@ +@import "tailwindcss"; +@import "tw-animate-css"; + +@custom-variant dark (&:is(.dark *)); + +:root { + --background: oklch(0.9789 0.0082 121.6272); + --foreground: oklch(0 0 0); + --card: oklch(1.0000 0 0); + --card-foreground: oklch(0 0 0); + --popover: oklch(1.0000 0 0); + --popover-foreground: oklch(0 0 0); + --primary: oklch(0.5106 0.2301 276.9656); + --primary-foreground: oklch(1.0000 0 0); + --secondary: oklch(0.7038 0.1230 182.5025); + --secondary-foreground: oklch(1.0000 0 0); + --muted: oklch(0.9551 0 0); + --muted-foreground: oklch(0.3211 0 0); + --accent: oklch(0.7686 0.1647 70.0804); + --accent-foreground: oklch(0 0 0); + --destructive: oklch(0.6368 0.2078 25.3313); + --destructive-foreground: oklch(1.0000 0 0); + --border: oklch(0 0 0); + --input: oklch(0.5555 0 0); + --ring: oklch(0.7853 0.1041 274.7134); + --chart-1: oklch(0.5106 0.2301 276.9656); + --chart-2: oklch(0.7038 0.1230 182.5025); + --chart-3: oklch(0.7686 0.1647 70.0804); + --chart-4: oklch(0.6559 0.2118 354.3084); + --chart-5: oklch(0.7227 0.1920 149.5793); + --sidebar: oklch(0.9789 0.0082 121.6272); + --sidebar-foreground: oklch(0 0 0); + --sidebar-primary: oklch(0.5106 0.2301 276.9656); + --sidebar-primary-foreground: oklch(1.0000 0 0); + --sidebar-accent: oklch(0.7686 0.1647 70.0804); + --sidebar-accent-foreground: oklch(0 0 0); + --sidebar-border: oklch(0 0 0); + --sidebar-ring: oklch(0.7853 0.1041 274.7134); + --radius: 1rem; + --shadow-x: 0px; + --shadow-y: 0px; + --shadow-blur: 0px; + --shadow-spread: 0px; + --shadow-opacity: 0.05; + --shadow-color: #1a1a1a; + --shadow-2xs: 0px 0px 0px 0px hsl(0 0% 10.1961% / 0.03); + --shadow-xs: 0px 0px 0px 0px hsl(0 0% 10.1961% / 0.03); + --shadow-sm: 0px 0px 0px 0px hsl(0 0% 10.1961% / 0.05), 0px 1px 2px -1px hsl(0 0% 10.1961% / 0.05); + --shadow: 0px 0px 0px 0px hsl(0 0% 10.1961% / 0.05), 0px 1px 2px -1px hsl(0 0% 10.1961% / 0.05); + --shadow-md: 0px 0px 0px 0px hsl(0 0% 10.1961% / 0.05), 0px 2px 4px -1px hsl(0 0% 10.1961% / 0.05); + --shadow-lg: 0px 0px 0px 0px hsl(0 0% 10.1961% / 0.05), 0px 4px 6px -1px hsl(0 0% 10.1961% / 0.05); + --shadow-xl: 0px 0px 0px 0px hsl(0 0% 10.1961% / 0.05), 0px 8px 10px -1px hsl(0 0% 10.1961% / 0.05); + --shadow-2xl: 0px 0px 0px 0px hsl(0 0% 10.1961% / 0.13); + --tracking-normal: normal; + --spacing: 0.25rem; +} + +.dark { + --background: oklch(0 0 0); + --foreground: oklch(1.0000 0 0); + --card: oklch(0.2455 0.0217 257.2823); + --card-foreground: oklch(1.0000 0 0); + --popover: oklch(0.2455 0.0217 257.2823); + --popover-foreground: oklch(1.0000 0 0); + --primary: oklch(0.6801 0.1583 276.9349); + --primary-foreground: oklch(0 0 0); + --secondary: oklch(0.7845 0.1325 181.9120); + --secondary-foreground: oklch(0 0 0); + --muted: oklch(0.3211 0 0); + --muted-foreground: oklch(0.8452 0 0); + --accent: oklch(0.8790 0.1534 91.6054); + --accent-foreground: oklch(0 0 0); + --destructive: oklch(0.7106 0.1661 22.2162); + --destructive-foreground: oklch(0 0 0); + --border: oklch(0.4459 0 0); + --input: oklch(1.0000 0 0); + --ring: oklch(0.6801 0.1583 276.9349); + --chart-1: oklch(0.6801 0.1583 276.9349); + --chart-2: oklch(0.7845 0.1325 181.9120); + --chart-3: oklch(0.8790 0.1534 91.6054); + --chart-4: oklch(0.7253 0.1752 349.7607); + --chart-5: oklch(0.8003 0.1821 151.7110); + --sidebar: oklch(0 0 0); + --sidebar-foreground: oklch(1.0000 0 0); + --sidebar-primary: oklch(0.6801 0.1583 276.9349); + --sidebar-primary-foreground: oklch(0 0 0); + --sidebar-accent: oklch(0.8790 0.1534 91.6054); + --sidebar-accent-foreground: oklch(0 0 0); + --sidebar-border: oklch(1.0000 0 0); + --sidebar-ring: oklch(0.6801 0.1583 276.9349); + --radius: 1rem; + --shadow-x: 0px; + --shadow-y: 0px; + --shadow-blur: 0px; + --shadow-spread: 0px; + --shadow-opacity: 0.05; + --shadow-color: #1a1a1a; + --shadow-2xs: 0px 0px 0px 0px hsl(0 0% 10.1961% / 0.03); + --shadow-xs: 0px 0px 0px 0px hsl(0 0% 10.1961% / 0.03); + --shadow-sm: 0px 0px 0px 0px hsl(0 0% 10.1961% / 0.05), 0px 1px 2px -1px hsl(0 0% 10.1961% / 0.05); + --shadow: 0px 0px 0px 0px hsl(0 0% 10.1961% / 0.05), 0px 1px 2px -1px hsl(0 0% 10.1961% / 0.05); + --shadow-md: 0px 0px 0px 0px hsl(0 0% 10.1961% / 0.05), 0px 2px 4px -1px hsl(0 0% 10.1961% / 0.05); + --shadow-lg: 0px 0px 0px 0px hsl(0 0% 10.1961% / 0.05), 0px 4px 6px -1px hsl(0 0% 10.1961% / 0.05); + --shadow-xl: 0px 0px 0px 0px hsl(0 0% 10.1961% / 0.05), 0px 8px 10px -1px hsl(0 0% 10.1961% / 0.05); + --shadow-2xl: 0px 0px 0px 0px hsl(0 0% 10.1961% / 0.13); +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); + + --font-sans: 'DM Sans', sans-serif; + --font-mono: 'Space Mono', monospace; + --font-serif: 'DM Sans', sans-serif; + + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + + --shadow-2xs: var(--shadow-2xs); + --shadow-xs: var(--shadow-xs); + --shadow-sm: var(--shadow-sm); + --shadow: var(--shadow); + --shadow-md: var(--shadow-md); + --shadow-lg: var(--shadow-lg); + --shadow-xl: var(--shadow-xl); + --shadow-2xl: var(--shadow-2xl); + + --tracking-tighter: calc(var(--tracking-normal) - 0.05em); + --tracking-tight: calc(var(--tracking-normal) - 0.025em); + --tracking-normal: var(--tracking-normal); + --tracking-wide: calc(var(--tracking-normal) + 0.025em); + --tracking-wider: calc(var(--tracking-normal) + 0.05em); + --tracking-widest: calc(var(--tracking-normal) + 0.1em); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + font-family: var(--font-sans); + letter-spacing: var(--tracking-normal); + } +} diff --git a/src/app/layout 2.tsx b/src/app/layout 2.tsx new file mode 100644 index 0000000..a322b3a --- /dev/null +++ b/src/app/layout 2.tsx @@ -0,0 +1,26 @@ +import type { Metadata } from "next"; +import "./globals.css"; + +export const metadata: Metadata = { + title: "QuitTrack - Track Your Journey to Quit Smoking", + description: "Track and manage your smoking habits, set goals, and quit safely with personalized plans.", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + + + + + + {children} + + + ); +} diff --git a/src/app/page 2.tsx b/src/app/page 2.tsx new file mode 100644 index 0000000..932f860 --- /dev/null +++ b/src/app/page 2.tsx @@ -0,0 +1,13 @@ +import { redirect } from 'next/navigation'; +import { getUser } from '@/lib/session'; +import { Dashboard } from '@/components/Dashboard'; + +export default async function Home() { + const user = await getUser(); + + if (!user) { + redirect('/login'); + } + + return ; +} diff --git a/src/app/signout/page.tsx b/src/app/signout/page.tsx new file mode 100644 index 0000000..4560ce8 --- /dev/null +++ b/src/app/signout/page.tsx @@ -0,0 +1,7 @@ +import { signOut } from "@workos-inc/authkit-nextjs"; +import { redirect } from "next/navigation"; + +export default async function SignOutPage() { + await signOut(); + redirect("/"); +} diff --git a/src/components/DailyInspirationCard.tsx b/src/components/DailyInspirationCard.tsx new file mode 100644 index 0000000..62de9ea --- /dev/null +++ b/src/components/DailyInspirationCard.tsx @@ -0,0 +1,244 @@ +'use client'; + +import { useState, useMemo } from 'react'; +import { Sparkles, Settings, BookOpen, Quote as QuoteIcon, Check } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { savePreferencesAsync, getPreferences, UserPreferences } from '@/lib/storage'; + +type Religion = 'christian' | 'muslim' | 'jewish' | 'secular'; + +interface Verse { + text: string; + source: string; +} + +const VERSES: Record = { + christian: [ + { text: "I can do all things through Christ which strengtheneth me.", source: "Philippians 4:13 (KJV)" }, + { text: "For God hath not given us the spirit of fear; but of power, and of love, and of a sound mind.", source: "2 Timothy 1:7 (KJV)" }, + { text: "But they that wait upon the LORD shall renew their strength; they shall mount up with wings as eagles.", source: "Isaiah 40:31 (KJV)" }, + { text: "Be strong and of a good courage; be not afraid, neither be thou dismayed: for the LORD thy God is with thee whithersoever thou goest.", source: "Joshua 1:9 (KJV)" }, + { text: "The LORD is my shepherd; I shall not want.", source: "Psalm 23:1 (KJV)" }, + { text: "Trust in the LORD with all thine heart; and lean not unto thine own understanding.", source: "Proverbs 3:5 (KJV)" }, + { text: "And be not conformed to this world: but be ye transformed by the renewing of your mind.", source: "Romans 12:2 (KJV)" }, + { text: "Cast thy burden upon the LORD, and he shall sustain thee.", source: "Psalm 55:22 (KJV)" }, + { text: "Fear thou not; for I am with thee: be not dismayed; for I am thy God: I will strengthen thee.", source: "Isaiah 41:10 (KJV)" }, + { text: "Come unto me, all ye that labour and are heavy laden, and I will give you rest.", source: "Matthew 11:28 (KJV)" }, + { text: "The name of the LORD is a strong tower: the righteous runneth into it, and is safe.", source: "Proverbs 18:10 (KJV)" }, + { text: "Peace I leave with you, my peace I give unto you: not as the world giveth, give I unto you.", source: "John 14:27 (KJV)" }, + { text: "If God be for us, who can be against us?", source: "Romans 8:31 (KJV)" }, + { text: "He healeth the broken in heart, and bindeth up their wounds.", source: "Psalm 147:3 (KJV)" }, + { text: "Wait on the LORD: be of good courage, and he shall strengthen thine heart.", source: "Psalm 27:14 (KJV)" }, + { text: "My flesh and my heart faileth: but God is the strength of my heart, and my portion for ever.", source: "Psalm 73:26 (KJV)" }, + { text: "God is our refuge and strength, a very present help in trouble.", source: "Psalm 46:1 (KJV)" }, + { text: "In him was life; and the life was the light of men.", source: "John 1:4 (KJV)" }, + { text: "Therefore if any man be in Christ, he is a new creature: old things are passed away; behold, all things are become new.", source: "2 Corinthians 5:17 (KJV)" }, + { text: "The LORD shall fight for you, and ye shall hold your peace.", source: "Exodus 14:14 (KJV)" }, + ], + muslim: [ + { text: "Indeed, with hardship [will be] ease.", source: "Surah Ash-Sharh 94:6" }, + { text: "Allah does not burden a soul beyond that it can bear.", source: "Surah Al-Baqarah 2:286" }, + { text: "So remember Me; I will remember you.", source: "Surah Al-Baqarah 2:152" }, + { text: "And whosoever fears Allah... He will make a way for him to get out (from every difficulty).", source: "Surah At-Talaq 65:2" }, + { text: "Indeed, Allah is with the patient.", source: "Surah Al-Baqarah 2:153" }, + { text: "Call upon Me; I will respond to you.", source: "Surah Ghafir 40:60" }, + { text: "And He found you lost and guided [you].", source: "Surah Ad-Duhaa 93:7" }, + { text: "Unquestionably, by the remembrance of Allah hearts are assured.", source: "Surah Ar-Ra'd 13:28" }, + { text: "And put your trust in Allah, and sufficient is Allah as a Disposer of affairs.", source: "Surah Al-Ahzab 33:3" }, + { text: "Verily, in the remembrance of Allah do hearts find rest.", source: "Surah Ar-Ra'd 13:28" }, + { text: "Our Lord, pour upon us patience and plant firmly our feet.", source: "Surah Al-Baqarah 2:250" }, + { text: "If Allah should aid you, no one can overcome you.", source: "Surah Ali 'Imran 3:160" }, + { text: "Indeed, my Lord is near and responsive.", source: "Surah Hud 11:61" }, + { text: "And seek help through patience and prayer, and indeed, it is difficult except for the humbly submissive.", source: "Surah Al-Baqarah 2:45" }, + { text: "So surely with hardship comes ease.", source: "Surah Ash-Sharh 94:5" }, + { text: "Do not despair of the mercy of Allah.", source: "Surah Az-Zumar 39:53" }, + { text: "He knows what is in every heart.", source: "Surah Al-Mulk 67:13" }, + { text: "Allah is the best of planners.", source: "Surah Al-Anfal 8:30" }, + { text: "My Lord, indeed I am, for whatever good You would send down to me, in need.", source: "Surah Al-Qasas 28:24" }, + { text: "Indeed, good deeds do away with misdeeds.", source: "Surah Hud 11:114" }, + ], + jewish: [ + { text: "Be strong and of good courage.", source: "Joshua 1:9 (Tanakh)" }, + { text: "I have set the LORD always before me; because He is at my right hand, I shall not be moved.", source: "Psalm 16:8 (Tanakh)" }, + { text: "The LORD is my light and my salvation; whom shall I fear?", source: "Psalm 27:1 (Tanakh)" }, + { text: "Trust in the LORD with all your heart, and do not rely on your own understanding.", source: "Proverbs 3:5 (Tanakh)" }, + { text: "Create in me a clean heart, O God, and put a new and right spirit within me.", source: "Psalm 51:10 (Tanakh)" }, + { text: "Where there is no vision, the people perish.", source: "Proverbs 29:18 (Tanakh)" }, + { text: "A righteous man falls seven times, and rises up again.", source: "Proverbs 24:16 (Tanakh)" }, + { text: "Do not fear, for I am with you; do not be dismayed, for I am your God.", source: "Isaiah 41:10 (Tanakh)" }, + { text: "As water reflects the face, so one’s life reflects the heart.", source: "Proverbs 27:19 (Tanakh)" }, + { text: "If I am not for myself, who will be for me? And if I am only for myself, what am I?", source: "Pirkei Avot 1:14" }, + { text: "The world stands on three things: Torah, service, and acts of loving kindness.", source: "Pirkei Avot 1:2" }, + { text: "According to the effort is the reward.", source: "Pirkei Avot 5:23" }, + { text: "It is not your duty to finish the work, but neither are you at liberty to neglect it.", source: "Pirkei Avot 2:16" }, + { text: "Who is wise? One who learns from every man. Who is strong? One who overpowers his inclinations.", source: "Pirkei Avot 4:1" }, + { text: "Even in laughter the heart may ache, and the end of joy may be grief.", source: "Proverbs 14:13 (Tanakh)" }, + { text: "A soft answer turns away wrath, but a harsh word stirs up anger.", source: "Proverbs 15:1 (Tanakh)" }, + { text: "He who walks with integrity walks securely.", source: "Proverbs 10:9 (Tanakh)" }, + { text: "The candle of God is the soul of man.", source: "Proverbs 20:27 (Tanakh)" }, + { text: "There is a time for everything, and a season for every activity under the heavens.", source: "Ecclesiastes 3:1 (Tanakh)" }, + { text: "What you hate, do not do to your neighbor.", source: "Hillel the Elder (Shabbat 31a)" }, + ], + secular: [ + { text: "The only way to do great work is to love what you do.", source: "Steve Jobs" }, + { text: "It is during our darkest moments that we must focus to see the light.", source: "Aristotle" }, + { text: "The greatest glory in living lies not in never falling, but in rising every time we fall.", source: "Nelson Mandela" }, + { text: "In the middle of difficulty lies opportunity.", source: "Albert Einstein" }, + { text: "What you get by achieving your goals is not as important as what you become by achieving your goals.", source: "Zig Ziglar" }, + { text: "The future belongs to those who believe in the beauty of their dreams.", source: "Eleanor Roosevelt" }, + { text: "It does not matter how slowly you go as long as you do not stop.", source: "Confucius" }, + { text: "Everything you've ever wanted is on the other side of fear.", source: "George Addair" }, + { text: "The best time to plant a tree was 20 years ago. The second best time is now.", source: "Chinese Proverb" }, + { text: "You are never too old to set another goal or to dream a new dream.", source: "C.S. Lewis" }, + { text: "The only impossible journey is the one you never begin.", source: "Tony Robbins" }, + { text: "Success is not final, failure is not fatal: it is the courage to continue that counts.", source: "Winston Churchill" }, + { text: "Believe you can and you're halfway there.", source: "Theodore Roosevelt" }, + { text: "The pain you feel today will be the strength you feel tomorrow.", source: "Arnold Schwarzenegger" }, + { text: "Your life does not get better by chance, it gets better by change.", source: "Jim Rohn" }, + { text: "The secret of change is to focus all of your energy not on fighting the old, but on building the new.", source: "Socrates" }, + { text: "What lies behind us and what lies before us are tiny matters compared to what lies within us.", source: "Ralph Waldo Emerson" }, + { text: "The man who moves a mountain begins by carrying away small stones.", source: "Confucius" }, + { text: "Our greatest weakness lies in giving up. The most certain way to succeed is always to try just one more time.", source: "Thomas Edison" }, + { text: "Fall seven times, stand up eight.", source: "Japanese Proverb" }, + ] +}; + +interface DailyInspirationCardProps { + initialReligion?: Religion | null; + onReligionChange?: (religion: Religion) => void; +} + +export function DailyInspirationCard({ initialReligion, onReligionChange }: DailyInspirationCardProps) { + const [currentReligion, setCurrentReligion] = useState(initialReligion || 'secular'); + + // Get a quote based on the day of the year and selected religion + const dailyVerse = useMemo(() => { + const verses = VERSES[currentReligion]; + const now = new Date(); + const start = new Date(now.getFullYear(), 0, 0); + const diff = now.getTime() - start.getTime(); + const oneDay = 1000 * 60 * 60 * 24; + const dayOfYear = Math.floor(diff / oneDay); + return verses[dayOfYear % verses.length]; + }, [currentReligion]); + + const handleReligionChange = async (religion: Religion) => { + setCurrentReligion(religion); + + // Save to backend if we can + const prefs = getPreferences(); + if (prefs) { + const updatedPrefs: UserPreferences = { + ...prefs, + religion: religion + }; + await savePreferencesAsync(updatedPrefs); + } + + if (onReligionChange) { + onReligionChange(religion); + } + }; + + const getLabel = (r: Religion) => { + switch (r) { + case 'christian': return 'Christian (Bible)'; + case 'muslim': return 'Muslim (Quran)'; + case 'jewish': return 'Jewish (Torah)'; + case 'secular': return 'Secular (Quotes)'; + } + }; + + const getIcon = () => { + switch (currentReligion) { + case 'christian': + case 'muslim': + case 'jewish': + return ; + default: + return ; + } + }; + + const getBackground = () => { + switch (currentReligion) { + case 'christian': + return 'linear-gradient(135deg, rgba(59, 130, 246, 0.35) 0%, rgba(37, 99, 235, 0.3) 50%, rgba(30, 64, 175, 0.4) 100%)'; // Blue + case 'muslim': + return 'linear-gradient(135deg, rgba(16, 185, 129, 0.35) 0%, rgba(5, 150, 105, 0.3) 50%, rgba(4, 120, 87, 0.4) 100%)'; // Emerald + case 'jewish': + return 'linear-gradient(135deg, rgba(147, 51, 234, 0.35) 0%, rgba(126, 34, 206, 0.3) 50%, rgba(107, 33, 168, 0.4) 100%)'; // Purple + default: + return 'linear-gradient(135deg, rgba(67, 56, 202, 0.35) 0%, rgba(109, 40, 217, 0.3) 50%, rgba(76, 29, 149, 0.4) 100%)'; // Indigo/Original + } + }; + + const getShadowColor = () => { + switch (currentReligion) { + case 'christian': return 'rgba(59, 130, 246, 0.15)'; + case 'muslim': return 'rgba(16, 185, 129, 0.15)'; + case 'jewish': return 'rgba(147, 51, 234, 0.15)'; + default: return 'rgba(99, 102, 241, 0.15)'; + } + }; + + return ( +
+
+ +
+
+
+ {getIcon()} + + {currentReligion === 'secular' ? 'Daily Inspiration' : 'Daily Verse'} + +
+ + + + + + + {(['christian', 'muslim', 'jewish', 'secular'] as Religion[]).map((r) => ( + handleReligionChange(r)} + className="flex items-center justify-between gap-2" + > + {getLabel(r)} + {currentReligion === r && } + + ))} + + +
+ +

+ “{dailyVerse.text}” +

+

+ — {dailyVerse.source} +

+
+
+ ); +} diff --git a/src/components/Dashboard.tsx b/src/components/Dashboard.tsx index 7182c55..e5c1d55 100644 --- a/src/components/Dashboard.tsx +++ b/src/components/Dashboard.tsx @@ -116,7 +116,7 @@ export function Dashboard({ user }: DashboardProps) { init(); }, [loadData, checkAndUnlockAchievements]); - const handleSetupComplete = async (data: { substance: 'nicotine' | 'weed'; name: string; age: number }) => { + const handleSetupComplete = async (data: { substance: 'nicotine' | 'weed'; name: string; age: number; religion: 'christian' | 'muslim' | 'jewish' | 'secular' }) => { const today = new Date().toISOString().split('T')[0]; const newPrefs: UserPreferences = { substance: data.substance, @@ -126,6 +126,7 @@ export function Dashboard({ user }: DashboardProps) { quitPlan: null, userName: data.name, userAge: data.age, + religion: data.religion, }; await savePreferencesAsync(newPrefs); setPreferences(newPrefs); @@ -216,6 +217,12 @@ export function Dashboard({ user }: DashboardProps) { usageData={usageData} onDataUpdate={loadData} userId={user.id} + religion={preferences.religion} + onReligionUpdate={async (religion) => { + const updatedPrefs = { ...preferences, religion }; + setPreferences(updatedPrefs); + await savePreferencesAsync(updatedPrefs); + }} />
diff --git a/src/components/SetupWizard.tsx b/src/components/SetupWizard.tsx index 1e3106f..ed66334 100644 --- a/src/components/SetupWizard.tsx +++ b/src/components/SetupWizard.tsx @@ -21,7 +21,7 @@ import { interface SetupWizardProps { open: boolean; - onComplete: (data: { substance: 'nicotine' | 'weed'; name: string; age: number }) => void; + onComplete: (data: { substance: 'nicotine' | 'weed'; name: string; age: number; religion: 'christian' | 'muslim' | 'jewish' | 'secular' }) => void; } export function SetupWizard({ open, onComplete }: SetupWizardProps) { @@ -29,6 +29,7 @@ export function SetupWizard({ open, onComplete }: SetupWizardProps) { const [name, setName] = useState(''); const [age, setAge] = useState('25'); const [substance, setSubstance] = useState<'nicotine' | 'weed' | ''>(''); + const [religion, setReligion] = useState<'christian' | 'muslim' | 'jewish' | 'secular' | ''>(''); const ages = Array.from({ length: 83 }, (_, i) => (i + 18).toString()); @@ -37,15 +38,18 @@ export function SetupWizard({ open, onComplete }: SetupWizardProps) { setStep(2); } else if (step === 2 && age) { setStep(3); + } else if (step === 3 && substance) { + setStep(4); } }; const handleComplete = () => { - if (substance && name.trim() && age) { + if (substance && name.trim() && age && religion) { onComplete({ substance, name: name.trim(), age: parseInt(age, 10), + religion: religion, }); } }; @@ -59,6 +63,7 @@ export function SetupWizard({ open, onComplete }: SetupWizardProps) { {step === 1 && "Let's get to know you a little better."} {step === 2 && "Just one more thing about you."} {step === 3 && "Set up your tracking preferences."} + {step === 4 && "What inspires you?"} @@ -143,7 +148,38 @@ export function SetupWizard({ open, onComplete }: SetupWizardProps) { - +
+ + )} + + {step === 4 && ( +
+
+ + +

+ We'll show you a new verse or quote each day to help keep you motivated. +

+
+ +
+ +
diff --git a/src/components/UsageCalendar.tsx b/src/components/UsageCalendar.tsx index dc18da8..3eb19bc 100644 --- a/src/components/UsageCalendar.tsx +++ b/src/components/UsageCalendar.tsx @@ -15,69 +15,25 @@ import { Label } from '@/components/ui/label'; import { UsageEntry, setUsageForDateAsync, clearDayDataAsync } from '@/lib/storage'; import { ChevronLeftIcon, ChevronRightIcon, Cigarette, Leaf, Sparkles } from 'lucide-react'; import { useTheme } from '@/lib/theme-context'; +import { DailyInspirationCard } from './DailyInspirationCard'; + -const quotes = [ - { text: "The only way to do great work is to love what you do.", author: "Steve Jobs" }, - { text: "It is during our darkest moments that we must focus to see the light.", author: "Aristotle" }, - { text: "The greatest glory in living lies not in never falling, but in rising every time we fall.", author: "Nelson Mandela" }, - { text: "In the middle of difficulty lies opportunity.", author: "Albert Einstein" }, - { text: "What you get by achieving your goals is not as important as what you become by achieving your goals.", author: "Zig Ziglar" }, - { text: "The future belongs to those who believe in the beauty of their dreams.", author: "Eleanor Roosevelt" }, - { text: "It does not matter how slowly you go as long as you do not stop.", author: "Confucius" }, - { text: "Everything you've ever wanted is on the other side of fear.", author: "George Addair" }, - { text: "The best time to plant a tree was 20 years ago. The second best time is now.", author: "Chinese Proverb" }, - { text: "You are never too old to set another goal or to dream a new dream.", author: "C.S. Lewis" }, - { text: "The only impossible journey is the one you never begin.", author: "Tony Robbins" }, - { text: "Success is not final, failure is not fatal: it is the courage to continue that counts.", author: "Winston Churchill" }, - { text: "Believe you can and you're halfway there.", author: "Theodore Roosevelt" }, - { text: "The pain you feel today will be the strength you feel tomorrow.", author: "Arnold Schwarzenegger" }, - { text: "Your life does not get better by chance, it gets better by change.", author: "Jim Rohn" }, - { text: "The secret of change is to focus all of your energy not on fighting the old, but on building the new.", author: "Socrates" }, - { text: "What lies behind us and what lies before us are tiny matters compared to what lies within us.", author: "Ralph Waldo Emerson" }, - { text: "The man who moves a mountain begins by carrying away small stones.", author: "Confucius" }, - { text: "Our greatest weakness lies in giving up. The most certain way to succeed is always to try just one more time.", author: "Thomas Edison" }, - { text: "Fall seven times, stand up eight.", author: "Japanese Proverb" }, - { text: "You don't have to be great to start, but you have to start to be great.", author: "Zig Ziglar" }, - { text: "The only person you are destined to become is the person you decide to be.", author: "Ralph Waldo Emerson" }, - { text: "When you reach the end of your rope, tie a knot in it and hang on.", author: "Franklin D. Roosevelt" }, - { text: "Do not wait to strike till the iron is hot; but make it hot by striking.", author: "William Butler Yeats" }, - { text: "Whether you think you can or you think you can't, you're right.", author: "Henry Ford" }, - { text: "The mind is everything. What you think you become.", author: "Buddha" }, - { text: "Strength does not come from physical capacity. It comes from an indomitable will.", author: "Mahatma Gandhi" }, - { text: "A journey of a thousand miles begins with a single step.", author: "Lao Tzu" }, - { text: "He who conquers himself is the mightiest warrior.", author: "Confucius" }, - { text: "The wound is the place where the light enters you.", author: "Rumi" }, - { text: "Rock bottom became the solid foundation on which I rebuilt my life.", author: "J.K. Rowling" }, - { text: "You have power over your mind, not outside events. Realize this, and you will find strength.", author: "Marcus Aurelius" }, - { text: "Out of suffering have emerged the strongest souls.", author: "Kahlil Gibran" }, - { text: "The only limit to our realization of tomorrow will be our doubts of today.", author: "Franklin D. Roosevelt" }, - { text: "Courage is not the absence of fear, but rather the judgment that something else is more important than fear.", author: "Ambrose Redmoon" }, - { text: "Every morning brings new potential, but if you dwell on the misfortunes of the day before, you tend to overlook tremendous opportunities.", author: "Harvey Mackay" }, -]; interface UsageCalendarProps { usageData: UsageEntry[]; onDataUpdate: () => void; userId: string; + religion?: 'christian' | 'muslim' | 'jewish' | 'secular' | null; + onReligionUpdate?: (religion: 'christian' | 'muslim' | 'jewish' | 'secular') => void; } -export function UsageCalendar({ usageData, onDataUpdate }: UsageCalendarProps) { +export function UsageCalendar({ usageData, onDataUpdate, religion, onReligionUpdate }: UsageCalendarProps) { const [selectedDate, setSelectedDate] = useState(undefined); const [editNicotineCount, setEditNicotineCount] = useState(''); const [editWeedCount, setEditWeedCount] = useState(''); const [isEditing, setIsEditing] = useState(false); const { theme } = useTheme(); - // Get a quote based on the day of the year - const dailyQuote = useMemo(() => { - const now = new Date(); - const start = new Date(now.getFullYear(), 0, 0); - const diff = now.getTime() - start.getTime(); - const oneDay = 1000 * 60 * 60 * 24; - const dayOfYear = Math.floor(diff / oneDay); - return quotes[dayOfYear % quotes.length]; - }, []); - const getUsageForDate = (date: Date, substance: 'nicotine' | 'weed'): number => { const dateStr = date.toISOString().split('T')[0]; const entry = usageData.find((e) => e.date === dateStr && e.substance === substance); @@ -205,9 +161,8 @@ export function UsageCalendar({ usageData, onDataUpdate }: UsageCalendarProps) {
- {/* Daily Quote */} -
-
-
-
- - Daily Inspiration -
-

- “{dailyQuote.text}” -

-

- — {dailyQuote.author} -

-
-
+ {/* Daily Inspiration */} +
diff --git a/src/hooks/use-storage.ts b/src/hooks/use-storage.ts new file mode 100644 index 0000000..bba6ca2 --- /dev/null +++ b/src/hooks/use-storage.ts @@ -0,0 +1,147 @@ +"use client"; + +import { useState, useEffect, useCallback } from "react"; +import { + getUserData, + saveUserData, + getUsageLogs, + addUsageLog, + getRecentLogs, + calculateStats, + getQuitPlan, + generateQuitPlan, + getCurrentWeek, + canGeneratePlan, + needsCheckIn as checkNeedsCheckIn, +} from "@/lib/storage"; +import { UserData, UsageLog, QuitPlan, SubstanceType } from "@/types"; + +export function useUserData() { + const [userData, setUserData] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + setUserData(getUserData()); + setIsLoading(false); + }, []); + + const updateUserData = useCallback((data: Partial) => { + const updated = saveUserData(data); + setUserData(updated); + return updated; + }, []); + + const completeOnboarding = useCallback( + (substanceType: SubstanceType, stayLoggedIn: boolean) => { + return updateUserData({ + substanceType, + stayLoggedIn, + onboardingComplete: true, + }); + }, + [updateUserData] + ); + + return { + userData, + isLoading, + updateUserData, + completeOnboarding, + isOnboardingComplete: userData?.onboardingComplete ?? false, + }; +} + +export function useUsageLogs() { + const [logs, setLogs] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + const refreshLogs = useCallback(() => { + setLogs(getUsageLogs()); + }, []); + + useEffect(() => { + refreshLogs(); + setIsLoading(false); + }, [refreshLogs]); + + const logUsage = useCallback( + (puffs: number, date?: string) => { + const newLog = addUsageLog(puffs, date); + refreshLogs(); + return newLog; + }, + [refreshLogs] + ); + + const getRecent = useCallback((days: number = 7) => { + return getRecentLogs(days); + }, []); + + const stats = calculateStats(getRecentLogs(7)); + + return { + logs, + isLoading, + logUsage, + getRecent, + refreshLogs, + stats, + }; +} + +export function useQuitPlan() { + const [plan, setPlan] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + const refreshPlan = useCallback(() => { + setPlan(getQuitPlan()); + }, []); + + useEffect(() => { + refreshPlan(); + setIsLoading(false); + }, [refreshPlan]); + + const createPlan = useCallback(() => { + const recentLogs = getRecentLogs(7); + const { averagePuffs } = calculateStats(recentLogs); + + if (averagePuffs === 0) { + return null; + } + + const newPlan = generateQuitPlan(averagePuffs); + setPlan(newPlan); + return newPlan; + }, []); + + const currentWeek = plan ? getCurrentWeek(plan) : 0; + const { canGenerate, daysTracked } = canGeneratePlan(); + + return { + plan, + isLoading, + createPlan, + refreshPlan, + currentWeek, + canGenerate, + daysTracked, + }; +} + +export function useCheckIn() { + const [needsCheckIn, setNeedsCheckIn] = useState(false); + + useEffect(() => { + setNeedsCheckIn(checkNeedsCheckIn()); + }, []); + + const markCheckedIn = useCallback(() => { + setNeedsCheckIn(false); + }, []); + + return { + needsCheckIn, + markCheckedIn, + }; +} diff --git a/src/lib/storage.ts b/src/lib/storage.ts index 9c16529..1db105a 100644 --- a/src/lib/storage.ts +++ b/src/lib/storage.ts @@ -15,6 +15,7 @@ export interface UserPreferences { quitPlan: QuitPlan | null; userName: string | null; userAge: number | null; + religion: 'christian' | 'muslim' | 'jewish' | 'secular' | null; } export interface QuitPlan { @@ -95,6 +96,7 @@ const defaultPreferences: UserPreferences = { quitPlan: null, userName: null, userAge: null, + religion: null, }; // Cache for preferences and usage data to avoid excessive API calls