Merge Tag v0.2.14

This commit is contained in:
2025-05-25 17:41:03 +02:00
15 changed files with 1059 additions and 78 deletions

View File

@@ -2,6 +2,12 @@
## Version 0.2.13
### Added
* support deleting user (#93)
## Version 0.2.13
### Fixed
* fix responsive design on mobile (#134)

View File

@@ -485,21 +485,80 @@ export async function updateUserPassword(userId: string, newPassword?: string):
}
export async function deleteUser(userId: string): Promise<void> {
const data = await loadUsersData()
const userIndex = data.users.findIndex(user => user.id === userId)
// Load all necessary data
const wishlistData = await loadData<WishlistData>('wishlist')
const habitsData = await loadData<HabitsData>('habits')
const coinsData = await loadData<CoinsData>('coins')
const authData = await loadUsersData()
// Process Wishlist Data
const updatedWishlistItems = wishlistData.items.reduce((acc, item) => {
if (item.userIds?.includes(userId)) {
if (item.userIds.length === 1) {
// Remove item if this is the only user
return acc
} else {
// Remove userId from item's userIds
acc.push({
...item,
userIds: item.userIds.filter(id => id !== userId)
})
}
} else {
// Keep item as is
acc.push(item)
}
return acc
}, [] as WishlistItemType[])
wishlistData.items = updatedWishlistItems
await saveData('wishlist', wishlistData)
// Process Habits Data
const updatedHabits = habitsData.habits.reduce((acc, habit) => {
if (habit.userIds?.includes(userId)) {
if (habit.userIds.length === 1) {
// Remove habit if this is the only user
return acc
} else {
// Remove userId from habit's userIds
acc.push({
...habit,
userIds: habit.userIds.filter(id => id !== userId)
})
}
} else {
// Keep habit as is
acc.push(habit)
}
return acc
}, [] as HabitsData['habits'])
habitsData.habits = updatedHabits
await saveData('habits', habitsData)
// Process Coins Data
coinsData.transactions = coinsData.transactions.filter(
transaction => transaction.userId !== userId
)
// Recalculate balance
coinsData.balance = coinsData.transactions.reduce(
(sum, transaction) => sum + transaction.amount,
0
)
await saveData('coins', coinsData)
// Delete User from Auth Data
const userIndex = authData.users.findIndex(user => user.id === userId)
if (userIndex === -1) {
throw new Error('User not found')
}
const newData: UserData = {
users: [
...data.users.slice(0, userIndex),
...data.users.slice(userIndex + 1)
]
}
authData.users = [
...authData.users.slice(0, userIndex),
...authData.users.slice(userIndex + 1)
]
await saveUsersData(newData)
await saveUsersData(authData)
}
export async function updateLastNotificationReadTimestamp(userId: string, timestamp: string): Promise<void> {

View File

@@ -0,0 +1,50 @@
import { NextResponse } from 'next/server'
import { auth } from '@/auth'
import { deleteUser } from '@/app/actions/data'
import { getCurrentUser } from '@/lib/server-helpers'
export async function POST(req: Request) {
try {
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const currentUserId = session.user.id
const currentUser = await getCurrentUser()
if (!currentUser) {
// This case should ideally not happen if session.user.id exists,
// but as a safeguard:
return NextResponse.json({ error: 'Unauthorized: User not found in system' }, { status: 401 })
}
let userIdToDelete: string
try {
const body = await req.json()
userIdToDelete = body.userId
} catch (error) {
return NextResponse.json({ error: 'Invalid request body: Could not parse JSON.' }, { status: 400 })
}
if (!userIdToDelete) {
return NextResponse.json({ error: 'Bad Request: userId is required' }, { status: 400 })
}
// Security Check: Users can only delete their own account unless they are an admin.
if (!currentUser.isAdmin && userIdToDelete !== currentUserId) {
return NextResponse.json({ error: 'Forbidden: You do not have permission to delete this user.' }, { status: 403 })
}
await deleteUser(userIdToDelete)
return NextResponse.json({ message: 'User deleted successfully' }, { status: 200 })
} catch (error) {
console.error('Error deleting user:', error)
if (error instanceof Error && error.message === 'User not found') {
return NextResponse.json({ error: 'User not found' }, { status: 404 })
}
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 })
}
}

