diff --git a/CHANGELOG.md b/CHANGELOG.md index dcb7858..85772a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## Version 0.2.13 +### Added + +* support deleting user (#93) + +## Version 0.2.13 + ### Fixed * fix responsive design on mobile (#134) diff --git a/app/actions/data.ts b/app/actions/data.ts index 22614df..f4f160e 100644 --- a/app/actions/data.ts +++ b/app/actions/data.ts @@ -489,21 +489,80 @@ export async function updateUserPassword(userId: string, newPassword?: string): } export async function deleteUser(userId: string): Promise { - const data = await loadUsersData() - const userIndex = data.users.findIndex(user => user.id === userId) + // Load all necessary data + const wishlistData = await loadData('wishlist') + const habitsData = await loadData('habits') + const coinsData = await loadData('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 { diff --git a/app/api/user/delete/route.ts b/app/api/user/delete/route.ts new file mode 100644 index 0000000..7dec306 --- /dev/null +++ b/app/api/user/delete/route.ts @@ -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 }) + } +} diff --git a/app/settings/page.tsx b/app/settings/page.tsx index 5fb574a..7f9c529 100644 --- a/app/settings/page.tsx +++ b/app/settings/page.tsx @@ -19,17 +19,26 @@ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' import { Button } from '@/components/ui/button'; import { User, Info } from 'lucide-react'; // Import Info icon import { toast } from '@/hooks/use-toast' +import { useSession } from 'next-auth/react'; // signOut removed +import { useRouter } from 'next/navigation'; +// 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 @@ -230,6 +239,8 @@ export default function SettingsPage() { + + {/* Danger Zone Card Removed */} ) diff --git a/components/UserSelectModal.tsx b/components/UserSelectModal.tsx index 8339972..95bf5e9 100644 --- a/components/UserSelectModal.tsx +++ b/components/UserSelectModal.tsx @@ -5,9 +5,20 @@ import PasswordEntryForm from './PasswordEntryForm'; import UserForm from './UserForm'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from './ui/dialog'; import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar'; -import { Crown, Pencil, Plus, User as UserIcon, UserRoundPen } from 'lucide-react'; +import { Crown, Pencil, Plus, User as UserIcon, UserRoundPen, Trash2 } from 'lucide-react'; import { Input } from './ui/input'; import { Button } from './ui/button'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog" import { useAtom } from 'jotai'; import { usersAtom } from '@/lib/atoms'; import { signIn } from '@/app/actions/user'; @@ -24,18 +35,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 (
{showEdit && ( - +
+ {showEdit && ( + + )} + {showEdit && user.id !== currentLoggedInUserId && ( + + + + + + + {tWarning('areYouSure')} + + {t('deleteUserConfirmation', { username: user.username })} + + + + { e.stopPropagation(); setShowDeleteConfirm(false);}} disabled={isDeleting}>{tWarning('cancel')} + { e.stopPropagation(); handleDeleteUser();}} + disabled={isDeleting} + className="bg-destructive text-destructive-foreground hover:bg-destructive/90" + > + {isDeleting ? t('deletingButtonText') : t('confirmDeleteButtonText')} + + + + + )} +
)}
); @@ -89,32 +186,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 (
{users - .filter(user => user.id !== currentUser?.id) + .filter(user => user.id !== currentUserFromHook?.id) // Show other users .map((user) => ( 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 && } + {currentUserFromHook?.isAdmin && }
); } @@ -125,10 +226,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(''); @@ -169,10 +277,11 @@ export default function UserSelectModal({ onClose }: { onClose: () => void }) { {!selectedUser && !isCreating && !isEditing ? ( ) : isCreating || isEditing ? ( , + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)) +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogHeader.displayName = "AlertDialogHeader" + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogFooter.displayName = "AlertDialogFooter" + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/messages/de.json b/messages/de.json index e279476..39cc8e3 100644 --- a/messages/de.json +++ b/messages/de.json @@ -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" } } diff --git a/messages/en.json b/messages/en.json index 0bee614..354b364 100644 --- a/messages/en.json +++ b/messages/en.json @@ -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" } } diff --git a/messages/es.json b/messages/es.json index cda005c..ddd2a98 100644 --- a/messages/es.json +++ b/messages/es.json @@ -4,7 +4,7 @@ }, "HabitList": { "myTasks": "Mis tareas", - "myHabits": "Mis hábitos", + "myHabits": "Mis hábitos", "addTaskButton": "Añadir tarea", "addHabitButton": "Añadir hábito", "searchTasksPlaceholder": "Buscar tareas...", @@ -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", @@ -190,7 +200,7 @@ "focusLabel2": "Tú puedes", "focusLabel3": "Sigue adelante", "focusLabel4": "Hazlo", - "focusLabel5": "Haz que suceda", + "focusLabel5": "Haz que suceda", "focusLabel6": "Mantente fuerte", "focusLabel7": "Esfuérzate", "focusLabel8": "Un paso a la vez", @@ -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" } } diff --git a/messages/fr.json b/messages/fr.json index 46ad610..0c0d07e 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -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" } } diff --git a/messages/ja.json b/messages/ja.json index 9721dda..8c42432 100644 --- a/messages/ja.json +++ b/messages/ja.json @@ -53,7 +53,7 @@ "HabitContextMenuItems": { "startPomodoro": "ポモドーロを開始", "moveToToday": "今日に移動", - "moveToTomorrow": "明日に移動", + "moveToTomorrow": "明日に移動", "unpin": "ピン留めを解除", "pin": "ピン留めする", "edit": "編集", @@ -81,7 +81,7 @@ "completeLabel": "完了", "timesSuffix": "回", "rewardLabel": "報酬", - "coinsSuffix": "コイン", + "coinsSuffix": "コイン", "shareLabel": "共有", "saveChangesButton": "変更を保存", "addTaskButton": "タスクを追加", @@ -110,11 +110,11 @@ "addButton": "報酬を追加" }, "Navigation": { - "dashboard": "ダッシュボード", + "dashboard": "ダッシュボード", "tasks": "タスク", "habits": "習慣", "calendar": "カレンダー", - "wishlist": "ウィッシュリスト", + "wishlist": "ウィッシュリスト", "coins": "コイン" }, "TodayEarnedCoins": { @@ -150,7 +150,17 @@ "selectUserTitle": "ユーザーを選択", "signInSuccessTitle": "サインインに成功しました", "signInSuccessDescription": "おかえりなさい、{username}さん!", - "errorInvalidPassword": "パスワードが無効です" + "errorInvalidPassword": "パスワードが無効です", + "deleteUserConfirmation": "ユーザー {username} を削除してもよろしいですか?この操作は元に戻せません。", + "confirmDeleteButtonText": "削除", + "deletingButtonText": "削除中...", + "deleteUserSuccessTitle": "ユーザーが削除されました", + "deleteUserSuccessDescription": "ユーザー {username} は正常に削除されました。", + "deleteUserErrorTitle": "削除に失敗しました", + "genericError": "予期しないエラーが発生しました。", + "networkError": "ネットワークエラーが発生しました。もう一度お試しください。", + "editUserTooltip": "ユーザーを編集", + "deleteUserTooltip": "ユーザーを削除" }, "CoinsManager": { "title": "コイン管理", @@ -167,7 +177,7 @@ "todaysTransactionsLabel": "今日の取引数", "transactionHistoryTitle": "取引履歴", "showLabel": "表示:", - "entriesSuffix": "件", + "entriesSuffix": "件", "showingEntries": "{from} から {to} 件(全 {total} 件)", "noTransactionsTitle": "取引履歴がありません", "noTransactionsDescription": "コインを獲得または使用すると、ここに取引履歴が表示されます", @@ -215,7 +225,7 @@ "wakeLockNotSupported": "ブラウザがWake Lockをサポートしていません", "wakeLockInUse": "Wake Lockは既に使用中です", "wakeLockRequestError": "Wake Lockのリクエストエラー:", - "wakeLockReleaseError": "Wake Lockの解放エラー:" + "wakeLockReleaseError": "Wake Lockの解放エラー:" }, "HabitCalendar": { "title": "習慣カレンダー", @@ -304,7 +314,7 @@ "pleaseTryAgainDescription": "再度お試しください", "addNotePlaceholder": "メモを追加...", "saveNoteTitle": "メモを保存", - "cancelButtonTitle": "キャンセル", + "cancelButtonTitle": "キャンセル", "deleteNoteTitle": "メモを削除", "editNoteAriaLabel": "メモを編集" }, @@ -323,12 +333,12 @@ }, "PasswordEntryForm": { "notYouButton": "違うユーザー?", - "passwordLabel": "パスワード", + "passwordLabel": "パスワード", "passwordPlaceholder": "パスワードを入力", "loginErrorToastTitle": "エラー", "loginFailedErrorToastDescription": "ログインに失敗しました", "cancelButton": "キャンセル", - "loginButton": "ログイン" + "loginButton": "ログイン" }, "CompletionCountBadge": { "countCompleted": "完了 {completedCount}/{totalCount}" @@ -403,5 +413,9 @@ "addedCoinsDescription": "{amount}コインを追加しました", "removedCoinsDescription": "{amount}コインを削除しました", "transactionNotFoundDescription": "取引が見つかりません" + }, + "Warning": { + "areYouSure": "本当によろしいですか?", + "cancel": "キャンセル" } } diff --git a/messages/ru.json b/messages/ru.json index 71af062..c8b1972 100644 --- a/messages/ru.json +++ b/messages/ru.json @@ -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": "Отмена" } } diff --git a/messages/zh.json b/messages/zh.json index b7c0aca..aed71ab 100644 --- a/messages/zh.json +++ b/messages/zh.json @@ -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": "取消" } } diff --git a/package-lock.json b/package-lock.json index 760f1c4..aa6c2fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" diff --git a/package.json b/package.json index 3ece558..8e94f18 100644 --- a/package.json +++ b/package.json @@ -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",