From 3ac311c3fd1aa7ac904e94b2a2c04317d1e4acac Mon Sep 17 00:00:00 2001 From: Doh Date: Sun, 25 May 2025 20:27:46 -0400 Subject: [PATCH 1/2] add cover image in README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ecd0cc1..0411b67 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # HabitTrove +![cover](https://github.com/user-attachments/assets/b63e98b4-64ae-49c7-ae7e-21ef76c04a5a) + HabitTrove is a gamified habit tracking application that helps you build and maintain positive habits by rewarding you with coins, which you can use to exchange for rewards. > **⚠️ Important:** HabitTrove is currently in beta. Please regularly backup your `data/` directory to prevent any potential data loss. From 42c8d14d6d8707fdf7754b6f98416c46def17611 Mon Sep 17 00:00:00 2001 From: Doh Date: Sun, 25 May 2025 20:33:08 -0400 Subject: [PATCH 2/2] fix emoji picker and about modal (#146) --- CHANGELOG.md | 7 ++++ components/AboutModal.tsx | 5 +-- components/AddEditHabitModal.tsx | 54 ++++++------------------- components/AddEditWishlistItemModal.tsx | 46 +++++++-------------- components/ClientWrapper.tsx | 9 ++++- components/EmojiPickerButton.tsx | 51 +++++++++++++++++++++++ components/Header.tsx | 1 - components/Navigation.tsx | 3 -- components/Profile.tsx | 48 +++++++++++----------- components/ui/dialog.tsx | 2 +- lib/atoms.ts | 1 + package.json | 2 +- 12 files changed, 122 insertions(+), 107 deletions(-) create mode 100644 components/EmojiPickerButton.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d0e7a0..d2f35bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## Version 0.2.17 + +### Fixed + +* fix emoji selector (#142) +* fix about modal (#145) + ## Version 0.2.16 ### Improved diff --git a/components/AboutModal.tsx b/components/AboutModal.tsx index 7422dfe..472f557 100644 --- a/components/AboutModal.tsx +++ b/components/AboutModal.tsx @@ -11,17 +11,16 @@ import ChangelogModal from "./ChangelogModal" import { useState } from "react" interface AboutModalProps { - isOpen: boolean onClose: () => void } -export default function AboutModal({ isOpen, onClose }: AboutModalProps) { +export default function AboutModal({ onClose }: AboutModalProps) { const t = useTranslations('AboutModal') const version = packageJson.version const [changelogOpen, setChangelogOpen] = useState(false) return ( - + diff --git a/components/AddEditHabitModal.tsx b/components/AddEditHabitModal.tsx index 11d6b41..37f6739 100644 --- a/components/AddEditHabitModal.tsx +++ b/components/AddEditHabitModal.tsx @@ -8,26 +8,16 @@ import { settingsAtom, browserSettingsAtom, usersAtom } from '@/lib/atoms' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog' import { Button } from '@/components/ui/button' import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar' -import { Switch } from '@/components/ui/switch' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Textarea } from '@/components/ui/textarea' -import { Info, SmilePlus, Zap } from 'lucide-react' +import { Zap } from 'lucide-react' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' -import data from '@emoji-mart/data' -import Picker from '@emoji-mart/react' import { Habit, SafeUser } from '@/lib/types' +import EmojiPickerButton from './EmojiPickerButton' import { convertHumanReadableFrequencyToMachineReadable, convertMachineReadableFrequencyToHumanReadable, d2s, d2t, serializeRRule } from '@/lib/utils' import { INITIAL_DUE, INITIAL_RECURRENCE_RULE, QUICK_DATES, RECURRENCE_RULE_MAP, MAX_COIN_LIMIT } from '@/lib/constants' -import * as chrono from 'chrono-node'; import { DateTime } from 'luxon' -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select" import { useHelpers } from '@/lib/client-helpers' interface AddEditHabitModalProps { @@ -46,9 +36,9 @@ export default function AddEditHabitModal({ onClose, onSave, habit, isTask }: Ad const [targetCompletions, setTargetCompletions] = useState(habit?.targetCompletions || 1) const isRecurRule = !isTask // Initialize ruleText with the actual frequency string or default, not the display text - const initialRuleText = habit?.frequency ? convertMachineReadableFrequencyToHumanReadable({ + const initialRuleText = habit?.frequency ? convertMachineReadableFrequencyToHumanReadable({ frequency: habit.frequency, - isRecurRule, + isRecurRule, timezone: settings.system.timezone }) : (isRecurRule ? INITIAL_RECURRENCE_RULE : INITIAL_DUE); const [ruleText, setRuleText] = useState(initialRuleText) @@ -119,33 +109,15 @@ export default function AddEditHabitModal({ onClose, onSave, habit, isTask }: Ad onChange={(e) => setName(e.target.value)} required /> - - - - - - { - setName(prev => { - // Add space before emoji if there isn't one already - const space = prev.length > 0 && !prev.endsWith(' ') ? ' ' : ''; - return `${prev}${space}${emoji.native}`; - }) - // Focus back on input after selection - const input = document.getElementById('name') as HTMLInputElement - input?.focus() - }} - /> - - + { + setName(prev => { + const space = prev.length > 0 && !prev.endsWith(' ') ? ' ' : ''; + return `${prev}${space}${emoji}`; + }) + }} + />
diff --git a/components/AddEditWishlistItemModal.tsx b/components/AddEditWishlistItemModal.tsx index 75a609e..bf4a0a9 100644 --- a/components/AddEditWishlistItemModal.tsx +++ b/components/AddEditWishlistItemModal.tsx @@ -9,12 +9,8 @@ import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Textarea } from '@/components/ui/textarea' -import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' -import { SmilePlus, Info } from 'lucide-react' -import data from '@emoji-mart/data' -import Picker from '@emoji-mart/react' import { WishlistItemType } from '@/lib/types' +import EmojiPickerButton from './EmojiPickerButton' import { MAX_COIN_LIMIT } from '@/lib/constants' interface AddEditWishlistItemModalProps { @@ -114,7 +110,7 @@ export default function AddEditWishlistItemModal({ } else { addWishlistItem(itemData) } - + setIsOpen(false) setEditingItem(null) } @@ -139,29 +135,15 @@ export default function AddEditWishlistItemModal({ className="flex-1" required /> - - - - - - { - setName(prev => `${prev}${emoji.native}`) - // Focus back on input after selection - const input = document.getElementById('name') as HTMLInputElement - input?.focus() - }} - /> - - + { + setName(prev => { + const space = prev.length > 0 && !prev.endsWith(' ') ? ' ' : ''; + return `${prev}${space}${emoji}`; + }) + }} + />
@@ -296,13 +278,13 @@ export default function AddEditWishlistItemModal({ { - setSelectedUserIds(prev => + setSelectedUserIds(prev => prev.includes(user.id) ? prev.filter(id => id !== user.id) : [...prev, user.id] diff --git a/components/ClientWrapper.tsx b/components/ClientWrapper.tsx index 02cb251..562f81f 100644 --- a/components/ClientWrapper.tsx +++ b/components/ClientWrapper.tsx @@ -2,14 +2,16 @@ import { ReactNode, useEffect } from 'react' import { useAtom } from 'jotai' -import { pomodoroAtom, userSelectAtom } from '@/lib/atoms' +import { aboutOpenAtom, pomodoroAtom, userSelectAtom } from '@/lib/atoms' import PomodoroTimer from './PomodoroTimer' import UserSelectModal from './UserSelectModal' import { useSession } from 'next-auth/react' +import AboutModal from './AboutModal' export default function ClientWrapper({ children }: { children: ReactNode }) { const [pomo] = useAtom(pomodoroAtom) const [userSelect, setUserSelect] = useAtom(userSelectAtom) + const [aboutOpen, setAboutOpen] = useAtom(aboutOpenAtom) const { data: session, status } = useSession() const currentUserId = session?.user.id @@ -27,7 +29,10 @@ export default function ClientWrapper({ children }: { children: ReactNode }) { )} {userSelect && ( - setUserSelect(false)}/> + setUserSelect(false)} /> + )} + {aboutOpen && ( + setAboutOpen(false)} /> )} ) diff --git a/components/EmojiPickerButton.tsx b/components/EmojiPickerButton.tsx new file mode 100644 index 0000000..04fe7a7 --- /dev/null +++ b/components/EmojiPickerButton.tsx @@ -0,0 +1,51 @@ +'use client' + +import { useState } from 'react' +import { Button } from '@/components/ui/button' +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' +import { SmilePlus } from 'lucide-react' +import data from '@emoji-mart/data' +import Picker from '@emoji-mart/react' + +interface EmojiPickerButtonProps { + onEmojiSelect: (emoji: string) => void + inputIdToFocus?: string // Optional: ID of the input to focus after selection +} + +export default function EmojiPickerButton({ onEmojiSelect, inputIdToFocus }: EmojiPickerButtonProps) { + const [isEmojiPickerOpen, setIsEmojiPickerOpen] = useState(false) + + return ( + + + + + { + if (inputIdToFocus) { + event.preventDefault(); + const input = document.getElementById(inputIdToFocus) as HTMLInputElement; + input?.focus(); + } + }} + > + { + onEmojiSelect(emoji.native); + setIsEmojiPickerOpen(false); + // Focus is handled by onCloseAutoFocus + }} + /> + + + ) +} diff --git a/components/Header.tsx b/components/Header.tsx index fcbdd60..6028ab1 100644 --- a/components/Header.tsx +++ b/components/Header.tsx @@ -16,7 +16,6 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' -import AboutModal from './AboutModal' import Link from 'next/link' import dynamic from 'next/dynamic' import { Profile } from './Profile' diff --git a/components/Navigation.tsx b/components/Navigation.tsx index 1b74ed5..7c1fa02 100644 --- a/components/Navigation.tsx +++ b/components/Navigation.tsx @@ -6,7 +6,6 @@ import { useAtom } from 'jotai' import { browserSettingsAtom } from '@/lib/atoms' import { useEffect, useState } from 'react' import { useTranslations } from 'next-intl' -import AboutModal from './AboutModal' import { HabitIcon, TaskIcon } from '@/lib/constants' import { useHelpers } from '@/lib/client-helpers' @@ -71,7 +70,6 @@ export default function Navigation({ className, viewPort }: NavigationProps) { ))}
- setShowAbout(false)} /> ) } @@ -97,7 +95,6 @@ export default function Navigation({ className, viewPort }: NavigationProps) { - setShowAbout(false)} /> ) } diff --git a/components/Profile.tsx b/components/Profile.tsx index 0f326fb..fac72df 100644 --- a/components/Profile.tsx +++ b/components/Profile.tsx @@ -8,8 +8,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from './ui/dialog' import UserForm from './UserForm' import Link from "next/link" import { useAtom } from "jotai" -import { settingsAtom, userSelectAtom } from "@/lib/atoms" -import AboutModal from "./AboutModal" +import { aboutOpenAtom, settingsAtom, userSelectAtom } from "@/lib/atoms" import { useEffect, useState } from "react" import { useTheme } from "next-themes" import { signOut } from "@/app/actions/user" @@ -22,7 +21,7 @@ export function Profile() { const [settings] = useAtom(settingsAtom) const [userSelect, setUserSelect] = useAtom(userSelectAtom) const [isEditing, setIsEditing] = useState(false) - const [showAbout, setShowAbout] = useState(false) + const [aboutOpen, setAboutOpen] = useAtom(aboutOpenAtom) const { theme, setTheme } = useTheme() const { currentUser: user } = useHelpers() const [open, setOpen] = useState(false) @@ -111,27 +110,32 @@ export function Profile() { - - - {t('settingsLink')} - +
+
+ + + {t('settingsLink')} + +
+
- - + { + setOpen(false); // Close the dropdown + setAboutOpen(true); // Open the about modal + }}> +
+
+ + {t('aboutButton')} +
+
-
-
+
+
{t('themeLabel')}
@@ -169,8 +173,6 @@ export function Profile() { - setShowAbout(false)} /> - {/* Add the UserForm dialog */} {isEditing && user && ( setIsEditing(false)}> diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx index 1647513..afbac43 100644 --- a/components/ui/dialog.tsx +++ b/components/ui/dialog.tsx @@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef< ({ }) export const userSelectAtom = atom(false) +export const aboutOpenAtom = atom(false) // Derived atom for completion cache export const completionCacheAtom = atom((get) => { diff --git a/package.json b/package.json index 271dd22..17c9451 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "habittrove", - "version": "0.2.16", + "version": "0.2.17", "private": true, "scripts": { "dev": "next dev --turbopack",