View File

@@ -15,19 +15,28 @@ import { serverSettingsAtom, settingsAtom } from '@/lib/atoms';
import { Settings, WeekDay } from '@/lib/types';
import { useAtom } from 'jotai';
import { Info } from 'lucide-react'; // Import Info icon
import { useSession } from 'next-auth/react'; // signOut removed
import { useTranslations } from 'next-intl';
import { useRouter } from 'next/navigation';
import { saveSettings } from '../actions/data';
// AlertDialog components and useState removed
// Trash2 icon removed
export default function SettingsPage() {
const t = useTranslations('SettingsPage');
// tWarning removed
const [settings, setSettings] = useAtom(settingsAtom);
const [serverSettings] = useAtom(serverSettingsAtom);
const { data: session } = useSession();
const router = useRouter();
// showConfirmDialog and isDeleting states removed
const updateSettings = async (newSettings: Settings) => {
await saveSettings(newSettings)
setSettings(newSettings)
}
// handleDeleteAccount function removed
if (!settings) return null
@@ -228,6 +237,8 @@ export default function SettingsPage() {
</CardContent>
</Card>
{/* Danger Zone Card Removed */}
</div >
</>
)

View File

@@ -1,6 +1,17 @@
'use client';
import { signIn } from '@/app/actions/user';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { toast } from '@/hooks/use-toast';
import { usersAtom } from '@/lib/atoms';
import { useHelpers } from '@/lib/client-helpers';
@@ -8,7 +19,7 @@ import { SafeUser, User } from '@/lib/types';
import { cn } from '@/lib/utils';
import { Description } from '@radix-ui/react-dialog';
import { useAtom } from 'jotai';
import { Crown, Plus, User as UserIcon, UserRoundPen } from 'lucide-react';
import { Crown, Plus, Trash2, User as UserIcon, UserRoundPen } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { useState } from 'react';
import PasswordEntryForm from './PasswordEntryForm';
@@ -21,18 +32,63 @@ function UserCard({
onSelect,
onEdit,
showEdit,
isCurrentUser
isCurrentUser,
currentLoggedInUserId, // For "don't delete self" check
onUserDeleted // Callback to update usersAtom
}: {
user: User,
onSelect: () => void,
onEdit: () => void,
showEdit: boolean,
isCurrentUser: boolean
isCurrentUser: boolean,
currentLoggedInUserId?: string,
onUserDeleted: (userId: string) => void,
}) {
const t = useTranslations('UserSelectModal');
const tWarning = useTranslations('Warning');
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
const handleDeleteUser = async () => {
setIsDeleting(true);
try {
const response = await fetch('/api/user/delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: user.id }),
});
if (response.ok) {
toast({
title: t('deleteUserSuccessTitle'),
description: t('deleteUserSuccessDescription', { username: user.username }),
});
onUserDeleted(user.id);
} else {
const errorData = await response.json();
toast({
title: t('deleteUserErrorTitle'),
description: errorData.error || t('genericError'),
variant: 'destructive',
});
}
} catch (error) {
toast({
title: t('deleteUserErrorTitle'),
description: t('networkError'),
variant: 'destructive',
});
} finally {
setIsDeleting(false);
setShowDeleteConfirm(false);
}
};
return (
<div key={user.id} className="relative group">
<button
onClick={onSelect}
disabled={isDeleting} // Disable main button while deleting this user
className={cn(
"flex flex-col items-center gap-2 p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors w-full",
isCurrentUser && "ring-2 ring-primary"
@@ -53,15 +109,56 @@ function UserCard({
</span>
</button>
{showEdit && (
<button
onClick={(e) => {
e.stopPropagation();
onEdit();
}}
className="absolute top-0 right-0 p-1 rounded-full bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 transition-colors"
>
<UserRoundPen className="h-4 w-4" />
</button>
<div className="absolute top-0 right-0 flex space-x-1">
{showEdit && (
<button
onClick={(e) => {
e.stopPropagation(); // Prevent card selection
onEdit();
}}
disabled={isDeleting}
className="p-1 rounded-full bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 transition-colors"
title={t('editUserTooltip')}
>
<UserRoundPen className="h-4 w-4" />
</button>
)}
{showEdit && user.id !== currentLoggedInUserId && (
<AlertDialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}>
<AlertDialogTrigger asChild>
<button
onClick={(e) => {
e.stopPropagation(); // Prevent card selection
setShowDeleteConfirm(true);
}}
disabled={isDeleting}
className="p-1 rounded-full bg-red-200 hover:bg-red-300 dark:bg-red-700 dark:hover:bg-red-600 transition-colors text-red-600 dark:text-red-300"
title={t('deleteUserTooltip')}
>
<Trash2 className="h-4 w-4" />
</button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{tWarning('areYouSure')}</AlertDialogTitle>
<AlertDialogDescription>
{t('deleteUserConfirmation', { username: user.username })}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={(e) => { e.stopPropagation(); setShowDeleteConfirm(false);}} disabled={isDeleting}>{tWarning('cancel')}</AlertDialogCancel>
<AlertDialogAction
onClick={(e) => { e.stopPropagation(); handleDeleteUser();}}
disabled={isDeleting}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{isDeleting ? t('deletingButtonText') : t('confirmDeleteButtonText')}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)}
</div>
)}
</div>
);
@@ -86,32 +183,36 @@ function AddUserButton({ onClick }: { onClick: () => void }) {
function UserSelectionView({
users,
currentUser,
currentUserFromHook, // Renamed to avoid confusion with map variable
onUserSelect,
onEditUser,
onCreateUser,
onUserDeleted, // Pass through the delete handler
}: {
users: User[],
currentUser?: SafeUser,
currentUserFromHook?: SafeUser,
onUserSelect: (userId: string) => void,
onEditUser: (userId: string) => void,
onCreateUser: () => void,
onUserDeleted: (userId: string) => void,
}) {
return (
<div className="grid grid-cols-3 gap-4 p-2 max-h-80 overflow-y-auto">
{users
.filter(user => user.id !== currentUser?.id)
.filter(user => user.id !== currentUserFromHook?.id) // Show other users
.map((user) => (
<UserCard
key={user.id}
user={user}
onSelect={() => onUserSelect(user.id)}
onEdit={() => onEditUser(user.id)}
showEdit={!!currentUser?.isAdmin}
isCurrentUser={false}
showEdit={!!currentUserFromHook?.isAdmin}
isCurrentUser={false} // This card isn't the currently logged-in user for switching TO
currentLoggedInUserId={currentUserFromHook?.id} // For the "don't delete self" check
onUserDeleted={onUserDeleted}
/>
))}
{currentUser?.isAdmin && <AddUserButton onClick={onCreateUser} />}
{currentUserFromHook?.isAdmin && <AddUserButton onClick={onCreateUser} />}
</div>
);
}
@@ -122,10 +223,17 @@ export default function UserSelectModal({ onClose }: { onClose: () => void }) {
const [isCreating, setIsCreating] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const [error, setError] = useState('');
const [usersData] = useAtom(usersAtom);
const [usersData, setUsersData] = useAtom(usersAtom);
const users = usersData.users;
const { currentUser } = useHelpers();
const handleUserDeleted = (userIdToDelete: string) => {
setUsersData(prevData => ({
...prevData,
users: prevData.users.filter(u => u.id !== userIdToDelete)
}));
};
const handleUserSelect = (userId: string) => {
setSelectedUser(userId);
setError('');
@@ -166,10 +274,11 @@ export default function UserSelectModal({ onClose }: { onClose: () => void }) {
{!selectedUser && !isCreating && !isEditing ? (
<UserSelectionView
users={users}
currentUser={currentUser}
currentUserFromHook={currentUser}
onUserSelect={handleUserSelect}
onEditUser={handleEditUser}
onCreateUser={handleCreateUser}
onUserDeleted={handleUserDeleted}
/>
) : isCreating || isEditing ? (
<UserForm

View File

@@ -0,0 +1,141 @@
"use client"
import * as React from "react"
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
const AlertDialog = AlertDialogPrimitive.Root
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
const AlertDialogPortal = AlertDialogPrimitive.Portal
const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
ref={ref}
/>
))
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
>(({ className, ...props }, ref) => (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
/>
</AlertDialogPortal>
))
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
const AlertDialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
AlertDialogHeader.displayName = "AlertDialogHeader"
const AlertDialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
AlertDialogFooter.displayName = "AlertDialogFooter"
const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold", className)}
{...props}
/>
))
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
AlertDialogDescription.displayName =
AlertDialogPrimitive.Description.displayName
const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action
ref={ref}
className={cn(buttonVariants(), className)}
{...props}
/>
))
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn(
buttonVariants({ variant: "outline" }),
"mt-2 sm:mt-0",
className
)}
{...props}
/>
))
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
}

