mirror of
https://github.com/ManInDark/HabitTrove.git
synced 2026-01-21 06:34:30 +01:00
Merge Tag v0.2.20
This commit is contained in:
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,5 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## Version 0.2.20
|
||||
|
||||
### Fixed
|
||||
|
||||
* coin balance shows correct value for selected user in coin management view (#151)
|
||||
|
||||
### Improved
|
||||
|
||||
* refactor code to remove client-helpers hook
|
||||
|
||||
## Version 0.2.19
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -7,8 +7,7 @@ import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { settingsAtom, usersAtom } from '@/lib/atoms'
|
||||
import { useHelpers } from '@/lib/client-helpers'
|
||||
import { currentUserAtom, settingsAtom, usersAtom } from '@/lib/atoms'
|
||||
import { INITIAL_DUE, INITIAL_RECURRENCE_RULE, MAX_COIN_LIMIT, QUICK_DATES } from '@/lib/constants'
|
||||
import { Habit } from '@/lib/types'
|
||||
import { convertHumanReadableFrequencyToMachineReadable, convertMachineReadableFrequencyToHumanReadable, d2t, serializeRRule } from '@/lib/utils'
|
||||
@@ -20,6 +19,7 @@ import { useState } from 'react'
|
||||
import { RRule } from 'rrule'
|
||||
import EmojiPickerButton from './EmojiPickerButton'
|
||||
|
||||
|
||||
interface AddEditHabitModalProps {
|
||||
onClose: () => void
|
||||
onSave: (habit: Omit<Habit, 'id'>) => Promise<void>
|
||||
@@ -42,7 +42,7 @@ export default function AddEditHabitModal({ onClose, onSave, habit, isTask }: Ad
|
||||
timezone: settings.system.timezone
|
||||
}) : (isRecurRule ? INITIAL_RECURRENCE_RULE : INITIAL_DUE);
|
||||
const [ruleText, setRuleText] = useState<string>(initialRuleText)
|
||||
const { currentUser } = useHelpers()
|
||||
const [currentUser] = useAtom(currentUserAtom)
|
||||
const [isQuickDatesOpen, setIsQuickDatesOpen] = useState(false)
|
||||
const [selectedUserIds, setSelectedUserIds] = useState<string[]>((habit?.userIds || []).filter(id => id !== currentUser?.id))
|
||||
const [usersData] = useAtom(usersAtom)
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { usersAtom } from '@/lib/atoms'
|
||||
import { useHelpers } from '@/lib/client-helpers'
|
||||
import { currentUserAtom, usersAtom } from '@/lib/atoms'
|
||||
import { MAX_COIN_LIMIT } from '@/lib/constants'
|
||||
import { WishlistItemType } from '@/lib/types'
|
||||
import { useAtom } from 'jotai'
|
||||
@@ -37,7 +35,7 @@ export default function AddEditWishlistItemModal({
|
||||
const [coinCost, setCoinCost] = useState(editingItem?.coinCost || 1)
|
||||
const [targetCompletions, setTargetCompletions] = useState<number | undefined>(editingItem?.targetCompletions)
|
||||
const [link, setLink] = useState(editingItem?.link || '')
|
||||
const { currentUser } = useHelpers()
|
||||
const [currentUser] = useAtom(currentUserAtom)
|
||||
const [selectedUserIds, setSelectedUserIds] = useState<string[]>((editingItem?.userIds || []).filter(id => id !== currentUser?.id))
|
||||
const [errors, setErrors] = useState<{ [key: string]: string }>({})
|
||||
const [usersData] = useAtom(usersAtom)
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
'use client'
|
||||
|
||||
import { ReactNode, Suspense, useEffect, useState } from 'react'
|
||||
import { useAtom } from 'jotai'
|
||||
import { aboutOpenAtom, pomodoroAtom, userSelectAtom } from '@/lib/atoms'
|
||||
import PomodoroTimer from './PomodoroTimer'
|
||||
import UserSelectModal from './UserSelectModal'
|
||||
import { aboutOpenAtom, currentUserIdAtom, pomodoroAtom, userSelectAtom } from '@/lib/atoms';
|
||||
import { useAtom, useSetAtom } from 'jotai';
|
||||
import { useSession } from 'next-auth/react'
|
||||
import { ReactNode, useEffect, useState } from 'react'
|
||||
import AboutModal from './AboutModal'
|
||||
import LoadingSpinner from './LoadingSpinner'
|
||||
import PomodoroTimer from './PomodoroTimer'
|
||||
import UserSelectModal from './UserSelectModal'
|
||||
|
||||
export default function ClientWrapper({ children }: { children: ReactNode }) {
|
||||
const [pomo] = useAtom(pomodoroAtom)
|
||||
const [userSelect, setUserSelect] = useAtom(userSelectAtom)
|
||||
const [aboutOpen, setAboutOpen] = useAtom(aboutOpenAtom)
|
||||
const setCurrentUserIdAtom = useSetAtom(currentUserIdAtom)
|
||||
const { data: session, status } = useSession()
|
||||
const currentUserId = session?.user.id
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
@@ -29,6 +30,10 @@ export default function ClientWrapper({ children }: { children: ReactNode }) {
|
||||
}
|
||||
}, [currentUserId, status, userSelect, setUserSelect])
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentUserIdAtom(currentUserId)
|
||||
}, [currentUserId, setCurrentUserIdAtom])
|
||||
|
||||
if (!isMounted) {
|
||||
return <LoadingSpinner />
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@ import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { useCoins } from '@/hooks/useCoins'
|
||||
import { settingsAtom, usersAtom } from '@/lib/atoms'
|
||||
import { useHelpers } from '@/lib/client-helpers'
|
||||
import { currentUserAtom, settingsAtom, usersAtom } from '@/lib/atoms'
|
||||
import { MAX_COIN_LIMIT } from '@/lib/constants'
|
||||
import { TransactionType } from '@/lib/types'
|
||||
import { d2s, t2d } from '@/lib/utils'
|
||||
@@ -22,7 +21,7 @@ import { TransactionNoteEditor } from './TransactionNoteEditor'
|
||||
|
||||
export default function CoinsManager() {
|
||||
const t = useTranslations('CoinsManager')
|
||||
const { currentUser } = useHelpers()
|
||||
const [currentUser] = useAtom(currentUserAtom)
|
||||
const [selectedUser, setSelectedUser] = useState<string>()
|
||||
const {
|
||||
add,
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Habit } from '@/lib/types';
|
||||
import { Habit, User } from '@/lib/types';
|
||||
import { useHabits } from '@/hooks/useHabits';
|
||||
import { useAtom } from 'jotai';
|
||||
import { pomodoroAtom, settingsAtom } from '@/lib/atoms';
|
||||
import { d2t, getNow, isHabitDueToday } from '@/lib/utils';
|
||||
import { pomodoroAtom, settingsAtom, currentUserAtom } from '@/lib/atoms';
|
||||
import { d2t, getNow, isHabitDueToday, hasPermission } from '@/lib/utils';
|
||||
import { DropdownMenuItem, DropdownMenuSeparator } from '@/components/ui/dropdown-menu';
|
||||
import { ContextMenuItem, ContextMenuSeparator } from '@/components/ui/context-menu';
|
||||
import { Timer, Calendar, Pin, Edit, Archive, ArchiveRestore, Trash2 } from 'lucide-react';
|
||||
import { useHelpers } from '@/lib/client-helpers'; // For permission checks if needed, though useHabits handles most
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
interface HabitContextMenuItemsProps {
|
||||
@@ -28,10 +27,10 @@ export function HabitContextMenuItems({
|
||||
const { saveHabit, archiveHabit, unarchiveHabit } = useHabits();
|
||||
const [settings] = useAtom(settingsAtom);
|
||||
const [, setPomo] = useAtom(pomodoroAtom);
|
||||
const { hasPermission } = useHelpers(); // Assuming useHabits handles permissions for its actions
|
||||
const [currentUser] = useAtom(currentUserAtom);
|
||||
|
||||
const canWrite = hasPermission('habit', 'write'); // For UI disabling if not handled by useHabits' actions
|
||||
const canInteract = hasPermission('habit', 'interact');
|
||||
const canWrite = hasPermission(currentUser, 'habit', 'write'); // For UI disabling if not handled by useHabits' actions
|
||||
const canInteract = hasPermission(currentUser, 'habit', 'interact');
|
||||
|
||||
const MenuItemComponent = context === 'daily-overview' ? ContextMenuItem : DropdownMenuItem;
|
||||
const MenuSeparatorComponent = context === 'daily-overview' ? ContextMenuSeparator : DropdownMenuSeparator;
|
||||
|
||||
@@ -6,12 +6,11 @@ import {
|
||||
DropdownMenuTrigger
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { useHabits } from '@/hooks/useHabits'
|
||||
import { settingsAtom, usersAtom } from '@/lib/atoms'
|
||||
import { useHelpers } from '@/lib/client-helpers'
|
||||
import { currentUserAtom, settingsAtom, usersAtom } from '@/lib/atoms'
|
||||
import { Habit, User } from '@/lib/types'
|
||||
import { convertMachineReadableFrequencyToHumanReadable, getCompletionsForToday, isTaskOverdue } from '@/lib/utils'
|
||||
import { convertMachineReadableFrequencyToHumanReadable, getCompletionsForToday, hasPermission, isTaskOverdue } from '@/lib/utils'
|
||||
import { useAtom } from 'jotai'
|
||||
import { Check, Coins, Edit, MoreVertical, Pin, Undo2 } from 'lucide-react'; // Removed unused icons
|
||||
import { Check, Coins, Edit, MoreVertical, Pin, Undo2 } from 'lucide-react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { useEffect, useState } from 'react'
|
||||
@@ -45,7 +44,7 @@ const renderUserAvatars = (habit: Habit, currentUser: User | null, usersData: {
|
||||
|
||||
|
||||
export default function HabitItem({ habit, onEdit, onDelete }: HabitItemProps) {
|
||||
const { completeHabit, undoComplete, archiveHabit, unarchiveHabit, saveHabit } = useHabits()
|
||||
const { completeHabit, undoComplete } = useHabits()
|
||||
const [settings] = useAtom(settingsAtom)
|
||||
const completionsToday = getCompletionsForToday({ habit, timezone: settings.system.timezone })
|
||||
const target = habit.targetCompletions || 1
|
||||
@@ -53,10 +52,10 @@ export default function HabitItem({ habit, onEdit, onDelete }: HabitItemProps) {
|
||||
const [isHighlighted, setIsHighlighted] = useState(false)
|
||||
const t = useTranslations('HabitItem');
|
||||
const [usersData] = useAtom(usersAtom)
|
||||
const { currentUser, hasPermission } = useHelpers()
|
||||
const canWrite = hasPermission('habit', 'write')
|
||||
const canInteract = hasPermission('habit', 'interact')
|
||||
const pathname = usePathname();
|
||||
const [currentUser] = useAtom(currentUserAtom)
|
||||
const canWrite = hasPermission(currentUser, 'habit', 'write')
|
||||
const canInteract = hasPermission(currentUser, 'habit', 'interact')
|
||||
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Link from 'next/link'
|
||||
import { NavDisplayProps, NavItemType } from './Navigation';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useHelpers } from '@/lib/client-helpers';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { NavDisplayProps } from './Navigation';
|
||||
|
||||
export default function MobileNavDisplay({ navItems }: NavDisplayProps) {
|
||||
const pathname = usePathname();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useMemo } from 'react'
|
||||
import { useAtom } from 'jotai'
|
||||
import { coinsAtom, habitsAtom, wishlistAtom, usersAtom } from '@/lib/atoms'
|
||||
import { coinsAtom, habitsAtom, wishlistAtom, usersAtom, currentUserAtom } from '@/lib/atoms'
|
||||
import { Bell } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useTranslations } from 'next-intl';
|
||||
@@ -14,12 +14,11 @@ import {
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { updateLastNotificationReadTimestamp } from '@/app/actions/data';
|
||||
import { d2t, getNow, t2d } from '@/lib/utils';
|
||||
import { useHelpers } from '@/lib/client-helpers';
|
||||
import { User, CoinTransaction } from '@/lib/types';
|
||||
|
||||
export default function NotificationBell() {
|
||||
const t = useTranslations('NotificationBell');
|
||||
const { currentUser } = useHelpers();
|
||||
const [currentUser] = useAtom(currentUserAtom);
|
||||
const [coinsData] = useAtom(coinsAtom)
|
||||
const [habitsData] = useAtom(habitsAtom)
|
||||
const [wishlistData] = useAtom(wishlistAtom)
|
||||
@@ -122,7 +121,7 @@ export default function NotificationBell() {
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="p-0 w-80 md:w-96">
|
||||
<NotificationDropdown
|
||||
currentUser={currentUser as User | null} // Cast needed as useHelpers can return undefined initially
|
||||
currentUser={currentUser as User | null} // Cast needed as as currentUser can be undefined
|
||||
unreadNotifications={unreadNotifications}
|
||||
displayedReadNotifications={displayedReadNotifications}
|
||||
habitsData={habitsData} // Pass necessary data down
|
||||
|
||||
@@ -5,8 +5,7 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
||||
import { toast } from "@/hooks/use-toast"
|
||||
import { aboutOpenAtom, settingsAtom, userSelectAtom } from "@/lib/atoms"
|
||||
import { useHelpers } from "@/lib/client-helpers"
|
||||
import { aboutOpenAtom, currentUserAtom, settingsAtom, userSelectAtom } from "@/lib/atoms"
|
||||
import { useAtom } from "jotai"
|
||||
import { ArrowRightLeft, Crown, Info, LogOut, Moon, Palette, Settings, Sun, User } from "lucide-react"
|
||||
import { useTranslations } from 'next-intl'
|
||||
@@ -23,7 +22,7 @@ export function Profile() {
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
const [aboutOpen, setAboutOpen] = useAtom(aboutOpenAtom)
|
||||
const { theme, setTheme } = useTheme()
|
||||
const { currentUser: user } = useHelpers()
|
||||
const [user] = useAtom(currentUserAtom)
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const handleSignOut = async () => {
|
||||
|
||||
@@ -13,8 +13,7 @@ import {
|
||||
AlertDialogTrigger,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { serverSettingsAtom, usersAtom } from '@/lib/atoms';
|
||||
import { useHelpers } from '@/lib/client-helpers';
|
||||
import { currentUserAtom, serverSettingsAtom, usersAtom } from '@/lib/atoms';
|
||||
import { Permission } from '@/lib/types';
|
||||
import { passwordSchema, usernameSchema } from '@/lib/zod';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
@@ -29,6 +28,7 @@ import { Input } from './ui/input';
|
||||
import { Label } from './ui/label';
|
||||
import { Switch } from './ui/switch';
|
||||
|
||||
|
||||
interface UserFormProps {
|
||||
userId?: string; // if provided, we're editing; if not, we're creating
|
||||
onCancel: () => void;
|
||||
@@ -40,7 +40,7 @@ export default function UserForm({ userId, onCancel, onSuccess }: UserFormProps)
|
||||
const [users, setUsersData] = useAtom(usersAtom);
|
||||
const serverSettings = useAtomValue(serverSettingsAtom)
|
||||
const user = userId ? users.users.find(u => u.id === userId) : undefined;
|
||||
const { currentUser } = useHelpers()
|
||||
const [currentUser] = useAtom(currentUserAtom)
|
||||
const getDefaultPermissions = (): Permission[] => [{
|
||||
habit: {
|
||||
write: true,
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
import { signIn } from '@/app/actions/user';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { usersAtom } from '@/lib/atoms';
|
||||
import { useHelpers } from '@/lib/client-helpers';
|
||||
import { currentUserAtom, usersAtom } from '@/lib/atoms';
|
||||
import { SafeUser, User } from '@/lib/types';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Description } from '@radix-ui/react-dialog';
|
||||
@@ -131,7 +130,7 @@ export default function UserSelectModal({ onClose }: { onClose: () => void }) {
|
||||
const [error, setError] = useState('');
|
||||
const [usersData, setUsersData] = useAtom(usersAtom);
|
||||
const users = usersData.users;
|
||||
const { currentUser } = useHelpers();
|
||||
const [currentUser] = useAtom(currentUserAtom);
|
||||
|
||||
|
||||
const handleUserSelect = (userId: string) => {
|
||||
|
||||
@@ -7,9 +7,9 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { usersAtom } from '@/lib/atoms'
|
||||
import { useHelpers } from '@/lib/client-helpers'
|
||||
import { currentUserAtom, usersAtom } from '@/lib/atoms'
|
||||
import { User, WishlistItemType } from '@/lib/types'
|
||||
import { hasPermission } from '@/lib/utils'
|
||||
import { useAtom } from 'jotai'
|
||||
import { Archive, ArchiveRestore, Coins, Edit, Gift, MoreVertical, Trash2 } from 'lucide-react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
@@ -59,9 +59,9 @@ export default function WishlistItem({
|
||||
isRecentlyRedeemed
|
||||
}: WishlistItemProps) {
|
||||
const t = useTranslations('WishlistItem')
|
||||
const { currentUser, hasPermission } = useHelpers()
|
||||
const canWrite = hasPermission('wishlist', 'write')
|
||||
const canInteract = hasPermission('wishlist', 'interact')
|
||||
const [currentUser] = useAtom(currentUserAtom)
|
||||
const canWrite = hasPermission(currentUser, 'wishlist', 'write')
|
||||
const canInteract = hasPermission(currentUser, 'wishlist', 'interact')
|
||||
const [usersData] = useAtom(usersAtom)
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useAtom } from 'jotai'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { useAtom } from 'jotai';
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { calculateCoinsEarnedToday, calculateCoinsSpentToday, calculateTotalEarned, calculateTotalSpent, calculateTransactionsToday, checkPermission } from '@/lib/utils'
|
||||
import {
|
||||
coinsAtom,
|
||||
@@ -11,11 +12,11 @@ import {
|
||||
coinsBalanceAtom,
|
||||
settingsAtom,
|
||||
usersAtom,
|
||||
currentUserAtom,
|
||||
} from '@/lib/atoms'
|
||||
import { addCoins, removeCoins, saveCoinsData } from '@/app/actions/data'
|
||||
import { CoinsData, User } from '@/lib/types'
|
||||
import { toast } from '@/hooks/use-toast'
|
||||
import { useHelpers } from '@/lib/client-helpers'
|
||||
import { MAX_COIN_LIMIT } from '@/lib/constants'
|
||||
|
||||
function handlePermissionCheck(
|
||||
@@ -51,23 +52,59 @@ export function useCoins(options?: { selectedUser?: string }) {
|
||||
const [coins, setCoins] = useAtom(coinsAtom)
|
||||
const [settings] = useAtom(settingsAtom)
|
||||
const [users] = useAtom(usersAtom)
|
||||
const { currentUser } = useHelpers()
|
||||
let user: User | undefined;
|
||||
if (!options?.selectedUser) {
|
||||
user = currentUser;
|
||||
} else {
|
||||
user = users.users.find(u => u.id === options.selectedUser)
|
||||
}
|
||||
const [currentUser] = useAtom(currentUserAtom)
|
||||
const [allCoinsData] = useAtom(coinsAtom) // All coin transactions
|
||||
const [loggedInUserBalance] = useAtom(coinsBalanceAtom) // Balance of the *currently logged-in* user
|
||||
const [atomCoinsEarnedToday] = useAtom(coinsEarnedTodayAtom);
|
||||
const [atomTotalEarned] = useAtom(totalEarnedAtom)
|
||||
const [atomTotalSpent] = useAtom(totalSpentAtom)
|
||||
const [atomCoinsSpentToday] = useAtom(coinsSpentTodayAtom);
|
||||
const [atomTransactionsToday] = useAtom(transactionsTodayAtom);
|
||||
const targetUser = options?.selectedUser ? users.users.find(u => u.id === options.selectedUser) : currentUser
|
||||
|
||||
// Filter transactions for the selectd user
|
||||
const transactions = coins.transactions.filter(t => t.userId === user?.id)
|
||||
const transactions = useMemo(() => {
|
||||
return allCoinsData.transactions.filter(t => t.userId === targetUser?.id);
|
||||
}, [allCoinsData, targetUser?.id]);
|
||||
|
||||
const [balance] = useAtom(coinsBalanceAtom)
|
||||
const [coinsEarnedToday] = useAtom(coinsEarnedTodayAtom)
|
||||
const [totalEarned] = useAtom(totalEarnedAtom)
|
||||
const [totalSpent] = useAtom(totalSpentAtom)
|
||||
const [coinsSpentToday] = useAtom(coinsSpentTodayAtom)
|
||||
const [transactionsToday] = useAtom(transactionsTodayAtom)
|
||||
const timezone = settings.system.timezone;
|
||||
const [coinsEarnedToday, setCoinsEarnedToday] = useState(0);
|
||||
const [totalEarned, setTotalEarned] = useState(0);
|
||||
const [totalSpent, setTotalSpent] = useState(0);
|
||||
const [coinsSpentToday, setCoinsSpentToday] = useState(0);
|
||||
const [transactionsToday, setTransactionsToday] = useState<number>(0);
|
||||
const [balance, setBalance] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
// Calculate other metrics
|
||||
if (targetUser?.id && targetUser.id === currentUser?.id) {
|
||||
// If the target user is the currently logged-in user, use the derived atom's value
|
||||
setCoinsEarnedToday(atomCoinsEarnedToday);
|
||||
setTotalEarned(atomTotalEarned);
|
||||
setTotalSpent(atomTotalSpent);
|
||||
setCoinsSpentToday(atomCoinsSpentToday);
|
||||
setTransactionsToday(atomTransactionsToday);
|
||||
setBalance(loggedInUserBalance);
|
||||
} else if (targetUser?.id) {
|
||||
// If an admin is viewing another user, calculate their metrics manually
|
||||
setCoinsEarnedToday(calculateCoinsEarnedToday(transactions, timezone));
|
||||
setTotalEarned(calculateTotalEarned(transactions));
|
||||
setTotalSpent(calculateTotalSpent(transactions));
|
||||
setCoinsSpentToday(calculateCoinsSpentToday(transactions, timezone));
|
||||
setTransactionsToday(calculateTransactionsToday(transactions, timezone));
|
||||
setBalance(transactions.reduce((acc, t) => acc + t.amount, 0));
|
||||
}
|
||||
}, [
|
||||
targetUser?.id,
|
||||
currentUser?.id,
|
||||
transactions, // Memoized: depends on allCoinsData and targetUser?.id
|
||||
timezone,
|
||||
loggedInUserBalance,
|
||||
atomCoinsEarnedToday,
|
||||
atomTotalEarned,
|
||||
atomTotalSpent,
|
||||
atomCoinsSpentToday,
|
||||
atomTransactionsToday,
|
||||
]);
|
||||
|
||||
const add = async (amount: number, description: string, note?: string) => {
|
||||
if (!handlePermissionCheck(currentUser, 'coins', 'write', tCommon)) return null
|
||||
@@ -91,7 +128,7 @@ export function useCoins(options?: { selectedUser?: string }) {
|
||||
description,
|
||||
type: 'MANUAL_ADJUSTMENT',
|
||||
note,
|
||||
userId: user?.id
|
||||
userId: targetUser?.id
|
||||
})
|
||||
setCoins(data)
|
||||
toast({ title: t("successTitle"), description: t("addedCoinsDescription", { amount }) })
|
||||
@@ -121,7 +158,7 @@ export function useCoins(options?: { selectedUser?: string }) {
|
||||
description,
|
||||
type: 'MANUAL_ADJUSTMENT',
|
||||
note,
|
||||
userId: user?.id
|
||||
userId: targetUser?.id
|
||||
})
|
||||
setCoins(data)
|
||||
toast({ title: t("successTitle"), description: t("removedCoinsDescription", { amount: numAmount }) })
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useAtom, atom } from 'jotai'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { habitsAtom, coinsAtom, settingsAtom, usersAtom, habitFreqMapAtom } from '@/lib/atoms'
|
||||
import { habitsAtom, coinsAtom, settingsAtom, usersAtom, habitFreqMapAtom, currentUserAtom } from '@/lib/atoms'
|
||||
import { addCoins, removeCoins, saveHabitsData } from '@/app/actions/data'
|
||||
import { Habit, Permission, SafeUser, User } from '@/lib/types'
|
||||
import { toast } from '@/hooks/use-toast'
|
||||
@@ -20,10 +20,10 @@ import {
|
||||
} from '@/lib/utils'
|
||||
import { ToastAction } from '@/components/ui/toast'
|
||||
import { Undo2 } from 'lucide-react'
|
||||
import { useHelpers } from '@/lib/client-helpers'
|
||||
|
||||
|
||||
function handlePermissionCheck(
|
||||
user: SafeUser | undefined,
|
||||
user: SafeUser | User | undefined,
|
||||
resource: 'habit' | 'wishlist' | 'coins',
|
||||
action: 'write' | 'interact',
|
||||
tCommon: (key: string, values?: Record<string, any>) => string
|
||||
@@ -54,7 +54,7 @@ export function useHabits() {
|
||||
const t = useTranslations('useHabits');
|
||||
const tCommon = useTranslations('Common');
|
||||
const [usersData] = useAtom(usersAtom)
|
||||
const { currentUser } = useHelpers()
|
||||
const [currentUser] = useAtom(currentUserAtom)
|
||||
const [habitsData, setHabitsData] = useAtom(habitsAtom)
|
||||
const [coins, setCoins] = useAtom(coinsAtom)
|
||||
const [settings] = useAtom(settingsAtom)
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { useAtom } from 'jotai'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { wishlistAtom, coinsAtom } from '@/lib/atoms'
|
||||
import { wishlistAtom, coinsAtom, currentUserAtom } from '@/lib/atoms'
|
||||
import { saveWishlistItems, removeCoins } from '@/app/actions/data'
|
||||
import { toast } from '@/hooks/use-toast'
|
||||
import { WishlistItemType } from '@/lib/types'
|
||||
import { WishlistItemType, User, SafeUser } from '@/lib/types'
|
||||
import { celebrations } from '@/utils/celebrations'
|
||||
import { checkPermission } from '@/lib/utils'
|
||||
import { useHelpers } from '@/lib/client-helpers'
|
||||
import { useCoins } from './useCoins'
|
||||
|
||||
function handlePermissionCheck(
|
||||
user: any, // Consider using a more specific type like SafeUser | User | undefined
|
||||
user: User | SafeUser | undefined,
|
||||
resource: 'habit' | 'wishlist' | 'coins',
|
||||
action: 'write' | 'interact',
|
||||
tCommon: (key: string, values?: Record<string, any>) => string
|
||||
@@ -39,7 +38,7 @@ function handlePermissionCheck(
|
||||
export function useWishlist() {
|
||||
const t = useTranslations('useWishlist');
|
||||
const tCommon = useTranslations('Common');
|
||||
const { currentUser: user } = useHelpers()
|
||||
const [user] = useAtom(currentUserAtom)
|
||||
const [wishlist, setWishlist] = useAtom(wishlistAtom)
|
||||
const [coins, setCoins] = useAtom(coinsAtom)
|
||||
const { balance } = useCoins()
|
||||
|
||||
23
lib/atoms.ts
23
lib/atoms.ts
@@ -22,7 +22,8 @@ import {
|
||||
getDefaultSettings,
|
||||
getDefaultUsersData,
|
||||
getDefaultWishlistData,
|
||||
Habit
|
||||
Habit,
|
||||
UserId
|
||||
} from "./types";
|
||||
|
||||
export interface BrowserSettings {
|
||||
@@ -77,10 +78,26 @@ export const transactionsTodayAtom = atom((get) => {
|
||||
return calculateTransactionsToday(coins.transactions, settings.system.timezone);
|
||||
});
|
||||
|
||||
// Derived atom for current balance from all transactions
|
||||
// Atom to store the current logged-in user's ID.
|
||||
// This should be set by your application when the user session is available.
|
||||
export const currentUserIdAtom = atom<UserId | undefined>(undefined);
|
||||
|
||||
export const currentUserAtom = atom((get) => {
|
||||
const currentUserId = get(currentUserIdAtom);
|
||||
const users = get(usersAtom);
|
||||
return users.users.find(user => user.id === currentUserId);
|
||||
})
|
||||
|
||||
// Derived atom for current balance for the logged-in user
|
||||
export const coinsBalanceAtom = atom((get) => {
|
||||
const loggedInUserId = get(currentUserIdAtom);
|
||||
if (!loggedInUserId) {
|
||||
return 0; // No user logged in or ID not set, so balance is 0
|
||||
}
|
||||
const coins = get(coinsAtom);
|
||||
return coins.transactions.reduce((sum, transaction) => sum + transaction.amount, 0);
|
||||
return coins.transactions
|
||||
.filter(transaction => transaction.userId === loggedInUserId)
|
||||
.reduce((sum, transaction) => sum + transaction.amount, 0);
|
||||
});
|
||||
|
||||
/* transient atoms */
|
||||
|
||||
19
lib/utils.ts
19
lib/utils.ts
@@ -2,7 +2,7 @@ import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import { DateTime, DateTimeFormatOptions } from "luxon"
|
||||
import { datetime, RRule } from 'rrule'
|
||||
import { Freq, Habit, CoinTransaction, Permission, ParsedFrequencyResult, ParsedResultType } from '@/lib/types'
|
||||
import { Freq, Habit, CoinTransaction, Permission, ParsedFrequencyResult, ParsedResultType, User } from '@/lib/types'
|
||||
import { DUE_MAP, INITIAL_DUE, RECURRENCE_RULE_MAP } from "./constants"
|
||||
import * as chrono from 'chrono-node'
|
||||
import _ from "lodash"
|
||||
@@ -464,3 +464,20 @@ export function checkPermission(
|
||||
export function uuid() {
|
||||
return uuidv4()
|
||||
}
|
||||
|
||||
export function hasPermission(
|
||||
currentUser: User | undefined,
|
||||
resource: 'habit' | 'wishlist' | 'coins',
|
||||
action: 'write' | 'interact'
|
||||
): boolean {
|
||||
// If no current user, no permissions.
|
||||
if (!currentUser) {
|
||||
return false;
|
||||
}
|
||||
// If user is admin, they have all permissions.
|
||||
if (currentUser.isAdmin) {
|
||||
return true;
|
||||
}
|
||||
// Otherwise, check specific permissions.
|
||||
return checkPermission(currentUser.permissions, resource, action);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "habittrove",
|
||||
"version": "0.2.19",
|
||||
"version": "0.2.20",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
|
||||
Reference in New Issue
Block a user