diff --git a/CHANGELOG.md b/CHANGELOG.md index 4876af5..e60bc9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## Version 0.1.17 + +### Added + +- transactions note + +### Fixed + +- coin statistics + ## Version 0.1.16 ### Fixed diff --git a/app/actions/data.ts b/app/actions/data.ts index 23e0c69..a170989 100644 --- a/app/actions/data.ts +++ b/app/actions/data.ts @@ -104,7 +104,8 @@ export async function addCoins( amount: number, description: string, type: TransactionType = 'MANUAL_ADJUSTMENT', - relatedItemId?: string + relatedItemId?: string, + note?: string ): Promise<CoinsData> { const data = await loadCoinsData() const newTransaction: CoinTransaction = { @@ -113,7 +114,8 @@ export async function addCoins( type, description, timestamp: d2t({ dateTime: getNow({}) }), - ...(relatedItemId && { relatedItemId }) + ...(relatedItemId && { relatedItemId }), + ...(note && note.trim() !== '' && { note }) } const newData: CoinsData = { @@ -144,7 +146,8 @@ export async function removeCoins( amount: number, description: string, type: TransactionType = 'MANUAL_ADJUSTMENT', - relatedItemId?: string + relatedItemId?: string, + note?: string ): Promise<CoinsData> { const data = await loadCoinsData() const newTransaction: CoinTransaction = { @@ -153,7 +156,8 @@ export async function removeCoins( type, description, timestamp: d2t({ dateTime: getNow({}) }), - ...(relatedItemId && { relatedItemId }) + ...(relatedItemId && { relatedItemId }), + ...(note && note.trim() !== '' && { note }) } const newData: CoinsData = { diff --git a/components/CoinsManager.tsx b/components/CoinsManager.tsx index d5f8e25..60ed4a5 100644 --- a/components/CoinsManager.tsx +++ b/components/CoinsManager.tsx @@ -4,7 +4,7 @@ import { useState } from 'react' import { t2d, d2s, getNow, isSameDate } from '@/lib/utils' import { Button } from '@/components/ui/button' import { FormattedNumber } from '@/components/FormattedNumber' -import { History } from 'lucide-react' +import { History, Pencil } from 'lucide-react' import EmptyState from './EmptyState' import { Input } from '@/components/ui/input' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' @@ -12,11 +12,13 @@ import { settingsAtom } from '@/lib/atoms' import Link from 'next/link' import { useAtom } from 'jotai' import { useCoins } from '@/hooks/useCoins' +import { TransactionNoteEditor } from './TransactionNoteEditor' export default function CoinsManager() { const { add, remove, + updateNote, balance, transactions, coinsEarnedToday, @@ -31,14 +33,26 @@ export default function CoinsManager() { const [pageSize, setPageSize] = useState(50) const [currentPage, setCurrentPage] = useState(1) + const [note, setNote] = useState('') + + const handleSaveNote = async (transactionId: string, note: string) => { + await updateNote(transactionId, note) + } + + const handleDeleteNote = async (transactionId: string) => { + await updateNote(transactionId, '') + } + const handleAddRemoveCoins = async () => { const numAmount = Number(amount) if (numAmount > 0) { - await add(numAmount, "Manual addition") + await add(numAmount, "Manual addition", note) setAmount(DEFAULT_AMOUNT) + setNote('') } else if (numAmount < 0) { - await remove(Math.abs(numAmount), "Manual removal") + await remove(Math.abs(numAmount), "Manual removal", note) setAmount(DEFAULT_AMOUNT) + setNote('') } } @@ -59,45 +73,51 @@ export default function CoinsManager() { </CardHeader> <CardContent> <div className="space-y-6"> - <div className="flex items-center justify-center gap-4"> - <Button - variant="outline" - size="icon" - className="h-10 w-10 text-lg" - onClick={() => setAmount(prev => (Number(prev) - 1).toString())} - > - - - </Button> - <div className="relative w-32"> - <Input - type="number" - value={amount} - onChange={(e) => setAmount(e.target.value)} - className="text-center text-xl font-medium h-12" - /> - <div className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground"> - 🪙 + <div className="flex flex-col gap-4"> + <div className="flex items-center justify-center gap-4"> + <Button + variant="outline" + size="icon" + className="h-10 w-10 text-lg" + onClick={() => setAmount(prev => (Number(prev) - 1).toString())} + > + - + </Button> + <div className="relative w-32"> + <Input + type="number" + value={amount} + onChange={(e) => setAmount(e.target.value)} + className="text-center text-xl font-medium h-12" + /> + <div className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground"> + 🪙 + </div> </div> + <Button + variant="outline" + size="icon" + className="h-10 w-10 text-lg" + onClick={() => setAmount(prev => (Number(prev) + 1).toString())} + > + + + </Button> </div> - <Button - variant="outline" - size="icon" - className="h-10 w-10 text-lg" - onClick={() => setAmount(prev => (Number(prev) + 1).toString())} - > - + - </Button> - </div> - <Button - onClick={handleAddRemoveCoins} - className="w-full h-14 transition-colors flex items-center justify-center font-medium" - variant="default" - > - <div className="flex items-center gap-2"> - {Number(amount) >= 0 ? 'Add Coins' : 'Remove Coins'} + <div className="w-full space-y-2"> + <div className="flex items-center gap-2"> + <Button + onClick={handleAddRemoveCoins} + className="flex-1 h-14 transition-colors flex items-center justify-center font-medium" + variant="default" + > + <div className="flex items-center gap-2"> + {Number(amount) >= 0 ? 'Add Coins' : 'Remove Coins'} + </div> + </Button> + </div> </div> - </Button> + </div> </div> </CardContent> </Card> @@ -236,6 +256,12 @@ export default function CoinsManager() { <p className="text-sm text-gray-500"> {d2s({ dateTime: t2d({ timestamp: transaction.timestamp, timezone: settings.system.timezone }), timezone: settings.system.timezone })} </p> + <TransactionNoteEditor + transactionId={transaction.id} + initialNote={transaction.note} + onSave={handleSaveNote} + onDelete={handleDeleteNote} + /> </div> <span className={`font-mono ${transaction.amount >= 0 diff --git a/components/Header.tsx b/components/Header.tsx index 447c06f..bafd807 100644 --- a/components/Header.tsx +++ b/components/Header.tsx @@ -38,17 +38,17 @@ export default function Header({ className }: HeaderProps) { <Link href="/coins" className="flex items-center gap-1 sm:gap-2 px-3 py-1.5 bg-white hover:bg-gray-50 dark:bg-gray-700 dark:hover:bg-gray-600 rounded-full transition-colors border border-gray-200 dark:border-gray-600"> <Coins className="h-5 w-5 text-yellow-500 dark:text-yellow-400" /> <div className="flex items-baseline gap-1 sm:gap-2"> - <FormattedNumber - amount={balance} - settings={settings} - className="text-gray-800 dark:text-gray-100 font-medium text-lg" + <FormattedNumber + amount={balance} + settings={settings} + className="text-gray-800 dark:text-gray-100 font-medium text-lg" /> {coinsEarnedToday > 0 && ( <span className="text-sm bg-green-50 dark:bg-green-900/30 text-green-700 dark:text-green-400 px-2 py-1 rounded-full border border-green-100 dark:border-green-800"> - <FormattedNumber - amount={coinsEarnedToday} - settings={settings} - className="inline" + <FormattedNumber + amount={coinsEarnedToday} + settings={settings} + className="inline" /> </span> )} diff --git a/components/TransactionNoteEditor.tsx b/components/TransactionNoteEditor.tsx new file mode 100644 index 0000000..7fe0fb2 --- /dev/null +++ b/components/TransactionNoteEditor.tsx @@ -0,0 +1,138 @@ +'use client' + +import { useState } from 'react' +import { Input } from '@/components/ui/input' +import { Button } from '@/components/ui/button' +import { Check, Loader2, Pencil, Trash2, X } from 'lucide-react' +import { toast } from '@/hooks/use-toast' + +interface TransactionNoteEditorProps { + transactionId: string + initialNote?: string + onSave: (id: string, note: string) => Promise<void> + onDelete: (id: string) => Promise<void> +} + +export function TransactionNoteEditor({ + transactionId, + initialNote = '', + onSave, + onDelete +}: TransactionNoteEditorProps) { + const [isEditing, setIsEditing] = useState(false) + const [noteText, setNoteText] = useState(initialNote) + const [isSaving, setIsSaving] = useState(false) + + const handleSave = async () => { + const trimmedNote = noteText.trim() + if (trimmedNote.length > 200) { + toast({ + title: 'Note too long', + description: 'Notes must be less than 200 characters', + variant: 'destructive' + }) + return + } + + setIsSaving(true) + try { + await onSave(transactionId, trimmedNote) + setIsEditing(false) + } catch (error) { + toast({ + title: 'Error saving note', + description: 'Please try again', + variant: 'destructive' + }) + // Revert to initial value on error + setNoteText(initialNote) + } finally { + setIsSaving(false) + } + } + + const handleDelete = async () => { + setIsSaving(true) + try { + await onDelete(transactionId) + setNoteText(initialNote) + setIsEditing(false) + } catch (error) { + toast({ + title: 'Error deleting note', + description: 'Please try again', + variant: 'destructive' + }) + } finally { + setIsSaving(false) + } + } + + if (isEditing) { + return ( + <div className="flex items-center gap-2 mt-1"> + <Input + value={noteText} + onChange={(e) => setNoteText(e.target.value)} + placeholder="Add a note..." + className="w-64" + maxLength={200} + /> + <div className="flex items-center gap-1"> + <Button + variant="ghost" + size="sm" + onClick={handleSave} + disabled={isSaving} + className="text-green-600 dark:text-green-500 hover:text-green-700 dark:hover:text-green-400 transition-colors" + title="Save note" + > + {isSaving ? <Loader2 className="h-4 w-4 animate-spin" /> : <Check className="h-4 w-4" />} + </Button> + <Button + variant="ghost" + size="sm" + onClick={() => { + setNoteText(initialNote) + setIsEditing(false) + }} + disabled={isSaving} + className="text-red-600 dark:text-red-500 hover:text-red-700 dark:hover:text-red-400 transition-colors" + title="Cancel" + > + <X className="h-4 w-4" /> + </Button> + {initialNote && ( + <Button + variant="ghost" + size="sm" + onClick={handleDelete} + disabled={isSaving} + className="text-gray-600 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-400 transition-colors" + title="Delete note" + > + {isSaving ? <Loader2 className="h-4 w-4 animate-spin" /> : <Trash2 className="h-4 w-4" />} + </Button> + )} + </div> + </div> + ) + } + + return ( + <div className="group flex items-center gap-2 mt-1"> + {noteText && ( + <span className="text-sm text-gray-500 dark:text-gray-400"> + {noteText} + </span> + )} + <button + onClick={() => setIsEditing(true)} + className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" + aria-label="Edit note" + > + <Pencil className="h-4 w-4" /> + </button> + </div> + ) +} diff --git a/hooks/useCoins.tsx b/hooks/useCoins.tsx index f9b5b18..54e611e 100644 --- a/hooks/useCoins.tsx +++ b/hooks/useCoins.tsx @@ -1,26 +1,25 @@ import { useAtom } from 'jotai' -import { - coinsAtom, - settingsAtom, +import { + coinsAtom, coinsEarnedTodayAtom, totalEarnedAtom, totalSpentAtom, coinsSpentTodayAtom, transactionsTodayAtom } from '@/lib/atoms' -import { addCoins, removeCoins } from '@/app/actions/data' +import { addCoins, removeCoins, saveCoinsData } from '@/app/actions/data' +import { CoinsData } from '@/lib/types' import { toast } from '@/hooks/use-toast' export function useCoins() { const [coins, setCoins] = useAtom(coinsAtom) - const [settings] = useAtom(settingsAtom) const [coinsEarnedToday] = useAtom(coinsEarnedTodayAtom) const [totalEarned] = useAtom(totalEarnedAtom) const [totalSpent] = useAtom(totalSpentAtom) const [coinsSpentToday] = useAtom(coinsSpentTodayAtom) const [transactionsToday] = useAtom(transactionsTodayAtom) - const add = async (amount: number, description: string) => { + const add = async (amount: number, description: string, note?: string) => { if (isNaN(amount) || amount <= 0) { toast({ title: "Invalid amount", @@ -29,13 +28,13 @@ export function useCoins() { return null } - const data = await addCoins(amount, description) + const data = await addCoins(amount, description, 'MANUAL_ADJUSTMENT', undefined, note) setCoins(data) toast({ title: "Success", description: `Added ${amount} coins` }) return data } - const remove = async (amount: number, description: string) => { + const remove = async (amount: number, description: string, note?: string) => { const numAmount = Math.abs(amount) if (isNaN(numAmount) || numAmount <= 0) { toast({ @@ -45,15 +44,45 @@ export function useCoins() { return null } - const data = await removeCoins(numAmount, description) + const data = await removeCoins(numAmount, description, 'MANUAL_ADJUSTMENT', undefined, note) setCoins(data) toast({ title: "Success", description: `Removed ${numAmount} coins` }) return data } + const updateNote = async (transactionId: string, note: string) => { + const transaction = coins.transactions.find(t => t.id === transactionId) + if (!transaction) { + toast({ + title: "Error", + description: "Transaction not found" + }) + return null + } + + const updatedTransaction = { + ...transaction, + note: note.trim() || undefined + } + + const updatedTransactions = coins.transactions.map(t => + t.id === transactionId ? updatedTransaction : t + ) + + const newData: CoinsData = { + ...coins, + transactions: updatedTransactions + } + + await saveCoinsData(newData) + setCoins(newData) + return newData + } + return { add, remove, + updateNote, balance: coins.balance, transactions: coins.transactions, coinsEarnedToday, diff --git a/lib/atoms.ts b/lib/atoms.ts index 09a99d7..9d2a861 100644 --- a/lib/atoms.ts +++ b/lib/atoms.ts @@ -5,7 +5,16 @@ import { getDefaultCoinsData, getDefaultWishlistData } from "./types"; -import { getTodayInTimezone, isSameDate, t2d } from "@/lib/utils"; +import { + getTodayInTimezone, + isSameDate, + t2d, + calculateCoinsEarnedToday, + calculateTotalEarned, + calculateTotalSpent, + calculateCoinsSpentToday, + calculateTransactionsToday +} from "@/lib/utils"; export const settingsAtom = atom(getDefaultSettings()); export const habitsAtom = atom(getDefaultHabitsData()); @@ -16,72 +25,31 @@ export const wishlistAtom = atom(getDefaultWishlistData()); export const coinsEarnedTodayAtom = atom((get) => { const coins = get(coinsAtom); const settings = get(settingsAtom); - const today = getTodayInTimezone(settings.system.timezone); - return coins.transactions - .filter(transaction => - isSameDate(t2d({ timestamp: transaction.timestamp, timezone: settings.system.timezone }), - t2d({ timestamp: today, timezone: settings.system.timezone })) - ) - .reduce((sum, transaction) => { - if (transaction.type !== 'HABIT_UNDO' && transaction.amount > 0) { - return sum + transaction.amount; - } - if (transaction.type === 'HABIT_UNDO') { - return sum - Math.abs(transaction.amount); - } - return sum; - }, 0); + return calculateCoinsEarnedToday(coins.transactions, settings.system.timezone); }); // Derived atom for total earned export const totalEarnedAtom = atom((get) => { const coins = get(coinsAtom); - return coins.transactions - .filter(t => { - if (t.type === 'HABIT_COMPLETION' && t.relatedItemId) { - return !coins.transactions.some(undoT => - undoT.type === 'HABIT_UNDO' && - undoT.relatedItemId === t.relatedItemId - ); - } - return t.amount > 0 && t.type !== 'HABIT_UNDO'; - }) - .reduce((sum, t) => sum + t.amount, 0); + return calculateTotalEarned(coins.transactions); }); // Derived atom for total spent export const totalSpentAtom = atom((get) => { const coins = get(coinsAtom); - return Math.abs( - coins.transactions - .filter(t => t.type === 'WISH_REDEMPTION' || t.type === 'MANUAL_ADJUSTMENT') - .reduce((sum, t) => sum + (t.amount < 0 ? t.amount : 0), 0) - ); + return calculateTotalSpent(coins.transactions); }); // Derived atom for coins spent today export const coinsSpentTodayAtom = atom((get) => { const coins = get(coinsAtom); const settings = get(settingsAtom); - const today = getTodayInTimezone(settings.system.timezone); - return Math.abs( - coins.transactions - .filter(t => - isSameDate(t2d({ timestamp: t.timestamp, timezone: settings.system.timezone }), - t2d({ timestamp: today, timezone: settings.system.timezone })) && - t.amount < 0 - ) - .reduce((sum, t) => sum + t.amount, 0) - ); + return calculateCoinsSpentToday(coins.transactions, settings.system.timezone); }); // Derived atom for transactions today export const transactionsTodayAtom = atom((get) => { const coins = get(coinsAtom); const settings = get(settingsAtom); - const today = getTodayInTimezone(settings.system.timezone); - return coins.transactions.filter(t => - isSameDate(t2d({ timestamp: t.timestamp, timezone: settings.system.timezone }), - t2d({ timestamp: today, timezone: settings.system.timezone })) - ).length; + return calculateTransactionsToday(coins.transactions, settings.system.timezone); }); diff --git a/lib/types.ts b/lib/types.ts index e90bdb4..f60083d 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -24,6 +24,7 @@ export interface CoinTransaction { description: string; timestamp: string; relatedItemId?: string; + note?: string; } export interface HabitsData { diff --git a/lib/utils.test.ts b/lib/utils.test.ts index 6e19faf..f4e6f92 100644 --- a/lib/utils.test.ts +++ b/lib/utils.test.ts @@ -1,5 +1,21 @@ import { expect, test, describe, beforeAll, afterAll } from "bun:test"; -import { cn, getTodayInTimezone, getNow, getNowInMilliseconds, t2d, d2t, d2s, d2sDate, d2n, isSameDate } from './utils' +import { + cn, + getTodayInTimezone, + getNow, + getNowInMilliseconds, + t2d, + d2t, + d2s, + d2sDate, + d2n, + isSameDate, + calculateCoinsEarnedToday, + calculateTotalEarned, + calculateTotalSpent, + calculateCoinsSpentToday, +} from './utils' +import { CoinTransaction } from './types' import { DateTime } from "luxon"; describe('cn utility', () => { @@ -93,4 +109,66 @@ describe('datetime utilities', () => { expect(isSameDate(date1, date3)).toBe(false) }) }) + + describe('transaction calculations', () => { + const testTransactions: CoinTransaction[] = [ + { + id: '1', + amount: 10, + type: 'HABIT_COMPLETION', + description: 'Test habit', + timestamp: '2024-01-01T12:00:00Z' + }, + { + id: '2', + amount: -5, + type: 'HABIT_UNDO', + description: 'Undo test habit', + timestamp: '2024-01-01T13:00:00Z', + relatedItemId: '1' + }, + { + id: '3', + amount: 20, + type: 'HABIT_COMPLETION', + description: 'Another habit', + timestamp: '2024-01-01T14:00:00Z' + }, + { + id: '4', + amount: -15, + type: 'WISH_REDEMPTION', + description: 'Redeemed wish', + timestamp: '2024-01-01T15:00:00Z' + }, + { + id: '5', + amount: 5, + type: 'HABIT_COMPLETION', + description: 'Yesterday habit', + timestamp: '2023-12-31T23:00:00Z' + } + ] + + test('calculateCoinsEarnedToday should calculate today\'s earnings including undos', () => { + const result = calculateCoinsEarnedToday(testTransactions, 'UTC') + expect(result).toBe(25) // 10 + 20 - 5 (including the -5 undo) + }) + + test('calculateTotalEarned should calculate lifetime earnings including undos', () => { + const result = calculateTotalEarned(testTransactions) + expect(result).toBe(30) // 10 + 20 + 5 - 5 (including the -5 undo) + }) + + test('calculateTotalSpent should calculate total spent excluding undos', () => { + const result = calculateTotalSpent(testTransactions) + expect(result).toBe(15) // Only the 15 wish redemption (excluding the 5 undo) + }) + + test('calculateCoinsSpentToday should calculate today\'s spending excluding undos', () => { + const result = calculateCoinsSpentToday(testTransactions, 'UTC') + expect(result).toBe(15) // Only the 15 wish redemption (excluding the 5 undo) + }) + + }) }) diff --git a/lib/utils.ts b/lib/utils.ts index 03bc7bc..3991342 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,7 +1,7 @@ import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" import { DateTime } from "luxon" -import { Habit } from '@/lib/types' +import { Habit, CoinTransaction } from '@/lib/types' export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) @@ -66,17 +66,17 @@ export function normalizeCompletionDate(date: string, timezone: string): string return DateTime.fromFormat(date, 'yyyy-MM-dd', { zone: timezone }).toUTC().toISO()!; } -export function getCompletionsForDate({ - habit, - date, - timezone -}: { - habit: Habit, - date: DateTime | string, - timezone: string +export function getCompletionsForDate({ + habit, + date, + timezone +}: { + habit: Habit, + date: DateTime | string, + timezone: string }): number { const dateObj = typeof date === 'string' ? DateTime.fromISO(date) : date - return habit.completions.filter((completion: string) => + return habit.completions.filter((completion: string) => isSameDate(t2d({ timestamp: completion, timezone }), dateObj) ).length } @@ -97,27 +97,79 @@ export function getCompletedHabitsForDate({ }) } -export function isHabitCompletedToday({ - habit, - timezone -}: { - habit: Habit, - timezone: string +export function isHabitCompletedToday({ + habit, + timezone +}: { + habit: Habit, + timezone: string }): boolean { const today = getTodayInTimezone(timezone) const completionsToday = getCompletionsForDate({ habit, date: today, timezone }) return completionsToday >= (habit.targetCompletions || 1) } -export function getHabitProgress({ - habit, - timezone -}: { - habit: Habit, - timezone: string +export function getHabitProgress({ + habit, + timezone +}: { + habit: Habit, + timezone: string }): number { const today = getTodayInTimezone(timezone) const completionsToday = getCompletionsForDate({ habit, date: today, timezone }) const target = habit.targetCompletions || 1 return Math.min(100, (completionsToday / target) * 100) } + +export function calculateCoinsEarnedToday(transactions: CoinTransaction[], timezone: string): number { + const today = getTodayInTimezone(timezone); + return transactions + .filter(transaction => + isSameDate(t2d({ timestamp: transaction.timestamp, timezone }), + t2d({ timestamp: today, timezone })) && + (transaction.amount > 0 || transaction.type === 'HABIT_UNDO') + ) + .reduce((sum, transaction) => sum + transaction.amount, 0); +} + +export function calculateTotalEarned(transactions: CoinTransaction[]): number { + return transactions + .filter(transaction => + transaction.amount > 0 || transaction.type === 'HABIT_UNDO' + ) + .reduce((sum, transaction) => sum + transaction.amount, 0); +} + +export function calculateTotalSpent(transactions: CoinTransaction[]): number { + return Math.abs( + transactions + .filter(transaction => + transaction.amount < 0 && + transaction.type !== 'HABIT_UNDO' + ) + .reduce((sum, transaction) => sum + transaction.amount, 0) + ); +} + +export function calculateCoinsSpentToday(transactions: CoinTransaction[], timezone: string): number { + const today = getTodayInTimezone(timezone); + return Math.abs( + transactions + .filter(transaction => + isSameDate(t2d({ timestamp: transaction.timestamp, timezone }), + t2d({ timestamp: today, timezone })) && + transaction.amount < 0 && + transaction.type !== 'HABIT_UNDO' + ) + .reduce((sum, transaction) => sum + transaction.amount, 0) + ); +} + +export function calculateTransactionsToday(transactions: CoinTransaction[], timezone: string): number { + const today = getTodayInTimezone(timezone); + return transactions.filter(t => + isSameDate(t2d({ timestamp: t.timestamp, timezone }), + t2d({ timestamp: today, timezone })) + ).length; +} diff --git a/package-lock.json b/package-lock.json index 8f4026a..5f324e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "habittrove", - "version": "0.1.12", + "version": "0.1.16", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "habittrove", - "version": "0.1.12", + "version": "0.1.16", "dependencies": { "@emoji-mart/data": "^1.2.1", "@emoji-mart/react": "^1.1.1",