View File

@@ -150,7 +150,17 @@
"selectUserTitle": "Benutzer auswählen",
"signInSuccessTitle": "Erfolgreich angemeldet",
"signInSuccessDescription": "Willkommen zurück, {username}!",
"errorInvalidPassword": "Ungültiges Passwort"
"errorInvalidPassword": "Ungültiges Passwort",
"deleteUserConfirmation": "Sind Sie sicher, dass Sie Benutzer {username} löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
"confirmDeleteButtonText": "Löschen",
"deletingButtonText": "Wird gelöscht...",
"deleteUserSuccessTitle": "Benutzer gelöscht",
"deleteUserSuccessDescription": "Benutzer {username} wurde erfolgreich gelöscht.",
"deleteUserErrorTitle": "Löschen fehlgeschlagen",
"genericError": "Ein unerwarteter Fehler ist aufgetreten.",
"networkError": "Ein Netzwerkfehler ist aufgetreten. Bitte versuchen Sie es erneut.",
"editUserTooltip": "Benutzer bearbeiten",
"deleteUserTooltip": "Benutzer löschen"
},
"CoinsManager": {
"title": "Münzverwaltung",
@@ -403,5 +413,9 @@
"addedCoinsDescription": "{amount} Münzen hinzugefügt",
"removedCoinsDescription": "{amount} Münzen entfernt",
"transactionNotFoundDescription": "Transaktion nicht gefunden"
},
"Warning": {
"areYouSure": "Sind Sie sicher?",
"cancel": "Abbrechen"
}
}

