mirror of
https://github.com/ManInDark/HabitTrove.git
synced 2026-01-20 22:24:28 +01:00
use jotai for all states (#19)
This commit is contained in:
@@ -8,31 +8,25 @@ import { History } from 'lucide-react'
|
||||
import EmptyState from './EmptyState'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { toast } from '@/hooks/use-toast'
|
||||
import { useCoins } from '@/hooks/useCoins'
|
||||
import { settingsAtom } from '@/lib/atoms'
|
||||
import Link from 'next/link'
|
||||
import { useAtom } from 'jotai'
|
||||
import { settingsAtom } from '@/lib/atoms'
|
||||
import { useCoins } from '@/hooks/useCoins'
|
||||
|
||||
export default function CoinsManager() {
|
||||
const { balance, transactions, addAmount, removeAmount } = useCoins()
|
||||
const { add, remove, balance, transactions } = useCoins()
|
||||
const [settings] = useAtom(settingsAtom)
|
||||
const DEFAULT_AMOUNT = '0'
|
||||
const [amount, setAmount] = useState(DEFAULT_AMOUNT)
|
||||
|
||||
const handleAddCoins = async () => {
|
||||
const data = await addAmount(Number(amount), "Manual addition")
|
||||
if (data) {
|
||||
const handleAddRemoveCoins = async () => {
|
||||
const numAmount = Number(amount)
|
||||
if (numAmount > 0) {
|
||||
await add(numAmount, "Manual addition")
|
||||
setAmount(DEFAULT_AMOUNT)
|
||||
toast({ title: "Success", description: `Added ${amount} coins` })
|
||||
}
|
||||
}
|
||||
|
||||
const handleRemoveCoins = async () => {
|
||||
const data = await removeAmount(Math.abs(Number(amount)), "Manual removal")
|
||||
if (data) {
|
||||
} else if (numAmount < 0) {
|
||||
await remove(Math.abs(numAmount), "Manual removal")
|
||||
setAmount(DEFAULT_AMOUNT)
|
||||
toast({ title: "Success", description: `Removed ${amount} coins` })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,14 +78,7 @@ export default function CoinsManager() {
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={() => {
|
||||
const numAmount = Number(amount);
|
||||
if (numAmount > 0) {
|
||||
handleAddCoins();
|
||||
} else if (numAmount < 0) {
|
||||
handleRemoveCoins();
|
||||
}
|
||||
}}
|
||||
onClick={handleAddRemoveCoins}
|
||||
className="w-full h-14 transition-colors flex items-center justify-center font-medium"
|
||||
variant="default"
|
||||
>
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
'use client'
|
||||
|
||||
import { loadCoinsData } from '@/app/actions/data'
|
||||
import { useHabits } from '@/hooks/useHabits'
|
||||
import { useWishlist } from '@/hooks/useWishlist'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useAtom } from 'jotai'
|
||||
import { wishlistAtom, habitsAtom, settingsAtom, coinsAtom } from '@/lib/atoms'
|
||||
import CoinBalance from './CoinBalance'
|
||||
import DailyOverview from './DailyOverview'
|
||||
import HabitOverview from './HabitOverview'
|
||||
import HabitStreak from './HabitStreak'
|
||||
import { useHabits } from '@/hooks/useHabits'
|
||||
|
||||
export default function Dashboard() {
|
||||
const { habits, completeHabit, undoComplete } = useHabits()
|
||||
const [coinBalance, setCoinBalance] = useState(0)
|
||||
const { wishlistItems } = useWishlist()
|
||||
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
const coinsData = await loadCoinsData()
|
||||
setCoinBalance(coinsData.balance)
|
||||
}
|
||||
loadData()
|
||||
}, [])
|
||||
const { completeHabit, undoComplete } = useHabits()
|
||||
const [habitsData] = useAtom(habitsAtom)
|
||||
const habits = habitsData.habits
|
||||
const [settings] = useAtom(settingsAtom)
|
||||
const [coins] = useAtom(coinsAtom)
|
||||
const coinBalance = coins.balance
|
||||
const [wishlist] = useAtom(wishlistAtom)
|
||||
const wishlistItems = wishlist.items
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
@@ -33,18 +28,8 @@ export default function Dashboard() {
|
||||
wishlistItems={wishlistItems}
|
||||
habits={habits}
|
||||
coinBalance={coinBalance}
|
||||
onComplete={async (habit) => {
|
||||
const newBalance = await completeHabit(habit)
|
||||
if (newBalance !== null) {
|
||||
setCoinBalance(newBalance)
|
||||
}
|
||||
}}
|
||||
onUndo={async (habit) => {
|
||||
const newBalance = await undoComplete(habit)
|
||||
if (newBalance !== null) {
|
||||
setCoinBalance(newBalance)
|
||||
}
|
||||
}}
|
||||
onComplete={completeHabit}
|
||||
onUndo={undoComplete}
|
||||
/>
|
||||
|
||||
{/* <HabitHeatmap habits={habits} /> */}
|
||||
|
||||
@@ -1,31 +1,20 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { Calendar } from '@/components/ui/calendar'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
|
||||
import { loadHabitsData } from '@/app/actions/data'
|
||||
import { Habit } from '@/lib/types'
|
||||
import { d2s, getNow } from '@/lib/utils'
|
||||
import { useAtom } from 'jotai'
|
||||
import { settingsAtom } from '@/lib/atoms'
|
||||
import { habitsAtom, settingsAtom } from '@/lib/atoms'
|
||||
import { DateTime } from 'luxon'
|
||||
import Linkify from './linkify'
|
||||
|
||||
export default function HabitCalendar() {
|
||||
const [settings] = useAtom(settingsAtom)
|
||||
const [selectedDate, setSelectedDate] = useState<DateTime>(getNow({ timezone: settings.system.timezone }))
|
||||
const [habits, setHabits] = useState<Habit[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
fetchHabitsData()
|
||||
}, [])
|
||||
|
||||
const fetchHabitsData = async () => {
|
||||
const data = await loadHabitsData()
|
||||
setHabits(data.habits)
|
||||
}
|
||||
const [habitsData] = useAtom(habitsAtom)
|
||||
const habits = habitsData.habits
|
||||
|
||||
const getHabitsForDate = (date: Date) => {
|
||||
const dateString = date.toISOString().split('T')[0]
|
||||
@@ -46,7 +35,7 @@ export default function HabitCalendar() {
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={selectedDate.toJSDate()}
|
||||
// onSelect={(e) => e && setSelectedDate(DateTime.fromJSDate(e))}
|
||||
onSelect={(e) => e && setSelectedDate(DateTime.fromJSDate(e))}
|
||||
className="rounded-md border"
|
||||
modifiers={{
|
||||
completed: (date) => getHabitsForDate(date).length > 0,
|
||||
|
||||
@@ -6,16 +6,16 @@ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle }
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Coins, Edit, Trash2, Check, Undo2 } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useHabits } from '@/hooks/useHabits'
|
||||
|
||||
interface HabitItemProps {
|
||||
habit: Habit
|
||||
onEdit: () => void
|
||||
onDelete: () => void
|
||||
onComplete: () => void
|
||||
onUndo: () => void
|
||||
}
|
||||
|
||||
export default function HabitItem({ habit, onEdit, onDelete, onComplete, onUndo }: HabitItemProps) {
|
||||
export default function HabitItem({ habit, onEdit, onDelete }: HabitItemProps) {
|
||||
const { completeHabit, undoComplete } = useHabits()
|
||||
const [settings] = useAtom(settingsAtom)
|
||||
const today = getTodayInTimezone(settings.system.timezone)
|
||||
const isCompletedToday = habit.completions?.includes(today)
|
||||
@@ -41,7 +41,7 @@ export default function HabitItem({ habit, onEdit, onDelete, onComplete, onUndo
|
||||
}, [habit.id])
|
||||
|
||||
return (
|
||||
<Card
|
||||
<Card
|
||||
id={`habit-${habit.id}`}
|
||||
className={`transition-all duration-500 ${isHighlighted ? 'bg-yellow-100 dark:bg-yellow-900' : ''}`}
|
||||
>
|
||||
@@ -71,7 +71,7 @@ export default function HabitItem({ habit, onEdit, onDelete, onComplete, onUndo
|
||||
<Button
|
||||
variant={isCompletedToday ? "secondary" : "default"}
|
||||
size="sm"
|
||||
onClick={onComplete}
|
||||
onClick={async () => await completeHabit(habit)}
|
||||
disabled={isCompletedToday}
|
||||
>
|
||||
<Check className="h-4 w-4 mr-2" />
|
||||
@@ -81,7 +81,7 @@ export default function HabitItem({ habit, onEdit, onDelete, onComplete, onUndo
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={onUndo}
|
||||
onClick={async () => await undoComplete(habit)}
|
||||
>
|
||||
<Undo2 />
|
||||
</Button>
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useHabits } from '@/hooks/useHabits'
|
||||
import { Plus, ListTodo } from 'lucide-react'
|
||||
import { useAtom } from 'jotai'
|
||||
import { habitsAtom, settingsAtom } from '@/lib/atoms'
|
||||
import EmptyState from './EmptyState'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import HabitItem from './HabitItem'
|
||||
import AddEditHabitModal from './AddEditHabitModal'
|
||||
import ConfirmDialog from './ConfirmDialog'
|
||||
import { Habit } from '@/lib/types'
|
||||
import { useHabits } from '@/hooks/useHabits'
|
||||
|
||||
export default function HabitList() {
|
||||
const { habits, addHabit, editHabit, deleteHabit, completeHabit, undoComplete } = useHabits()
|
||||
const { saveHabit, deleteHabit } = useHabits()
|
||||
const [habitsData, setHabitsData] = useAtom(habitsAtom)
|
||||
const habits = habitsData.habits
|
||||
const [settings] = useAtom(settingsAtom)
|
||||
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||
const [editingHabit, setEditingHabit] = useState<Habit | null>(null)
|
||||
const [deleteConfirmation, setDeleteConfirmation] = useState<{ isOpen: boolean, habitId: string | null }>({
|
||||
@@ -39,17 +44,15 @@ export default function HabitList() {
|
||||
</div>
|
||||
) : (
|
||||
habits.map((habit) => (
|
||||
<HabitItem
|
||||
key={habit.id}
|
||||
habit={habit}
|
||||
onEdit={() => {
|
||||
setEditingHabit(habit)
|
||||
setIsModalOpen(true)
|
||||
}}
|
||||
onDelete={() => setDeleteConfirmation({ isOpen: true, habitId: habit.id })}
|
||||
onComplete={() => completeHabit(habit)}
|
||||
onUndo={() => undoComplete(habit)}
|
||||
/>
|
||||
<HabitItem
|
||||
key={habit.id}
|
||||
habit={habit}
|
||||
onEdit={() => {
|
||||
setEditingHabit(habit)
|
||||
setIsModalOpen(true)
|
||||
}}
|
||||
onDelete={() => setDeleteConfirmation({ isOpen: true, habitId: habit.id })}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
@@ -60,11 +63,7 @@ export default function HabitList() {
|
||||
setEditingHabit(null)
|
||||
}}
|
||||
onSave={async (habit) => {
|
||||
if (editingHabit) {
|
||||
await editHabit({ ...habit, id: editingHabit.id })
|
||||
} else {
|
||||
await addHabit(habit)
|
||||
}
|
||||
await saveHabit({ ...habit, id: editingHabit?.id })
|
||||
setIsModalOpen(false)
|
||||
setEditingHabit(null)
|
||||
}}
|
||||
@@ -73,9 +72,9 @@ export default function HabitList() {
|
||||
<ConfirmDialog
|
||||
isOpen={deleteConfirmation.isOpen}
|
||||
onClose={() => setDeleteConfirmation({ isOpen: false, habitId: null })}
|
||||
onConfirm={() => {
|
||||
onConfirm={async () => {
|
||||
if (deleteConfirmation.habitId) {
|
||||
deleteHabit(deleteConfirmation.habitId)
|
||||
await deleteHabit(deleteConfirmation.habitId)
|
||||
}
|
||||
setDeleteConfirmation({ isOpen: false, habitId: null })
|
||||
}}
|
||||
|
||||
@@ -1,22 +1,12 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { BarChart } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { getTodayInTimezone } from '@/lib/utils'
|
||||
import { loadHabitsData } from '@/app/actions/data'
|
||||
import { Habit } from '@/lib/types'
|
||||
import { useAtom } from 'jotai'
|
||||
import { settingsAtom } from '@/lib/atoms'
|
||||
import { habitsAtom, settingsAtom } from '@/lib/atoms'
|
||||
|
||||
export default function HabitOverview() {
|
||||
const [habits, setHabits] = useState<Habit[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
const fetchHabits = async () => {
|
||||
const data = await loadHabitsData()
|
||||
setHabits(data.habits)
|
||||
}
|
||||
fetchHabits()
|
||||
}, [])
|
||||
const [habitsData] = useAtom(habitsAtom)
|
||||
const habits = habitsData.habits
|
||||
|
||||
const [settings] = useAtom(settingsAtom)
|
||||
const today = getTodayInTimezone(settings.system.timezone)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { useWishlist } from '@/hooks/useWishlist'
|
||||
import { Plus, Gift } from 'lucide-react'
|
||||
import EmptyState from './EmptyState'
|
||||
import { Button } from '@/components/ui/button'
|
||||
@@ -8,16 +9,15 @@ import WishlistItem from './WishlistItem'
|
||||
import AddEditWishlistItemModal from './AddEditWishlistItemModal'
|
||||
import ConfirmDialog from './ConfirmDialog'
|
||||
import { WishlistItemType } from '@/lib/types'
|
||||
import { useWishlist } from '@/hooks/useWishlist'
|
||||
|
||||
export default function WishlistManager() {
|
||||
const {
|
||||
wishlistItems,
|
||||
addWishlistItem,
|
||||
editWishlistItem,
|
||||
deleteWishlistItem,
|
||||
redeemWishlistItem,
|
||||
canRedeem
|
||||
canRedeem,
|
||||
wishlistItems
|
||||
} = useWishlist()
|
||||
|
||||
const [highlightedItemId, setHighlightedItemId] = useState<string | null>(null)
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
'use client'
|
||||
|
||||
import { settingsAtom } from "@/lib/atoms"
|
||||
import { settingsAtom, habitsAtom, coinsAtom, wishlistAtom } from "@/lib/atoms"
|
||||
import { useHydrateAtoms } from "jotai/utils"
|
||||
import { Settings } from "@/lib/types"
|
||||
import { JotaiHydrateInitialValues } from "@/lib/types"
|
||||
|
||||
export function JotaiHydrate({
|
||||
export function JotaiHydrate({
|
||||
children,
|
||||
initialSettings
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
initialSettings: Settings
|
||||
}) {
|
||||
useHydrateAtoms([[settingsAtom, initialSettings]])
|
||||
initialValues
|
||||
}: { children: React.ReactNode, initialValues: JotaiHydrateInitialValues }) {
|
||||
useHydrateAtoms([
|
||||
[settingsAtom, initialValues.settings],
|
||||
[habitsAtom, initialValues.habits],
|
||||
[coinsAtom, initialValues.coins],
|
||||
[wishlistAtom, initialValues.wishlist]
|
||||
])
|
||||
return children
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Linkify0 from "linkify-react";
|
||||
|
||||
export default function Linkify({ children }: { children: React.ReactNode }) {
|
||||
return <Linkify0 options={{ className: "underline" }}>{children}</Linkify0>;
|
||||
return <Linkify0 options={{ className: "underline", target: "_blank" }}>{children}</Linkify0>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user