diff --git a/messages/en.json b/messages/en.json
index b97d4ec..e1ad911 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -147,9 +147,8 @@
"users_page_ui_select_placeholder_role": "Select role",
"users_page_ui_select_placeholder_ban_exp": "Select time",
"users_page_ui_dialog_alert_title": "Unban this user?",
- "users_page_ui_dialog_alert_ban_title": "",
- "users_page_ui_dialog_alert_description": "Detail: \nName: {name}. \nEmail: {email}",
- "users_page_ui_dialog_alert_description_2": "Reason: {reason}. \nExpiration: {exp}",
+ "users_page_ui_dialog_alert_ban_title": "Lock this user?",
+ "users_page_ui_dialog_alert_description_title": "Detail",
"houses_page_ui_title": "Houses",
"houses_page_ui_table_header_name": "Name",
"houses_page_ui_table_header_members": "Members",
@@ -161,9 +160,12 @@
"houses_page_form_user": "User",
"houses_page_form_create_for": "Create for",
"houses_page_form_color": "Color",
+ "houses_page_ui_dialog_alert_delete_title": "Delete house: {name}?",
+ "houses_page_ui_dialog_alert_delete_description": "This action cannot be undone! It will delete all related data like: Box, Item. Please think carefully!",
"houses_page_message_create_house_success": "Created house successfully!",
"houses_page_message_house_not_found": "House not found!",
"houses_page_message_update_house_success": "Updated house successfully!",
+ "houses_page_message_delete_house_success": "Delete house successfully!",
"backend_message": [
{
"match": {
diff --git a/messages/vi.json b/messages/vi.json
index 2db8ba4..c4cd18b 100644
--- a/messages/vi.json
+++ b/messages/vi.json
@@ -149,8 +149,7 @@
"users_page_ui_select_placeholder_ban_exp": "Hãy chọn thời gian cấm",
"users_page_ui_dialog_alert_title": "Bạn muốn mở khóa người dùng này?",
"users_page_ui_dialog_alert_ban_title": "Bạn muốn khóa người dùng này?",
- "users_page_ui_dialog_alert_description": "Chi tiết: \nTên: {name}. \nEmail: {email}",
- "users_page_ui_dialog_alert_description_2": "\nLý do: {reason}. \nHiệu lực: {exp}",
+ "users_page_ui_dialog_alert_description_title": "Chi tiết",
"houses_page_ui_title": "Nhà",
"houses_page_ui_table_header_name": "Tên",
"houses_page_ui_table_header_members": "Thành viên",
@@ -162,9 +161,12 @@
"houses_page_form_user": "Người dùng",
"houses_page_form_create_for": "Tạo cho",
"houses_page_form_color": "Màu sắc",
+ "houses_page_ui_dialog_alert_delete_title": "Bạn muốn xóa nhà này: {name}?",
+ "houses_page_ui_dialog_alert_delete_description": "Thao tác này không thể hoàn tác! Nó sẽ xóa hết mọi dữ liệu liên quan như: Hộp chứa, Vật Phẩm. Xin suy tính kỹ lưỡng!",
"houses_page_message_create_house_success": "Tạo nhà thành công!",
"houses_page_message_house_not_found": "Không tìm thấy nhà này!",
"houses_page_message_update_house_success": "Cập nhật nhà thành công!",
+ "houses_page_message_delete_house_success": "Xóa nhà thành công!",
"backend_message": [
{
"match": {
diff --git a/package.json b/package.json
index be69881..2498727 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
"better-auth": "^1.4.10",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+ "html-react-parser": "^5.2.16",
"next-themes": "^0.4.6",
"prisma": "^7.1.0",
"radix-ui": "^1.4.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4f55f56..9b66164 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -9,7 +9,7 @@ importers:
.:
dependencies:
'@base-ui/react':
- specifier: ^1.1.0
+ specifier: ^1.0.0
version: 1.1.0(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@fontsource-variable/inter':
specifier: ^5.2.8
@@ -65,6 +65,9 @@ importers:
clsx:
specifier: ^2.1.1
version: 2.1.1
+ html-react-parser:
+ specifier: ^5.2.16
+ version: 5.2.16(@types/react@19.2.10)(react@19.2.4)
next-themes:
specifier: ^0.4.6
version: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -81,7 +84,7 @@ importers:
specifier: ^19.2.4
version: 19.2.4(react@19.2.4)
shadcn:
- specifier: 3.8.2
+ specifier: ^3.6.1
version: 3.8.2(@types/node@25.2.0)(hono@4.11.4)(typescript@5.9.3)
sonner:
specifier: ^2.0.7
@@ -3299,10 +3302,22 @@ packages:
resolution: {integrity: sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==}
engines: {node: '>=16.9.0'}
+ html-dom-parser@5.1.7:
+ resolution: {integrity: sha512-Sn+6S3Z8P3P12qqUm4+9wnchC3Bjc4DHp60fgnUdgeiy6e3EbECFWdrmyTBuphxJA5Is7V400+v7ct/Ix2pJFw==}
+
html-encoding-sniffer@6.0.0:
resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==}
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+ html-react-parser@5.2.16:
+ resolution: {integrity: sha512-1S6KLse1hKWOXYL/PSnZhsARJBE6eIO93CjPlDKMneO0wz8YTnzTfc9Yw4mWsCk2kcB9IrU+R0W6Rdi4N7YfJw==}
+ peerDependencies:
+ '@types/react': 0.14 || 15 || 16 || 17 || 18 || 19
+ react: 0.14 || 15 || 16 || 17 || 18 || 19
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
htmlparser2@10.1.0:
resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==}
@@ -3360,6 +3375,9 @@ packages:
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+ inline-style-parser@0.2.7:
+ resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==}
+
ipaddr.js@1.9.1:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'}
@@ -4087,6 +4105,9 @@ packages:
react-is@17.0.2:
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
+ react-property@2.0.2:
+ resolution: {integrity: sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==}
+
react-refresh@0.18.0:
resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==}
engines: {node: '>=0.10.0'}
@@ -4382,6 +4403,12 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
+ style-to-js@1.1.21:
+ resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==}
+
+ style-to-object@1.0.14:
+ resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==}
+
supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
@@ -8159,12 +8186,27 @@ snapshots:
hono@4.11.4: {}
+ html-dom-parser@5.1.7:
+ dependencies:
+ domhandler: 5.0.3
+ htmlparser2: 10.1.0
+
html-encoding-sniffer@6.0.0(@noble/hashes@2.0.1):
dependencies:
'@exodus/bytes': 1.11.0(@noble/hashes@2.0.1)
transitivePeerDependencies:
- '@noble/hashes'
+ html-react-parser@5.2.16(@types/react@19.2.10)(react@19.2.4):
+ dependencies:
+ domhandler: 5.0.3
+ html-dom-parser: 5.1.7
+ react: 19.2.4
+ react-property: 2.0.2
+ style-to-js: 1.1.21
+ optionalDependencies:
+ '@types/react': 19.2.10
+
htmlparser2@10.1.0:
dependencies:
domelementtype: 2.3.0
@@ -8223,6 +8265,8 @@ snapshots:
inherits@2.0.4: {}
+ inline-style-parser@0.2.7: {}
+
ipaddr.js@1.9.1: {}
is-arrayish@0.2.1: {}
@@ -8913,6 +8957,8 @@ snapshots:
react-is@17.0.2: {}
+ react-property@2.0.2: {}
+
react-refresh@0.18.0: {}
react-remove-scroll-bar@2.3.8(@types/react@19.2.10)(react@19.2.4):
@@ -9243,6 +9289,14 @@ snapshots:
strip-json-comments@3.1.1: {}
+ style-to-js@1.1.21:
+ dependencies:
+ style-to-object: 1.0.14
+
+ style-to-object@1.0.14:
+ dependencies:
+ inline-style-parser: 0.2.7
+
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
diff --git a/src/components/audit/audit-columns.tsx b/src/components/audit/audit-columns.tsx
index c0f94f7..acba04a 100644
--- a/src/components/audit/audit-columns.tsx
+++ b/src/components/audit/audit-columns.tsx
@@ -5,7 +5,7 @@ import { formatters } from '@utils/formatters';
import { LOG_ACTION } from '@/types/enum';
import ActionBadge from './action-badge';
-import ViewDetailAudit from './view-detail-dialog';
+import ViewDetailAudit from './view-log-detail-dialog';
export const logColumns: ColumnDef[] = [
{
diff --git a/src/components/audit/view-detail-dialog.tsx b/src/components/audit/view-log-detail-dialog.tsx
similarity index 100%
rename from src/components/audit/view-detail-dialog.tsx
rename to src/components/audit/view-log-detail-dialog.tsx
diff --git a/src/components/house/delete-house-dialog.tsx b/src/components/house/delete-house-dialog.tsx
new file mode 100644
index 0000000..668f391
--- /dev/null
+++ b/src/components/house/delete-house-dialog.tsx
@@ -0,0 +1,155 @@
+import { m } from '@/paraglide/messages';
+import { deleteHouse } from '@/service/house.api';
+import { housesQueries } from '@/service/queries';
+import { ReturnError } from '@/types/common';
+import useHasPermission from '@hooks/use-has-permission';
+import usePreventAutoFocus from '@hooks/use-prevent-auto-focus';
+import { ShieldWarningIcon, TrashIcon } from '@phosphor-icons/react';
+import { useMutation, useQueryClient } from '@tanstack/react-query';
+import { Button } from '@ui/button';
+import {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from '@ui/dialog';
+import { Label } from '@ui/label';
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from '@ui/table';
+import { Tooltip, TooltipContent, TooltipTrigger } from '@ui/tooltip';
+import parse from 'html-react-parser';
+import { useState } from 'react';
+import { toast } from 'sonner';
+import RoleBadge from '../avatar/role-badge';
+
+type DeleteHouseProps = {
+ data: OrganizationWithMembers;
+};
+
+const DeleteHouseAction = ({ data }: DeleteHouseProps) => {
+ const [_open, _setOpen] = useState(false);
+ const prevent = usePreventAutoFocus();
+ const { hasPermission, isLoading } = useHasPermission('house', 'delete');
+
+ const queryClient = useQueryClient();
+
+ const { mutate: deleteHouseMutation } = useMutation({
+ mutationFn: deleteHouse,
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: [...housesQueries.all, 'list'],
+ });
+ _setOpen(false);
+ toast.success(m.houses_page_message_delete_house_success(), {
+ richColors: true,
+ });
+ },
+ onError: (error: ReturnError) => {
+ console.error(error);
+ const code = error.code as Parameters<
+ typeof m.backend_message
+ >[0]['code'];
+ toast.error(m.backend_message({ code }), {
+ richColors: true,
+ });
+ },
+ });
+
+ const onConfirm = () => {
+ deleteHouseMutation({ data });
+ };
+
+ if (isLoading) return null;
+
+ if (hasPermission) {
+ return (
+
+ );
+ }
+
+ return null;
+};
+
+export default DeleteHouseAction;
diff --git a/src/components/house/house-column.tsx b/src/components/house/house-column.tsx
index b08f1f1..8dfaf26 100644
--- a/src/components/house/house-column.tsx
+++ b/src/components/house/house-column.tsx
@@ -1,8 +1,9 @@
import { m } from '@paraglide/messages';
import { ColumnDef } from '@tanstack/react-table';
import { formatters } from '@utils/formatters';
+import DeleteHouseAction from './delete-house-dialog';
import EditHouseAction from './edit-house-dialog';
-import ViewDetailHouse from './view-detail-dialog';
+import ViewDetailHouse from './view-house-detail-dialog';
export const houseColumns: ColumnDef[] = [
{
@@ -42,6 +43,7 @@ export const houseColumns: ColumnDef[] = [
+
);
},
diff --git a/src/components/house/view-detail-dialog.tsx b/src/components/house/view-house-detail-dialog.tsx
similarity index 100%
rename from src/components/house/view-detail-dialog.tsx
rename to src/components/house/view-house-detail-dialog.tsx
diff --git a/src/components/user/ban-user-confirm-dialog.tsx b/src/components/user/ban-user-confirm-dialog.tsx
index ee3e6de..c802f2b 100644
--- a/src/components/user/ban-user-confirm-dialog.tsx
+++ b/src/components/user/ban-user-confirm-dialog.tsx
@@ -17,7 +17,14 @@ import {
} from '@ui/dialog';
import { UserWithRole } from 'better-auth/plugins';
import { toast } from 'sonner';
-import DisplayBreakLineMessage from '../DisplayBreakLineMessage';
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from '../ui/table';
import { useBanContext } from './ban-user-dialog';
type BanConfirmProps = {
@@ -74,20 +81,49 @@ const BanUserConfirm = ({ data }: BanConfirmProps) => {
{m.users_page_ui_dialog_alert_ban_title()}
-
- {m.users_page_ui_dialog_alert_description({
- name: data.name,
- email: data.email,
- })}
- {m.users_page_ui_dialog_alert_description_2({
- reason: submitData.banReason,
- exp: m.exp_time({
- time: submitData.banExp.toString() as Parameters<
- typeof m.exp_time
- >[0]['time'],
- }),
- })}
-
+
+
+
+
+
+ {m.users_page_ui_dialog_alert_description_title()}
+
+
+
+
+
+
+ {m.users_page_ui_table_header_name()}:
+
+ {data.name}
+
+
+
+ {m.users_page_ui_table_header_email()}:
+
+ {data.email}
+
+
+
+ {m.users_page_ui_form_ban_reason()}:
+
+ {submitData.banReason}
+
+
+
+ {m.users_page_ui_form_ban_exp()}:
+
+
+ {m.exp_time({
+ time: submitData.banExp.toString() as Parameters<
+ typeof m.exp_time
+ >[0]['time'],
+ })}
+
+
+
+
+