View File

@@ -150,7 +150,17 @@
"selectUserTitle": "Select User",
"signInSuccessTitle": "Signed in successfully",
"signInSuccessDescription": "Welcome back, {username}!",
"errorInvalidPassword": "invalid password"
"errorInvalidPassword": "invalid password",
"deleteUserConfirmation": "Are you sure you want to delete user {username}? This action cannot be undone.",
"confirmDeleteButtonText": "Delete",
"deletingButtonText": "Deleting...",
"deleteUserSuccessTitle": "User Deleted",
"deleteUserSuccessDescription": "User {username} has been successfully deleted.",
"deleteUserErrorTitle": "Deletion Failed",
"genericError": "An unexpected error occurred.",
"networkError": "A network error occurred. Please try again.",
"deleteUserTooltip": "Delete user",
"editUserTooltip": "Edit user"
},
"CoinsManager": {
"title": "Coins Management",
@@ -403,5 +413,9 @@
"addedCoinsDescription": "Added {amount} coins",
"removedCoinsDescription": "Removed {amount} coins",
"transactionNotFoundDescription": "Transaction not found"
},
"Warning": {
"areYouSure": "Are you sure?",
"cancel": "Cancel"
}
}

View File

@@ -150,7 +150,17 @@
"selectUserTitle": "Seleccionar usuario",
"signInSuccessTitle": "Inicio de sesión exitoso",
"signInSuccessDescription": "¡Bienvenido de nuevo, {username}!",
"errorInvalidPassword": "contraseña inválida"
"errorInvalidPassword": "contraseña inválida",
"deleteUserConfirmation": "¿Estás seguro de que quieres eliminar al usuario {username}? Esta acción no se puede deshacer.",
"confirmDeleteButtonText": "Eliminar",
"deletingButtonText": "Eliminando...",
"deleteUserSuccessTitle": "Usuario eliminado",
"deleteUserSuccessDescription": "El usuario {username} ha sido eliminado correctamente.",
"deleteUserErrorTitle": "Error al eliminar",
"genericError": "Ocurrió un error inesperado.",
"networkError": "Ocurrió un error de red. Por favor, inténtalo de nuevo.",
"editUserTooltip": "Editar usuario",
"deleteUserTooltip": "Eliminar usuario"
},
"CoinsManager": {
"title": "Gestión de monedas",
@@ -403,5 +413,9 @@
"addedCoinsDescription": "Añadidas {amount} monedas",
"removedCoinsDescription": "Quitadas {amount} monedas",
"transactionNotFoundDescription": "Transacción no encontrada"
},
"Warning": {
"areYouSure": "¿Estás seguro?",
"cancel": "Cancelar"
}
}

