@simple-auth-kit/cli 1.3.2 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/copy.ts +67 -10
- package/package.json +1 -1
- package/registry/admin-apps/nextjs/shared/.husky/commit-msg +1 -0
- package/registry/admin-apps/nextjs/shared/.husky/pre-commit +1 -0
- package/registry/admin-apps/nextjs/shared/.lintstagedrc.json +4 -0
- package/registry/admin-apps/nextjs/shared/.prettierrc.json +3 -0
- package/registry/admin-apps/nextjs/shared/commitlint.config.js +5 -0
- package/registry/admin-apps/nextjs/shared/eslint.config.js +28 -0
- package/registry/admin-apps/nextjs/shared/gitignore +11 -0
- package/registry/admin-apps/nextjs/variants/base/package.json +11 -2
- package/registry/admin-apps/nextjs/variants/base/src/app/(console)/audit-log/page.tsx +72 -16
- package/registry/admin-apps/nextjs/variants/base/src/app/(console)/customers/[id]/edit/page.tsx +107 -26
- package/registry/admin-apps/nextjs/variants/base/src/app/(console)/customers/[id]/page.tsx +131 -29
- package/registry/admin-apps/nextjs/variants/base/src/app/(console)/languages/[id]/edit/page.tsx +111 -25
- package/registry/admin-apps/nextjs/variants/base/src/app/(console)/languages/[id]/page.tsx +97 -22
- package/registry/admin-apps/nextjs/variants/base/src/app/(console)/permissions/[id]/edit/page.tsx +129 -46
- package/registry/admin-apps/nextjs/variants/base/src/app/(console)/permissions/[id]/page.tsx +64 -14
- package/registry/admin-apps/nextjs/variants/base/src/app/(console)/roles/[id]/page.tsx +91 -22
- package/registry/admin-apps/nextjs/variants/base/src/app/(console)/users/[id]/edit/page.tsx +94 -23
- package/registry/admin-apps/nextjs/variants/workspaces/package.json +11 -2
- package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/account/page.tsx +179 -41
- package/registry/admin-apps/react/shared/.husky/commit-msg +1 -0
- package/registry/admin-apps/react/shared/.husky/pre-commit +1 -0
- package/registry/admin-apps/react/shared/.lintstagedrc.json +4 -0
- package/registry/admin-apps/react/shared/.prettierrc.json +3 -0
- package/registry/admin-apps/react/shared/commitlint.config.js +3 -0
- package/registry/admin-apps/react/shared/eslint.config.js +38 -0
- package/registry/admin-apps/react/shared/gitignore +17 -0
- package/registry/admin-apps/react/variants/base/package.json +15 -2
- package/registry/admin-apps/react/variants/base/src/pages/CountryDetailPage.tsx +76 -19
- package/registry/admin-apps/react/variants/base/src/pages/CustomerDetailPage.tsx +110 -26
- package/registry/admin-apps/react/variants/base/src/pages/EditCountryPage.tsx +115 -29
- package/registry/admin-apps/react/variants/base/src/pages/EditCustomerPage.tsx +123 -31
- package/registry/admin-apps/react/variants/base/src/pages/EditLanguagePage.tsx +100 -24
- package/registry/admin-apps/react/variants/base/src/pages/EditRolePage.tsx +99 -26
- package/registry/admin-apps/react/variants/base/src/pages/EditUserPage.tsx +126 -31
- package/registry/admin-apps/react/variants/base/src/pages/LanguageDetailPage.tsx +75 -19
- package/registry/admin-apps/react/variants/base/src/pages/PermissionDetailPage.tsx +58 -13
- package/registry/admin-apps/react/variants/base/src/pages/PermissionsPage.tsx +132 -29
- package/registry/admin-apps/react/variants/base/src/pages/RoleDetailPage.tsx +70 -17
- package/registry/admin-apps/react/variants/base/src/pages/RolesPage.tsx +277 -59
- package/registry/admin-apps/react/variants/workspaces/package.json +15 -2
- package/registry/combos/express-drizzle/eslint.config.js +18 -0
- package/registry/combos/express-drizzle/package.json +5 -1
- package/registry/combos/express-drizzle/shared/src/request-context.ts +3 -0
- package/registry/combos/express-prisma/eslint.config.js +18 -0
- package/registry/combos/express-prisma/package.json +5 -1
- package/registry/combos/express-prisma/shared/src/request-context.ts +3 -0
- package/registry/combos/nestjs-drizzle/eslint.config.js +18 -0
- package/registry/combos/nestjs-drizzle/package.json +5 -1
- package/registry/combos/nestjs-drizzle/shared/src/auth.controller.ts +138 -33
- package/registry/combos/nestjs-drizzle/shared/src/route-tiers.ts +71 -18
- package/registry/combos/nestjs-prisma/eslint.config.js +18 -0
- package/registry/combos/nestjs-prisma/package.json +5 -1
- package/registry/combos/nestjs-prisma/shared/src/auth.controller.ts +148 -35
- package/registry/combos/nestjs-prisma/shared/src/route-tiers.ts +72 -18
- package/registry/mobile-apps/bare-rn/shared/.husky/commit-msg +1 -0
- package/registry/mobile-apps/bare-rn/shared/.husky/pre-commit +1 -0
- package/registry/mobile-apps/bare-rn/shared/.lintstagedrc.json +4 -0
- package/registry/mobile-apps/bare-rn/shared/.prettierrc.json +5 -0
- package/registry/mobile-apps/bare-rn/shared/commitlint.config.js +3 -0
- package/registry/mobile-apps/bare-rn/shared/eslint.config.js +19 -0
- package/registry/mobile-apps/bare-rn/shared/gitignore +78 -0
- package/registry/mobile-apps/bare-rn/variants/base/package.json +17 -12
- package/registry/mobile-apps/bare-rn/variants/workspaces/package.json +17 -12
- package/registry/mobile-apps/expo/shared/.husky/commit-msg +1 -0
- package/registry/mobile-apps/expo/shared/.husky/pre-commit +1 -0
- package/registry/mobile-apps/expo/shared/.lintstagedrc.json +4 -0
- package/registry/mobile-apps/expo/shared/.prettierrc.json +5 -0
- package/registry/mobile-apps/expo/shared/commitlint.config.js +3 -0
- package/registry/mobile-apps/expo/shared/eslint.config.js +9 -0
- package/registry/mobile-apps/expo/shared/gitignore +44 -0
- package/registry/mobile-apps/expo/shared/src/screens/SessionsScreen.tsx +6 -17
- package/registry/mobile-apps/expo/shared/src/store/sessionsStore.ts +35 -0
- package/registry/mobile-apps/expo/variants/base/package.json +12 -3
- package/registry/mobile-apps/expo/variants/workspaces/package.json +12 -3
- package/registry/mobile-apps/expo/variants/workspaces/src/screens/WorkspaceSelectScreen.tsx +1 -1
- package/registry.json +13 -36
- package/registry/mobile-apps/bare-rn/shared/.eslintrc.js +0 -4
- package/registry/mobile-apps/bare-rn/shared/.prettierrc.js +0 -5
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from "react";
|
|
1
|
+
import { startTransition, useCallback, useEffect, useState } from "react";
|
|
2
2
|
import { Link, useNavigate, useParams } from "react-router-dom";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
AuthApiError,
|
|
5
|
+
type CountrySummary,
|
|
6
|
+
} from "@simple-auth-kit/auth-client";
|
|
4
7
|
import { format } from "date-fns";
|
|
5
8
|
import { PencilIcon } from "lucide-react";
|
|
6
9
|
import { toast } from "sonner";
|
|
@@ -11,7 +14,13 @@ import { Breadcrumb } from "@/components/breadcrumb";
|
|
|
11
14
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
12
15
|
import { Badge } from "@/components/ui/badge";
|
|
13
16
|
import { Button, buttonVariants } from "@/components/ui/button";
|
|
14
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
Card,
|
|
19
|
+
CardContent,
|
|
20
|
+
CardDescription,
|
|
21
|
+
CardHeader,
|
|
22
|
+
CardTitle,
|
|
23
|
+
} from "@/components/ui/card";
|
|
15
24
|
import { Modal } from "@/components/ui/modal";
|
|
16
25
|
|
|
17
26
|
function apiErrorMessage(err: unknown, fallback: string): string {
|
|
@@ -52,7 +61,7 @@ export function CountryDetailPage() {
|
|
|
52
61
|
}, [id]);
|
|
53
62
|
|
|
54
63
|
useEffect(() => {
|
|
55
|
-
void load();
|
|
64
|
+
startTransition(() => void load());
|
|
56
65
|
}, [load]);
|
|
57
66
|
|
|
58
67
|
async function toggleActive() {
|
|
@@ -61,10 +70,19 @@ export function CountryDetailPage() {
|
|
|
61
70
|
try {
|
|
62
71
|
if (country.isActive) await authClient.deactivateCountry(id);
|
|
63
72
|
else await authClient.activateCountry(id);
|
|
64
|
-
setCountry((prev) =>
|
|
65
|
-
|
|
73
|
+
setCountry((prev) =>
|
|
74
|
+
prev ? { ...prev, isActive: !prev.isActive } : prev,
|
|
75
|
+
);
|
|
76
|
+
toast.success(
|
|
77
|
+
country.isActive ? "Country deactivated." : "Country activated.",
|
|
78
|
+
);
|
|
66
79
|
} catch (err) {
|
|
67
|
-
toast.error(
|
|
80
|
+
toast.error(
|
|
81
|
+
apiErrorMessage(
|
|
82
|
+
err,
|
|
83
|
+
"Couldn't change this country's status. Try again.",
|
|
84
|
+
),
|
|
85
|
+
);
|
|
68
86
|
} finally {
|
|
69
87
|
setStatusPending(false);
|
|
70
88
|
}
|
|
@@ -74,9 +92,16 @@ export function CountryDetailPage() {
|
|
|
74
92
|
|
|
75
93
|
return (
|
|
76
94
|
<div className="flex flex-col gap-4">
|
|
77
|
-
<Breadcrumb
|
|
95
|
+
<Breadcrumb
|
|
96
|
+
items={[
|
|
97
|
+
{ title: "Countries", href: "/countries" },
|
|
98
|
+
{ title: country?.name ?? "Details", href: `/countries/${id}` },
|
|
99
|
+
]}
|
|
100
|
+
/>
|
|
78
101
|
|
|
79
|
-
{loading && !country &&
|
|
102
|
+
{loading && !country && (
|
|
103
|
+
<p className="text-sm text-muted-foreground">Loading…</p>
|
|
104
|
+
)}
|
|
80
105
|
|
|
81
106
|
{country && (
|
|
82
107
|
<>
|
|
@@ -87,15 +112,25 @@ export function CountryDetailPage() {
|
|
|
87
112
|
{country.emoji} {country.name}
|
|
88
113
|
</CardTitle>
|
|
89
114
|
<CardDescription>
|
|
90
|
-
Created: {format(new Date(country.createdAt), "dd MMM yyyy")}
|
|
115
|
+
Created: {format(new Date(country.createdAt), "dd MMM yyyy")}{" "}
|
|
116
|
+
· Updated:{" "}
|
|
117
|
+
{format(new Date(country.updatedAt), "dd MMM yyyy")}
|
|
91
118
|
</CardDescription>
|
|
92
119
|
</div>
|
|
93
120
|
<div className="flex items-center gap-1.5">
|
|
94
|
-
<Badge variant={country.isActive ? "success" : "destructive"}>
|
|
121
|
+
<Badge variant={country.isActive ? "success" : "destructive"}>
|
|
122
|
+
{country.isActive ? "Active" : "Inactive"}
|
|
123
|
+
</Badge>
|
|
95
124
|
<Link
|
|
96
125
|
to={`/countries/${id}/edit`}
|
|
97
|
-
className={cn(
|
|
98
|
-
|
|
126
|
+
className={cn(
|
|
127
|
+
buttonVariants({ variant: "outline", size: "sm" }),
|
|
128
|
+
)}
|
|
129
|
+
title={
|
|
130
|
+
canManage
|
|
131
|
+
? undefined
|
|
132
|
+
: `You need the "${PERMISSIONS.countriesManage}" permission to do this.`
|
|
133
|
+
}
|
|
99
134
|
aria-disabled={!canManage}
|
|
100
135
|
onClick={(e) => !canManage && e.preventDefault()}
|
|
101
136
|
>
|
|
@@ -106,8 +141,16 @@ export function CountryDetailPage() {
|
|
|
106
141
|
</CardHeader>
|
|
107
142
|
<CardContent className="flex flex-col gap-4">
|
|
108
143
|
<Avatar className="size-16 rounded-md">
|
|
109
|
-
{country.flag &&
|
|
110
|
-
|
|
144
|
+
{country.flag && (
|
|
145
|
+
<AvatarImage
|
|
146
|
+
src={country.flag}
|
|
147
|
+
alt=""
|
|
148
|
+
className="object-cover"
|
|
149
|
+
/>
|
|
150
|
+
)}
|
|
151
|
+
<AvatarFallback className="rounded-md text-2xl">
|
|
152
|
+
{country.emoji || "?"}
|
|
153
|
+
</AvatarFallback>
|
|
111
154
|
</Avatar>
|
|
112
155
|
|
|
113
156
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
@@ -129,7 +172,11 @@ export function CountryDetailPage() {
|
|
|
129
172
|
<Button
|
|
130
173
|
variant={country.isActive ? "destructive" : "outline"}
|
|
131
174
|
disabled={!canStatus || statusPending}
|
|
132
|
-
title={
|
|
175
|
+
title={
|
|
176
|
+
canStatus
|
|
177
|
+
? undefined
|
|
178
|
+
: `You need the "${PERMISSIONS.countriesStatus}" permission to do this.`
|
|
179
|
+
}
|
|
133
180
|
onClick={() => void toggleActive()}
|
|
134
181
|
>
|
|
135
182
|
{country.isActive ? "Deactivate" : "Activate"}
|
|
@@ -138,7 +185,11 @@ export function CountryDetailPage() {
|
|
|
138
185
|
<Button
|
|
139
186
|
variant="destructive"
|
|
140
187
|
disabled={!canManage}
|
|
141
|
-
title={
|
|
188
|
+
title={
|
|
189
|
+
canManage
|
|
190
|
+
? undefined
|
|
191
|
+
: `You need the "${PERMISSIONS.countriesManage}" permission to do this.`
|
|
192
|
+
}
|
|
142
193
|
onClick={() => setConfirmingDelete(true)}
|
|
143
194
|
>
|
|
144
195
|
Delete country
|
|
@@ -181,7 +232,9 @@ function DeleteCountryDialog({
|
|
|
181
232
|
toast.success("Country deleted.");
|
|
182
233
|
onDeleted();
|
|
183
234
|
} catch (err) {
|
|
184
|
-
toast.error(
|
|
235
|
+
toast.error(
|
|
236
|
+
apiErrorMessage(err, "Couldn't delete this country. Try again."),
|
|
237
|
+
);
|
|
185
238
|
} finally {
|
|
186
239
|
setSubmitting(false);
|
|
187
240
|
}
|
|
@@ -196,7 +249,11 @@ function DeleteCountryDialog({
|
|
|
196
249
|
>
|
|
197
250
|
<div className="flex flex-col gap-4">
|
|
198
251
|
<div className="flex gap-2">
|
|
199
|
-
<Button
|
|
252
|
+
<Button
|
|
253
|
+
variant="destructive"
|
|
254
|
+
disabled={submitting}
|
|
255
|
+
onClick={() => void handleDelete()}
|
|
256
|
+
>
|
|
200
257
|
{submitting ? "Deleting…" : "Delete country"}
|
|
201
258
|
</Button>
|
|
202
259
|
<Button variant="outline" onClick={onClose}>
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from "react";
|
|
1
|
+
import { startTransition, useCallback, useEffect, useState } from "react";
|
|
2
2
|
import { Link, useNavigate, useParams } from "react-router-dom";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
AuthApiError,
|
|
5
|
+
type CustomerSummary,
|
|
6
|
+
} from "@simple-auth-kit/auth-client";
|
|
4
7
|
import { format } from "date-fns";
|
|
5
8
|
import { PencilIcon } from "lucide-react";
|
|
6
9
|
import { toast } from "sonner";
|
|
@@ -11,7 +14,13 @@ import { Breadcrumb } from "@/components/breadcrumb";
|
|
|
11
14
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
12
15
|
import { Badge } from "@/components/ui/badge";
|
|
13
16
|
import { Button, buttonVariants } from "@/components/ui/button";
|
|
14
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
Card,
|
|
19
|
+
CardContent,
|
|
20
|
+
CardDescription,
|
|
21
|
+
CardHeader,
|
|
22
|
+
CardTitle,
|
|
23
|
+
} from "@/components/ui/card";
|
|
15
24
|
import { Modal } from "@/components/ui/modal";
|
|
16
25
|
|
|
17
26
|
function apiErrorMessage(err: unknown, fallback: string): string {
|
|
@@ -68,7 +77,7 @@ export function CustomerDetailPage() {
|
|
|
68
77
|
}, [id]);
|
|
69
78
|
|
|
70
79
|
useEffect(() => {
|
|
71
|
-
void load();
|
|
80
|
+
startTransition(() => void load());
|
|
72
81
|
}, [load]);
|
|
73
82
|
|
|
74
83
|
async function toggleActive() {
|
|
@@ -77,10 +86,19 @@ export function CustomerDetailPage() {
|
|
|
77
86
|
try {
|
|
78
87
|
if (customer.isActive) await authClient.deactivateCustomer(id);
|
|
79
88
|
else await authClient.activateCustomer(id);
|
|
80
|
-
setCustomer((prev) =>
|
|
81
|
-
|
|
89
|
+
setCustomer((prev) =>
|
|
90
|
+
prev ? { ...prev, isActive: !prev.isActive } : prev,
|
|
91
|
+
);
|
|
92
|
+
toast.success(
|
|
93
|
+
customer.isActive ? "Customer deactivated." : "Customer activated.",
|
|
94
|
+
);
|
|
82
95
|
} catch (err) {
|
|
83
|
-
toast.error(
|
|
96
|
+
toast.error(
|
|
97
|
+
apiErrorMessage(
|
|
98
|
+
err,
|
|
99
|
+
"Couldn't change this customer's status. Try again.",
|
|
100
|
+
),
|
|
101
|
+
);
|
|
84
102
|
} finally {
|
|
85
103
|
setStatusPending(false);
|
|
86
104
|
}
|
|
@@ -90,27 +108,46 @@ export function CustomerDetailPage() {
|
|
|
90
108
|
|
|
91
109
|
return (
|
|
92
110
|
<div className="flex flex-col gap-4">
|
|
93
|
-
<Breadcrumb
|
|
111
|
+
<Breadcrumb
|
|
112
|
+
items={[
|
|
113
|
+
{ title: "Customers", href: "/customers" },
|
|
114
|
+
{ title: customer?.email ?? "Details", href: `/customers/${id}` },
|
|
115
|
+
]}
|
|
116
|
+
/>
|
|
94
117
|
|
|
95
|
-
{loading && !customer &&
|
|
118
|
+
{loading && !customer && (
|
|
119
|
+
<p className="text-sm text-muted-foreground">Loading…</p>
|
|
120
|
+
)}
|
|
96
121
|
|
|
97
122
|
{customer && (
|
|
98
123
|
<>
|
|
99
124
|
<Card>
|
|
100
125
|
<CardHeader className="flex flex-row items-center justify-between">
|
|
101
126
|
<div>
|
|
102
|
-
<CardTitle>
|
|
127
|
+
<CardTitle>
|
|
128
|
+
{customerName(customer) || customer.email}
|
|
129
|
+
</CardTitle>
|
|
103
130
|
<CardDescription>
|
|
104
|
-
Joined: {format(new Date(customer.joinedDate), "dd MMM yyyy")}
|
|
105
|
-
{
|
|
131
|
+
Joined: {format(new Date(customer.joinedDate), "dd MMM yyyy")}{" "}
|
|
132
|
+
· Created:{" "}
|
|
133
|
+
{format(new Date(customer.createdAt), "dd MMM yyyy")} ·
|
|
134
|
+
Updated: {format(new Date(customer.updatedAt), "dd MMM yyyy")}
|
|
106
135
|
</CardDescription>
|
|
107
136
|
</div>
|
|
108
137
|
<div className="flex items-center gap-1.5">
|
|
109
|
-
<Badge variant={customer.isActive ? "success" : "destructive"}>
|
|
138
|
+
<Badge variant={customer.isActive ? "success" : "destructive"}>
|
|
139
|
+
{customer.isActive ? "Active" : "Inactive"}
|
|
140
|
+
</Badge>
|
|
110
141
|
<Link
|
|
111
142
|
to={`/customers/${id}/edit`}
|
|
112
|
-
className={cn(
|
|
113
|
-
|
|
143
|
+
className={cn(
|
|
144
|
+
buttonVariants({ variant: "outline", size: "sm" }),
|
|
145
|
+
)}
|
|
146
|
+
title={
|
|
147
|
+
canManage
|
|
148
|
+
? undefined
|
|
149
|
+
: `You need the "${PERMISSIONS.customersManage}" permission to do this.`
|
|
150
|
+
}
|
|
114
151
|
aria-disabled={!canManage}
|
|
115
152
|
onClick={(e) => !canManage && e.preventDefault()}
|
|
116
153
|
>
|
|
@@ -121,8 +158,16 @@ export function CustomerDetailPage() {
|
|
|
121
158
|
</CardHeader>
|
|
122
159
|
<CardContent className="flex flex-col gap-4">
|
|
123
160
|
<Avatar className="size-16">
|
|
124
|
-
{customer.photo &&
|
|
125
|
-
|
|
161
|
+
{customer.photo && (
|
|
162
|
+
<AvatarImage
|
|
163
|
+
src={customer.photo}
|
|
164
|
+
alt=""
|
|
165
|
+
className="object-cover"
|
|
166
|
+
/>
|
|
167
|
+
)}
|
|
168
|
+
<AvatarFallback className="text-base">
|
|
169
|
+
{initialsOf(customer)}
|
|
170
|
+
</AvatarFallback>
|
|
126
171
|
</Avatar>
|
|
127
172
|
|
|
128
173
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
@@ -131,14 +176,39 @@ export function CustomerDetailPage() {
|
|
|
131
176
|
{field("First name", customer.firstName)}
|
|
132
177
|
{field("Last name", customer.lastName)}
|
|
133
178
|
{field("Phone", customer.phone)}
|
|
134
|
-
{field(
|
|
135
|
-
|
|
136
|
-
|
|
179
|
+
{field(
|
|
180
|
+
"Gender",
|
|
181
|
+
customer.gender &&
|
|
182
|
+
customer.gender.charAt(0).toUpperCase() +
|
|
183
|
+
customer.gender.slice(1),
|
|
184
|
+
)}
|
|
185
|
+
{field(
|
|
186
|
+
"Date of birth",
|
|
187
|
+
customer.dob
|
|
188
|
+
? format(new Date(customer.dob), "dd MMM yyyy")
|
|
189
|
+
: null,
|
|
190
|
+
)}
|
|
191
|
+
{field(
|
|
192
|
+
"Joined date",
|
|
193
|
+
format(new Date(customer.joinedDate), "dd MMM yyyy"),
|
|
194
|
+
)}
|
|
137
195
|
</div>
|
|
138
196
|
|
|
139
197
|
<div className="flex flex-wrap gap-1.5">
|
|
140
|
-
<Badge
|
|
141
|
-
|
|
198
|
+
<Badge
|
|
199
|
+
variant={customer.isEmailVerified ? "success" : "outline"}
|
|
200
|
+
>
|
|
201
|
+
{customer.isEmailVerified
|
|
202
|
+
? "Email verified"
|
|
203
|
+
: "Email unverified"}
|
|
204
|
+
</Badge>
|
|
205
|
+
<Badge
|
|
206
|
+
variant={customer.isPhoneVerified ? "success" : "outline"}
|
|
207
|
+
>
|
|
208
|
+
{customer.isPhoneVerified
|
|
209
|
+
? "Phone verified"
|
|
210
|
+
: "Phone unverified"}
|
|
211
|
+
</Badge>
|
|
142
212
|
</div>
|
|
143
213
|
</CardContent>
|
|
144
214
|
</Card>
|
|
@@ -151,7 +221,11 @@ export function CustomerDetailPage() {
|
|
|
151
221
|
<Button
|
|
152
222
|
variant={customer.isActive ? "destructive" : "outline"}
|
|
153
223
|
disabled={!canStatus || statusPending}
|
|
154
|
-
title={
|
|
224
|
+
title={
|
|
225
|
+
canStatus
|
|
226
|
+
? undefined
|
|
227
|
+
: `You need the "${PERMISSIONS.customersStatus}" permission to do this.`
|
|
228
|
+
}
|
|
155
229
|
onClick={() => void toggleActive()}
|
|
156
230
|
>
|
|
157
231
|
{customer.isActive ? "Deactivate" : "Activate"}
|
|
@@ -160,7 +234,11 @@ export function CustomerDetailPage() {
|
|
|
160
234
|
<Button
|
|
161
235
|
variant="destructive"
|
|
162
236
|
disabled={!canManage}
|
|
163
|
-
title={
|
|
237
|
+
title={
|
|
238
|
+
canManage
|
|
239
|
+
? undefined
|
|
240
|
+
: `You need the "${PERMISSIONS.customersManage}" permission to do this.`
|
|
241
|
+
}
|
|
164
242
|
onClick={() => setConfirmingDelete(true)}
|
|
165
243
|
>
|
|
166
244
|
Delete customer
|
|
@@ -203,7 +281,9 @@ function DeleteCustomerDialog({
|
|
|
203
281
|
toast.success("Customer deleted.");
|
|
204
282
|
onDeleted();
|
|
205
283
|
} catch (err) {
|
|
206
|
-
toast.error(
|
|
284
|
+
toast.error(
|
|
285
|
+
apiErrorMessage(err, "Couldn't delete this customer. Try again."),
|
|
286
|
+
);
|
|
207
287
|
} finally {
|
|
208
288
|
setSubmitting(false);
|
|
209
289
|
}
|
|
@@ -218,7 +298,11 @@ function DeleteCustomerDialog({
|
|
|
218
298
|
>
|
|
219
299
|
<div className="flex flex-col gap-4">
|
|
220
300
|
<div className="flex gap-2">
|
|
221
|
-
<Button
|
|
301
|
+
<Button
|
|
302
|
+
variant="destructive"
|
|
303
|
+
disabled={submitting}
|
|
304
|
+
onClick={() => void handleDelete()}
|
|
305
|
+
>
|
|
222
306
|
{submitting ? "Deleting…" : "Delete customer"}
|
|
223
307
|
</Button>
|
|
224
308
|
<Button variant="outline" onClick={onClose}>
|