View File

@@ -150,7 +150,17 @@
"selectUserTitle": "Sélectionner un utilisateur",
"signInSuccessTitle": "Connecté avec succès",
"signInSuccessDescription": "Bienvenue, {username} !",
"errorInvalidPassword": "mot de passe invalide"
"errorInvalidPassword": "mot de passe invalide",
"deleteUserConfirmation": "Êtes-vous sûr de vouloir supprimer l'utilisateur {username} ? Cette action est irréversible.",
"confirmDeleteButtonText": "Supprimer",
"deletingButtonText": "Suppression en cours...",
"deleteUserSuccessTitle": "Utilisateur supprimé",
"deleteUserSuccessDescription": "L'utilisateur {username} a été supprimé avec succès.",
"deleteUserErrorTitle": "Échec de la suppression",
"genericError": "Une erreur inattendue s'est produite.",
"networkError": "Une erreur réseau s'est produite. Veuillez réessayer.",
"editUserTooltip": "Modifier l'utilisateur",
"deleteUserTooltip": "Supprimer l'utilisateur"
},
"CoinsManager": {
"title": "Gestion des pièces",
@@ -403,5 +413,9 @@
"addedCoinsDescription": "Ajouté {amount} pièces",
"removedCoinsDescription": "Retiré {amount} pièces",
"transactionNotFoundDescription": "Transaction non trouvée"
},
"Warning": {
"areYouSure": "Êtes-vous sûr ?",
"cancel": "Annuler"
}
}

View File

@@ -150,7 +150,17 @@
"selectUserTitle": "ユーザーを選択",
"signInSuccessTitle": "サインインに成功しました",
"signInSuccessDescription": "おかえりなさい、{username}さん!",
"errorInvalidPassword": "パスワードが無効です"
"errorInvalidPassword": "パスワードが無効です",
"deleteUserConfirmation": "ユーザー {username} を削除してもよろしいですか?この操作は元に戻せません。",
"confirmDeleteButtonText": "削除",
"deletingButtonText": "削除中...",
"deleteUserSuccessTitle": "ユーザーが削除されました",
"deleteUserSuccessDescription": "ユーザー {username} は正常に削除されました。",
"deleteUserErrorTitle": "削除に失敗しました",
"genericError": "予期しないエラーが発生しました。",
"networkError": "ネットワークエラーが発生しました。もう一度お試しください。",
"editUserTooltip": "ユーザーを編集",
"deleteUserTooltip": "ユーザーを削除"
},
"CoinsManager": {
"title": "コイン管理",
@@ -403,5 +413,9 @@
"addedCoinsDescription": "{amount}コインを追加しました",
"removedCoinsDescription": "{amount}コインを削除しました",
"transactionNotFoundDescription": "取引が見つかりません"
},
"Warning": {
"areYouSure": "本当によろしいですか?",
"cancel": "キャンセル"
}
}

View File

@@ -150,7 +150,17 @@
"selectUserTitle": "Выбрать пользователя",
"signInSuccessTitle": "Успешный вход",
"signInSuccessDescription": "Добро пожаловать, {username}!",
"errorInvalidPassword": "Неверный пароль"
"errorInvalidPassword": "Неверный пароль",
"deleteUserConfirmation": "Вы уверены, что хотите удалить пользователя {username}? Это действие нельзя отменить.",
"confirmDeleteButtonText": "Удалить",
"deletingButtonText": "Удаление...",
"deleteUserSuccessTitle": "Пользователь удален",
"deleteUserSuccessDescription": "Пользователь {username} успешно удален.",
"deleteUserErrorTitle": "Ошибка удаления",
"genericError": "Произошла непредвиденная ошибка.",
"networkError": "Произошла сетевая ошибка. Пожалуйста, попробуйте еще раз.",
"editUserTooltip": "Редактировать пользователя",
"deleteUserTooltip": "Удалить пользователя"
},
"CoinsManager": {
"title": "Управление монетами",
@@ -403,5 +413,9 @@
"addedCoinsDescription": "Добавлено {amount} монет",
"removedCoinsDescription": "Удалено {amount} монет",
"transactionNotFoundDescription": "Транзакция не найдена"
},
"Warning": {
"areYouSure": "Вы уверены?",
"cancel": "Отмена"
}
}

View File

@@ -150,7 +150,17 @@
"selectUserTitle": "选择用户",
"signInSuccessTitle": "登录成功",
"signInSuccessDescription": "欢迎回来,{username}",
"errorInvalidPassword": "密码错误"
"errorInvalidPassword": "密码错误",
"deleteUserConfirmation": "您确定要删除用户 {username} 吗?此操作无法撤销。",
"confirmDeleteButtonText": "删除",
"deletingButtonText": "正在删除...",
"deleteUserSuccessTitle": "用户已删除",
"deleteUserSuccessDescription": "用户 {username} 已成功删除。",
"deleteUserErrorTitle": "删除失败",
"genericError": "发生意外错误。",
"networkError": "发生网络错误。请再试一次。",
"editUserTooltip": "编辑用户",
"deleteUserTooltip": "删除用户"
},
"CoinsManager": {
"title": "金币管理",
@@ -403,5 +413,9 @@
"addedCoinsDescription": "添加了{amount}金币",
"removedCoinsDescription": "移除了{amount}金币",
"transactionNotFoundDescription": "未找到交易记录"
},
"Warning": {
"areYouSure": "您确定吗?",
"cancel": "取消"
}
}

564
package-lock.json generated
View File

@@ -1,16 +1,17 @@
{
"name": "habittrove",
"version": "0.2.11",
"version": "0.2.13",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "habittrove",
"version": "0.2.11",
"version": "0.2.13",
"dependencies": {
"@emoji-mart/data": "^1.2.1",
"@emoji-mart/react": "^1.1.1",
"@next/font": "^14.2.15",
"@radix-ui/react-alert-dialog": "^1.1.14",
"@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-context-menu": "^2.2.4",
"@radix-ui/react-dialog": "^1.1.4",
@@ -21,7 +22,7 @@
"@radix-ui/react-scroll-area": "^1.2.4",
"@radix-ui/react-select": "^2.1.4",
"@radix-ui/react-separator": "^1.1.3",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-toast": "^1.2.4",
"@radix-ui/react-tooltip": "^1.1.6",
@@ -1060,6 +1061,93 @@
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
"integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="
},
"node_modules/@radix-ui/react-alert-dialog": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.14.tgz",
"integrity": "sha512-IOZfZ3nPvN6lXpJTBCunFQPRSvK8MDgSc1FB85xnIpUKOw9en0dJj8JmCAxV7BiZdtYlUpmrQjoTFkVYtdoWzQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.2",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-dialog": "1.1.14",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-slot": "1.2.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/primitive": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz",
"integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==",
"license": "MIT"
},
"node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
"integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-context": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
"integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-primitive": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
"integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-slot": "1.2.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-arrow": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.1.tgz",
@@ -1132,6 +1220,24 @@
}
}
},
"node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
"integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
@@ -1188,24 +1294,25 @@
}
},
"node_modules/@radix-ui/react-dialog": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.4.tgz",
"integrity": "sha512-Ur7EV1IwQGCyaAuyDRiOLA5JIUZxELJljF+MbM/2NC0BYwfuRrbpS30BiQBJrVruscgUkieKkqXYDOoByaxIoA==",
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.14.tgz",
"integrity": "sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-dismissable-layer": "1.1.3",
"@radix-ui/react-focus-guards": "1.1.1",
"@radix-ui/react-focus-scope": "1.1.1",
"@radix-ui/react-id": "1.1.0",
"@radix-ui/react-portal": "1.1.3",
"@radix-ui/react-presence": "1.1.2",
"@radix-ui/react-primitive": "2.0.1",
"@radix-ui/react-slot": "1.1.1",
"@radix-ui/react-use-controllable-state": "1.1.0",
"aria-hidden": "^1.1.1",
"react-remove-scroll": "^2.6.1"
"@radix-ui/primitive": "1.1.2",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-dismissable-layer": "1.1.10",
"@radix-ui/react-focus-guards": "1.1.2",
"@radix-ui/react-focus-scope": "1.1.7",
"@radix-ui/react-id": "1.1.1",
"@radix-ui/react-portal": "1.1.9",
"@radix-ui/react-presence": "1.1.4",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-slot": "1.2.3",
"@radix-ui/react-use-controllable-state": "1.2.2",
"aria-hidden": "^1.2.4",
"react-remove-scroll": "^2.6.3"
},
"peerDependencies": {
"@types/react": "*",
@@ -1222,6 +1329,265 @@
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/primitive": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz",
"integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==",
"license": "MIT"
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
"integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-context": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
"integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": {
"version": "1.1.10",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz",
"integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.2",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-use-callback-ref": "1.1.1",
"@radix-ui/react-use-escape-keydown": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-guards": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz",
"integrity": "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-scope": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz",
"integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-use-callback-ref": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-id": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
"integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
"integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-presence": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz",
"integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
"integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-slot": "1.2.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
"integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-controllable-state": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
"integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-effect-event": "0.0.2",
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-escape-keydown": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz",
"integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-callback-ref": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-layout-effect": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
"integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-direction": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz",
@@ -1406,6 +1772,24 @@
}
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
"integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popover": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.4.tgz",
@@ -1442,6 +1826,24 @@
}
}
},
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
"integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popper": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.1.tgz",
@@ -1541,6 +1943,24 @@
}
}
},
"node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
"integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-progress": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.1.tgz",
@@ -1819,6 +2239,24 @@
}
}
},
"node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
"integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-separator": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.3.tgz",
@@ -1899,11 +2337,12 @@
}
},
"node_modules/@radix-ui/react-slot": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
"integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
@@ -1915,6 +2354,21 @@
}
}
},
"node_modules/@radix-ui/react-slot/node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
"integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-switch": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.1.2.tgz",
@@ -2009,6 +2463,24 @@
}
}
},
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
"integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
@@ -2040,6 +2512,39 @@
}
}
},
"node_modules/@radix-ui/react-use-effect-event": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
"integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-effect-event/node_modules/@radix-ui/react-use-layout-effect": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
"integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-escape-keydown": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz",
@@ -7987,15 +8492,16 @@
}
},
"node_modules/react-remove-scroll": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.2.tgz",
"integrity": "sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==",
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.0.tgz",
"integrity": "sha512-sGsQtcjMqdQyijAHytfGEELB8FufGbfXIsvUTe+NLx1GDRJCXtCFLBLUI1eyZCKXXvbEU2C6gai0PZKoIE9Vbg==",
"license": "MIT",
"dependencies": {
"react-remove-scroll-bar": "^2.3.7",
"react-style-singleton": "^2.2.1",
"react-style-singleton": "^2.2.3",
"tslib": "^2.1.0",
"use-callback-ref": "^1.3.3",
"use-sidecar": "^1.1.2"
"use-sidecar": "^1.1.3"
},
"engines": {
"node": ">=10"

View File

@@ -1,6 +1,6 @@
{
"name": "habittrove",
"version": "0.2.13",
"version": "0.2.14",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
@@ -18,6 +18,7 @@
"@emoji-mart/data": "^1.2.1",
"@emoji-mart/react": "^1.1.1",
"@next/font": "^14.2.15",
"@radix-ui/react-alert-dialog": "^1.1.14",
"@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-context-menu": "^2.2.4",
"@radix-ui/react-dialog": "^1.1.4",
@@ -28,7 +29,7 @@
"@radix-ui/react-scroll-area": "^1.2.4",
"@radix-ui/react-select": "^2.1.4",
"@radix-ui/react-separator": "^1.1.3",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-toast": "^1.2.4",
"@radix-ui/react-tooltip": "^1.1.6",