@mesob/auth-react 0.0.7 → 0.1.0
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/dist/components/auth/auth-page-layout.d.ts +1 -3
- package/dist/components/auth/auth-page-layout.js +1 -17
- package/dist/components/auth/auth-page-layout.js.map +1 -1
- package/dist/components/auth/countdown.js +70 -9
- package/dist/components/auth/countdown.js.map +1 -1
- package/dist/components/auth/forgot-password.js +101 -35
- package/dist/components/auth/forgot-password.js.map +1 -1
- package/dist/components/auth/pages/forgot-password-page.d.ts +2 -13
- package/dist/components/auth/pages/forgot-password-page.js +198 -126
- package/dist/components/auth/pages/forgot-password-page.js.map +1 -1
- package/dist/components/auth/pages/reset-password-page.d.ts +1 -12
- package/dist/components/auth/pages/reset-password-page.js +288 -200
- package/dist/components/auth/pages/reset-password-page.js.map +1 -1
- package/dist/components/auth/pages/sign-in-page.d.ts +1 -12
- package/dist/components/auth/pages/sign-in-page.js +352 -230
- package/dist/components/auth/pages/sign-in-page.js.map +1 -1
- package/dist/components/auth/pages/sign-up-page.d.ts +1 -11
- package/dist/components/auth/pages/sign-up-page.js +310 -216
- package/dist/components/auth/pages/sign-up-page.js.map +1 -1
- package/dist/components/auth/pages/verify-email-page.d.ts +2 -12
- package/dist/components/auth/pages/verify-email-page.js +203 -135
- package/dist/components/auth/pages/verify-email-page.js.map +1 -1
- package/dist/components/auth/pages/verify-phone-page.d.ts +1 -11
- package/dist/components/auth/pages/verify-phone-page.js +206 -137
- package/dist/components/auth/pages/verify-phone-page.js.map +1 -1
- package/dist/components/auth/reset-password-form.d.ts +1 -1
- package/dist/components/auth/reset-password-form.js +188 -106
- package/dist/components/auth/reset-password-form.js.map +1 -1
- package/dist/components/auth/sign-in.d.ts +3 -3
- package/dist/components/auth/sign-in.js +228 -109
- package/dist/components/auth/sign-in.js.map +1 -1
- package/dist/components/auth/sign-up.js +210 -122
- package/dist/components/auth/sign-up.js.map +1 -1
- package/dist/components/auth/verification-form.d.ts +1 -1
- package/dist/components/auth/verification-form.js +101 -53
- package/dist/components/auth/verification-form.js.map +1 -1
- package/dist/components/error-boundary.d.ts +27 -0
- package/dist/components/error-boundary.js +49 -0
- package/dist/components/error-boundary.js.map +1 -0
- package/dist/components/iam/permissions/permissions-page.d.ts +5 -0
- package/dist/components/iam/permissions/permissions-page.js +201 -0
- package/dist/components/iam/permissions/permissions-page.js.map +1 -0
- package/dist/components/iam/roles/roles-page.d.ts +5 -0
- package/dist/components/iam/roles/roles-page.js +199 -0
- package/dist/components/iam/roles/roles-page.js.map +1 -0
- package/dist/components/iam/sessions/sessions-page.d.ts +5 -0
- package/dist/components/iam/sessions/sessions-page.js +202 -0
- package/dist/components/iam/sessions/sessions-page.js.map +1 -0
- package/dist/components/iam/tenants/tenants-page.d.ts +5 -0
- package/dist/components/iam/tenants/tenants-page.js +202 -0
- package/dist/components/iam/tenants/tenants-page.js.map +1 -0
- package/dist/components/iam/users/users-page.d.ts +5 -0
- package/dist/components/iam/users/users-page.js +211 -0
- package/dist/components/iam/users/users-page.js.map +1 -0
- package/dist/components/profile/profile-page.d.ts +8 -0
- package/dist/components/profile/profile-page.js +163 -0
- package/dist/components/profile/profile-page.js.map +1 -0
- package/dist/components/shared/data-table/data-table.d.ts +22 -0
- package/dist/components/shared/data-table/data-table.js +85 -0
- package/dist/components/shared/data-table/data-table.js.map +1 -0
- package/dist/components/skeletons/auth-form-skeleton.d.ts +5 -0
- package/dist/components/skeletons/auth-form-skeleton.js +32 -0
- package/dist/components/skeletons/auth-form-skeleton.js.map +1 -0
- package/dist/components/skeletons/profile-skeleton.d.ts +5 -0
- package/dist/components/skeletons/profile-skeleton.js +33 -0
- package/dist/components/skeletons/profile-skeleton.js.map +1 -0
- package/dist/components/skeletons/table-skeleton.d.ts +9 -0
- package/dist/components/skeletons/table-skeleton.js +39 -0
- package/dist/components/skeletons/table-skeleton.js.map +1 -0
- package/dist/handle-error-BqDMxnQZ.d.ts +8 -0
- package/dist/index.d.ts +75 -208
- package/dist/index.js +2091 -1057
- package/dist/index.js.map +1 -1
- package/package.json +9 -3
- package/dist/handle-error-H0iqQxJ5.d.ts +0 -6
|
@@ -3,39 +3,90 @@
|
|
|
3
3
|
// src/components/auth/verification-form.tsx
|
|
4
4
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
5
5
|
import { Button as Button2 } from "@mesob/ui/components/button";
|
|
6
|
-
import {
|
|
7
|
-
Form,
|
|
8
|
-
FormControl,
|
|
9
|
-
FormField,
|
|
10
|
-
FormItem,
|
|
11
|
-
FormMessage
|
|
12
|
-
} from "@mesob/ui/components/form";
|
|
6
|
+
import { Field, FieldError, FieldGroup } from "@mesob/ui/components/field";
|
|
13
7
|
import {
|
|
14
8
|
InputOTP,
|
|
15
9
|
InputOTPGroup,
|
|
16
10
|
InputOTPSlot
|
|
17
11
|
} from "@mesob/ui/components/input-otp";
|
|
18
12
|
import { Spinner as Spinner2 } from "@mesob/ui/components/spinner";
|
|
19
|
-
import {
|
|
20
|
-
import { useMemo } from "react";
|
|
21
|
-
import { useForm } from "react-hook-form";
|
|
13
|
+
import { Controller, useForm } from "react-hook-form";
|
|
22
14
|
import { z } from "zod";
|
|
23
15
|
|
|
16
|
+
// src/lib/translations.ts
|
|
17
|
+
function createTranslator(messages, namespace) {
|
|
18
|
+
return (key, params) => {
|
|
19
|
+
const fullKey = namespace ? `${namespace}.${key}` : key;
|
|
20
|
+
const keys = fullKey.split(".");
|
|
21
|
+
let value = messages;
|
|
22
|
+
for (const k of keys) {
|
|
23
|
+
if (value && typeof value === "object" && value !== null) {
|
|
24
|
+
value = value[k];
|
|
25
|
+
} else {
|
|
26
|
+
return fullKey;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (typeof value !== "string") {
|
|
30
|
+
return fullKey;
|
|
31
|
+
}
|
|
32
|
+
if (params) {
|
|
33
|
+
return value.replace(
|
|
34
|
+
/\{(\w+)\}/g,
|
|
35
|
+
(_, param) => String(params[param] ?? `{${param}}`)
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/provider.tsx
|
|
43
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
44
|
+
import { deepmerge } from "deepmerge-ts";
|
|
45
|
+
import createFetchClient from "openapi-fetch";
|
|
46
|
+
import createClient from "openapi-react-query";
|
|
47
|
+
import { createContext, useContext, useEffect, useState } from "react";
|
|
48
|
+
import { jsx } from "react/jsx-runtime";
|
|
49
|
+
var SessionContext = createContext(null);
|
|
50
|
+
var ApiContext = createContext(null);
|
|
51
|
+
var ConfigContext = createContext(null);
|
|
52
|
+
var queryClient = new QueryClient({
|
|
53
|
+
defaultOptions: {
|
|
54
|
+
queries: {
|
|
55
|
+
staleTime: 1e3 * 60 * 5,
|
|
56
|
+
gcTime: 1e3 * 60 * 10,
|
|
57
|
+
retry: 1,
|
|
58
|
+
refetchOnWindowFocus: false
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
function useConfig() {
|
|
63
|
+
const context = useContext(ConfigContext);
|
|
64
|
+
if (!context) {
|
|
65
|
+
throw new Error("useConfig must be used within MesobAuthProvider");
|
|
66
|
+
}
|
|
67
|
+
return context;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/hooks/use-translator.ts
|
|
71
|
+
function useTranslator(namespace) {
|
|
72
|
+
const { config } = useConfig();
|
|
73
|
+
return createTranslator(config.messages || {}, namespace);
|
|
74
|
+
}
|
|
75
|
+
|
|
24
76
|
// src/components/auth/countdown.tsx
|
|
25
77
|
import { Button } from "@mesob/ui/components/button";
|
|
26
78
|
import { Spinner } from "@mesob/ui/components/spinner";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
79
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
80
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
30
81
|
var Countdown = ({
|
|
31
82
|
initialSeconds = 60,
|
|
32
83
|
onResend,
|
|
33
84
|
resending = false
|
|
34
85
|
}) => {
|
|
35
|
-
const t =
|
|
36
|
-
const [seconds, setSeconds] =
|
|
37
|
-
const [isResending, setIsResending] =
|
|
38
|
-
|
|
86
|
+
const t = useTranslator("Common");
|
|
87
|
+
const [seconds, setSeconds] = useState2(initialSeconds);
|
|
88
|
+
const [isResending, setIsResending] = useState2(false);
|
|
89
|
+
useEffect2(() => {
|
|
39
90
|
if (seconds <= 0) {
|
|
40
91
|
return;
|
|
41
92
|
}
|
|
@@ -61,7 +112,7 @@ var Countdown = ({
|
|
|
61
112
|
}
|
|
62
113
|
};
|
|
63
114
|
if (seconds > 0) {
|
|
64
|
-
return /* @__PURE__ */
|
|
115
|
+
return /* @__PURE__ */ jsx2(Button, { variant: "ghost", disabled: true, children: t("resendIn", { seconds }) });
|
|
65
116
|
}
|
|
66
117
|
return /* @__PURE__ */ jsxs(
|
|
67
118
|
Button,
|
|
@@ -70,7 +121,7 @@ var Countdown = ({
|
|
|
70
121
|
onClick: handleResend,
|
|
71
122
|
disabled: isResending || resending,
|
|
72
123
|
children: [
|
|
73
|
-
isResending || resending && /* @__PURE__ */
|
|
124
|
+
isResending || resending && /* @__PURE__ */ jsx2(Spinner, {}),
|
|
74
125
|
t("resend")
|
|
75
126
|
]
|
|
76
127
|
}
|
|
@@ -78,33 +129,33 @@ var Countdown = ({
|
|
|
78
129
|
};
|
|
79
130
|
|
|
80
131
|
// src/components/auth/verification-form.tsx
|
|
81
|
-
import { jsx as
|
|
132
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
133
|
+
var verificationSchema = (t) => z.object({
|
|
134
|
+
code: z.string().length(6, t("form.codeLength"))
|
|
135
|
+
});
|
|
82
136
|
var VerificationForm = ({
|
|
83
137
|
onSubmit,
|
|
84
138
|
onResend,
|
|
85
139
|
isLoading = false
|
|
86
140
|
}) => {
|
|
87
|
-
const t =
|
|
88
|
-
const verificationSchema = useMemo(
|
|
89
|
-
() => z.object({
|
|
90
|
-
code: z.string().length(6, t("form.codeLength"))
|
|
91
|
-
}),
|
|
92
|
-
[t]
|
|
93
|
-
);
|
|
141
|
+
const t = useTranslator("Auth.verification");
|
|
94
142
|
const form = useForm({
|
|
95
|
-
resolver: zodResolver(verificationSchema),
|
|
143
|
+
resolver: zodResolver(verificationSchema(t)),
|
|
96
144
|
defaultValues: {
|
|
97
145
|
code: ""
|
|
98
146
|
}
|
|
99
147
|
});
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
148
|
+
const handleSubmit = form.handleSubmit(async (values) => {
|
|
149
|
+
await onSubmit(values);
|
|
150
|
+
});
|
|
151
|
+
return /* @__PURE__ */ jsxs2("form", { id: "verification-form", onSubmit: handleSubmit, children: [
|
|
152
|
+
/* @__PURE__ */ jsx3(FieldGroup, { children: /* @__PURE__ */ jsx3(
|
|
153
|
+
Controller,
|
|
103
154
|
{
|
|
104
|
-
control: form.control,
|
|
105
155
|
name: "code",
|
|
106
|
-
|
|
107
|
-
|
|
156
|
+
control: form.control,
|
|
157
|
+
render: ({ field, fieldState }) => /* @__PURE__ */ jsxs2(Field, { "data-invalid": fieldState.invalid, children: [
|
|
158
|
+
/* @__PURE__ */ jsx3("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx3(
|
|
108
159
|
InputOTP,
|
|
109
160
|
{
|
|
110
161
|
maxLength: 6,
|
|
@@ -114,40 +165,37 @@ var VerificationForm = ({
|
|
|
114
165
|
onChange: field.onChange,
|
|
115
166
|
onBlur: field.onBlur,
|
|
116
167
|
containerClassName: "gap-4 justify-center mb-2 flex items-center",
|
|
168
|
+
"aria-invalid": fieldState.invalid,
|
|
117
169
|
children: /* @__PURE__ */ jsxs2(InputOTPGroup, { className: "gap-3 *:data-[slot=input-otp-slot]:h-12 *:data-[slot=input-otp-slot]:w-12 *:data-[slot=input-otp-slot]:rounded-md *:data-[slot=input-otp-slot]:border *:data-[slot=input-otp-slot]:text-xl", children: [
|
|
118
|
-
/* @__PURE__ */
|
|
119
|
-
/* @__PURE__ */
|
|
120
|
-
/* @__PURE__ */
|
|
121
|
-
/* @__PURE__ */
|
|
122
|
-
/* @__PURE__ */
|
|
123
|
-
/* @__PURE__ */
|
|
170
|
+
/* @__PURE__ */ jsx3(InputOTPSlot, { className: "h-12", index: 0 }),
|
|
171
|
+
/* @__PURE__ */ jsx3(InputOTPSlot, { className: "h-12", index: 1 }),
|
|
172
|
+
/* @__PURE__ */ jsx3(InputOTPSlot, { className: "h-12", index: 2 }),
|
|
173
|
+
/* @__PURE__ */ jsx3(InputOTPSlot, { className: "h-12", index: 3 }),
|
|
174
|
+
/* @__PURE__ */ jsx3(InputOTPSlot, { className: "h-12", index: 4 }),
|
|
175
|
+
/* @__PURE__ */ jsx3(InputOTPSlot, { className: "h-12", index: 5 })
|
|
124
176
|
] })
|
|
125
177
|
}
|
|
126
|
-
) })
|
|
127
|
-
/* @__PURE__ */
|
|
178
|
+
) }),
|
|
179
|
+
fieldState.invalid && /* @__PURE__ */ jsx3(FieldError, { errors: [fieldState.error] })
|
|
128
180
|
] })
|
|
129
181
|
}
|
|
130
|
-
),
|
|
131
|
-
/* @__PURE__ */ jsxs2("div", { className: "flex justify-between items-center", children: [
|
|
132
|
-
/* @__PURE__ */
|
|
182
|
+
) }),
|
|
183
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex justify-between items-center mt-4", children: [
|
|
184
|
+
/* @__PURE__ */ jsx3(Countdown, { onResend, resending: isLoading }),
|
|
133
185
|
/* @__PURE__ */ jsxs2(
|
|
134
186
|
Button2,
|
|
135
187
|
{
|
|
136
|
-
type: "
|
|
137
|
-
|
|
138
|
-
form.handleSubmit(async (values) => {
|
|
139
|
-
await onSubmit(values);
|
|
140
|
-
})();
|
|
141
|
-
},
|
|
188
|
+
type: "submit",
|
|
189
|
+
form: "verification-form",
|
|
142
190
|
disabled: isLoading || form.watch("code").length !== 6,
|
|
143
191
|
children: [
|
|
144
|
-
isLoading && /* @__PURE__ */
|
|
192
|
+
isLoading && /* @__PURE__ */ jsx3(Spinner2, {}),
|
|
145
193
|
t("form.confirm")
|
|
146
194
|
]
|
|
147
195
|
}
|
|
148
196
|
)
|
|
149
197
|
] })
|
|
150
|
-
] })
|
|
198
|
+
] });
|
|
151
199
|
};
|
|
152
200
|
export {
|
|
153
201
|
VerificationForm
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/auth/verification-form.tsx","../../../src/components/auth/countdown.tsx"],"sourcesContent":["'use client';\n\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { Button } from '@mesob/ui/components/button';\nimport {\n Form,\n FormControl,\n FormField,\n FormItem,\n FormMessage,\n} from '@mesob/ui/components/form';\nimport {\n InputOTP,\n InputOTPGroup,\n InputOTPSlot,\n} from '@mesob/ui/components/input-otp';\nimport { Spinner } from '@mesob/ui/components/spinner';\nimport { useTranslations } from 'next-intl';\nimport { useMemo } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { z } from 'zod';\nimport type { AuthErrorContent } from '../../utils/handle-error';\nimport { Countdown } from './countdown';\n\ntype VerificationFormValues = {\n code: string;\n};\n\ntype VerificationFormProps = {\n verificationId: string;\n onSubmit: (values: VerificationFormValues) => Promise<void> | void;\n onResend: () => Promise<void> | void;\n isLoading?: boolean;\n error?: AuthErrorContent | string | null;\n};\n\nexport const VerificationForm = ({\n onSubmit,\n onResend,\n isLoading = false,\n}: VerificationFormProps) => {\n const t = useTranslations('Auth.verification');\n const verificationSchema = useMemo(\n () =>\n z.object({\n code: z.string().length(6, t('form.codeLength')),\n }),\n [t],\n );\n const form = useForm<VerificationFormValues>({\n resolver: zodResolver(verificationSchema),\n defaultValues: {\n code: '',\n },\n });\n\n return (\n <Form {...form}>\n <form className=\"space-y-4\">\n <FormField\n control={form.control}\n name=\"code\"\n render={({ field }) => (\n <FormItem>\n <FormControl>\n <div className=\"flex justify-center\">\n <InputOTP\n maxLength={6}\n id=\"otp\"\n required\n value={field.value ?? ''}\n onChange={field.onChange}\n onBlur={field.onBlur}\n containerClassName=\"gap-4 justify-center mb-2 flex items-center\"\n >\n <InputOTPGroup className=\"gap-3 *:data-[slot=input-otp-slot]:h-12 *:data-[slot=input-otp-slot]:w-12 *:data-[slot=input-otp-slot]:rounded-md *:data-[slot=input-otp-slot]:border *:data-[slot=input-otp-slot]:text-xl\">\n <InputOTPSlot className=\"h-12\" index={0} />\n <InputOTPSlot className=\"h-12\" index={1} />\n <InputOTPSlot className=\"h-12\" index={2} />\n <InputOTPSlot className=\"h-12\" index={3} />\n <InputOTPSlot className=\"h-12\" index={4} />\n <InputOTPSlot className=\"h-12\" index={5} />\n </InputOTPGroup>\n </InputOTP>\n </div>\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <div className=\"flex justify-between items-center\">\n <Countdown onResend={onResend} resending={isLoading} />\n <Button\n type=\"button\"\n onClick={() => {\n form.handleSubmit(async (values) => {\n await onSubmit(values);\n })();\n }}\n disabled={isLoading || form.watch('code').length !== 6}\n >\n {isLoading && <Spinner />}\n {t('form.confirm')}\n </Button>\n </div>\n </form>\n </Form>\n );\n};\n","'use client';\n\nimport { Button } from '@mesob/ui/components/button';\nimport { Spinner } from '@mesob/ui/components/spinner';\nimport { useTranslations } from 'next-intl';\nimport { useEffect, useState } from 'react';\n\ntype CountdownProps = {\n initialSeconds?: number;\n onResend: () => Promise<void> | void;\n resending?: boolean;\n};\n\nexport const Countdown = ({\n initialSeconds = 60,\n onResend,\n resending = false,\n}: CountdownProps) => {\n const t = useTranslations('Common');\n const [seconds, setSeconds] = useState(initialSeconds);\n const [isResending, setIsResending] = useState(false);\n\n useEffect(() => {\n if (seconds <= 0) {\n return;\n }\n\n const timer = setInterval(() => {\n setSeconds((prev) => {\n if (prev <= 1) {\n clearInterval(timer);\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n\n return () => clearInterval(timer);\n }, [seconds]);\n\n const handleResend = async () => {\n setIsResending(true);\n try {\n await onResend();\n setSeconds(initialSeconds);\n } catch (_error) {\n // Error handling is done by parent\n } finally {\n setIsResending(false);\n }\n };\n\n if (seconds > 0) {\n return (\n <Button variant=\"ghost\" disabled>\n {t('resendIn', { seconds })}\n </Button>\n );\n }\n\n return (\n <Button\n variant=\"ghost\"\n onClick={handleResend}\n disabled={isResending || resending}\n >\n {isResending || (resending && <Spinner />)}\n {t('resend')}\n </Button>\n );\n};\n"],"mappings":";;;AAEA,SAAS,mBAAmB;AAC5B,SAAS,UAAAA,eAAc;AACvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,WAAAC,gBAAe;AACxB,SAAS,mBAAAC,wBAAuB;AAChC,SAAS,eAAe;AACxB,SAAS,eAAe;AACxB,SAAS,SAAS;;;AClBlB,SAAS,cAAc;AACvB,SAAS,eAAe;AACxB,SAAS,uBAAuB;AAChC,SAAS,WAAW,gBAAgB;AAiD9B,cAOF,YAPE;AAzCC,IAAM,YAAY,CAAC;AAAA,EACxB,iBAAiB;AAAA,EACjB;AAAA,EACA,YAAY;AACd,MAAsB;AACpB,QAAM,IAAI,gBAAgB,QAAQ;AAClC,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,cAAc;AACrD,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,KAAK;AAEpD,YAAU,MAAM;AACd,QAAI,WAAW,GAAG;AAChB;AAAA,IACF;AAEA,UAAM,QAAQ,YAAY,MAAM;AAC9B,iBAAW,CAAC,SAAS;AACnB,YAAI,QAAQ,GAAG;AACb,wBAAc,KAAK;AACnB,iBAAO;AAAA,QACT;AACA,eAAO,OAAO;AAAA,MAChB,CAAC;AAAA,IACH,GAAG,GAAI;AAEP,WAAO,MAAM,cAAc,KAAK;AAAA,EAClC,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,eAAe,YAAY;AAC/B,mBAAe,IAAI;AACnB,QAAI;AACF,YAAM,SAAS;AACf,iBAAW,cAAc;AAAA,IAC3B,SAAS,QAAQ;AAAA,IAEjB,UAAE;AACA,qBAAe,KAAK;AAAA,IACtB;AAAA,EACF;AAEA,MAAI,UAAU,GAAG;AACf,WACE,oBAAC,UAAO,SAAQ,SAAQ,UAAQ,MAC7B,YAAE,YAAY,EAAE,QAAQ,CAAC,GAC5B;AAAA,EAEJ;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,SAAQ;AAAA,MACR,SAAS;AAAA,MACT,UAAU,eAAe;AAAA,MAExB;AAAA,uBAAgB,aAAa,oBAAC,WAAQ;AAAA,QACtC,EAAE,QAAQ;AAAA;AAAA;AAAA,EACb;AAEJ;;;ADKoB,SACE,OAAAC,MADF,QAAAC,aAAA;AAvCb,IAAM,mBAAmB,CAAC;AAAA,EAC/B;AAAA,EACA;AAAA,EACA,YAAY;AACd,MAA6B;AAC3B,QAAM,IAAIC,iBAAgB,mBAAmB;AAC7C,QAAM,qBAAqB;AAAA,IACzB,MACE,EAAE,OAAO;AAAA,MACP,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,iBAAiB,CAAC;AAAA,IACjD,CAAC;AAAA,IACH,CAAC,CAAC;AAAA,EACJ;AACA,QAAM,OAAO,QAAgC;AAAA,IAC3C,UAAU,YAAY,kBAAkB;AAAA,IACxC,eAAe;AAAA,MACb,MAAM;AAAA,IACR;AAAA,EACF,CAAC;AAED,SACE,gBAAAF,KAAC,QAAM,GAAG,MACR,0BAAAC,MAAC,UAAK,WAAU,aACd;AAAA,oBAAAD;AAAA,MAAC;AAAA;AAAA,QACC,SAAS,KAAK;AAAA,QACd,MAAK;AAAA,QACL,QAAQ,CAAC,EAAE,MAAM,MACf,gBAAAC,MAAC,YACC;AAAA,0BAAAD,KAAC,eACC,0BAAAA,KAAC,SAAI,WAAU,uBACb,0BAAAA;AAAA,YAAC;AAAA;AAAA,cACC,WAAW;AAAA,cACX,IAAG;AAAA,cACH,UAAQ;AAAA,cACR,OAAO,MAAM,SAAS;AAAA,cACtB,UAAU,MAAM;AAAA,cAChB,QAAQ,MAAM;AAAA,cACd,oBAAmB;AAAA,cAEnB,0BAAAC,MAAC,iBAAc,WAAU,8LACvB;AAAA,gCAAAD,KAAC,gBAAa,WAAU,QAAO,OAAO,GAAG;AAAA,gBACzC,gBAAAA,KAAC,gBAAa,WAAU,QAAO,OAAO,GAAG;AAAA,gBACzC,gBAAAA,KAAC,gBAAa,WAAU,QAAO,OAAO,GAAG;AAAA,gBACzC,gBAAAA,KAAC,gBAAa,WAAU,QAAO,OAAO,GAAG;AAAA,gBACzC,gBAAAA,KAAC,gBAAa,WAAU,QAAO,OAAO,GAAG;AAAA,gBACzC,gBAAAA,KAAC,gBAAa,WAAU,QAAO,OAAO,GAAG;AAAA,iBAC3C;AAAA;AAAA,UACF,GACF,GACF;AAAA,UACA,gBAAAA,KAAC,eAAY;AAAA,WACf;AAAA;AAAA,IAEJ;AAAA,IAEA,gBAAAC,MAAC,SAAI,WAAU,qCACb;AAAA,sBAAAD,KAAC,aAAU,UAAoB,WAAW,WAAW;AAAA,MACrD,gBAAAC;AAAA,QAACE;AAAA,QAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,MAAM;AACb,iBAAK,aAAa,OAAO,WAAW;AAClC,oBAAM,SAAS,MAAM;AAAA,YACvB,CAAC,EAAE;AAAA,UACL;AAAA,UACA,UAAU,aAAa,KAAK,MAAM,MAAM,EAAE,WAAW;AAAA,UAEpD;AAAA,yBAAa,gBAAAH,KAACI,UAAA,EAAQ;AAAA,YACtB,EAAE,cAAc;AAAA;AAAA;AAAA,MACnB;AAAA,OACF;AAAA,KACF,GACF;AAEJ;","names":["Button","Spinner","useTranslations","jsx","jsxs","useTranslations","Button","Spinner"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/auth/verification-form.tsx","../../../src/lib/translations.ts","../../../src/provider.tsx","../../../src/hooks/use-translator.ts","../../../src/components/auth/countdown.tsx"],"sourcesContent":["'use client';\n\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { Button } from '@mesob/ui/components/button';\nimport { Field, FieldError, FieldGroup } from '@mesob/ui/components/field';\nimport {\n InputOTP,\n InputOTPGroup,\n InputOTPSlot,\n} from '@mesob/ui/components/input-otp';\nimport { Spinner } from '@mesob/ui/components/spinner';\nimport { Controller, useForm } from 'react-hook-form';\nimport { z } from 'zod';\nimport { useTranslator } from '../../hooks/use-translator';\nimport type { AuthErrorContent } from '../../utils/handle-error';\nimport { Countdown } from './countdown';\n\ntype VerificationFormValues = {\n code: string;\n};\n\ntype VerificationFormProps = {\n verificationId: string;\n onSubmit: (values: VerificationFormValues) => Promise<void> | void;\n onResend: () => Promise<void> | void;\n isLoading?: boolean;\n error?: AuthErrorContent | string | null;\n};\n\nconst verificationSchema = (t: (key: string) => string) =>\n z.object({\n code: z.string().length(6, t('form.codeLength')),\n });\n\nexport const VerificationForm = ({\n onSubmit,\n onResend,\n isLoading = false,\n}: VerificationFormProps) => {\n const t = useTranslator('Auth.verification');\n const form = useForm<VerificationFormValues>({\n resolver: zodResolver(verificationSchema(t)),\n defaultValues: {\n code: '',\n },\n });\n\n const handleSubmit = form.handleSubmit(async (values) => {\n await onSubmit(values);\n });\n\n return (\n <form id=\"verification-form\" onSubmit={handleSubmit}>\n <FieldGroup>\n <Controller\n name=\"code\"\n control={form.control}\n render={({ field, fieldState }) => (\n <Field data-invalid={fieldState.invalid}>\n <div className=\"flex justify-center\">\n <InputOTP\n maxLength={6}\n id=\"otp\"\n required\n value={field.value ?? ''}\n onChange={field.onChange}\n onBlur={field.onBlur}\n containerClassName=\"gap-4 justify-center mb-2 flex items-center\"\n aria-invalid={fieldState.invalid}\n >\n <InputOTPGroup className=\"gap-3 *:data-[slot=input-otp-slot]:h-12 *:data-[slot=input-otp-slot]:w-12 *:data-[slot=input-otp-slot]:rounded-md *:data-[slot=input-otp-slot]:border *:data-[slot=input-otp-slot]:text-xl\">\n <InputOTPSlot className=\"h-12\" index={0} />\n <InputOTPSlot className=\"h-12\" index={1} />\n <InputOTPSlot className=\"h-12\" index={2} />\n <InputOTPSlot className=\"h-12\" index={3} />\n <InputOTPSlot className=\"h-12\" index={4} />\n <InputOTPSlot className=\"h-12\" index={5} />\n </InputOTPGroup>\n </InputOTP>\n </div>\n {fieldState.invalid && <FieldError errors={[fieldState.error]} />}\n </Field>\n )}\n />\n </FieldGroup>\n <div className=\"flex justify-between items-center mt-4\">\n <Countdown onResend={onResend} resending={isLoading} />\n <Button\n type=\"submit\"\n form=\"verification-form\"\n disabled={isLoading || form.watch('code').length !== 6}\n >\n {isLoading && <Spinner />}\n {t('form.confirm')}\n </Button>\n </div>\n </form>\n );\n};\n","type Messages = Record<string, unknown>;\n\nexport function createTranslator(messages: Messages, namespace?: string) {\n return (key: string, params?: Record<string, string | number>): string => {\n const fullKey = namespace ? `${namespace}.${key}` : key;\n const keys = fullKey.split('.');\n\n let value: unknown = messages;\n for (const k of keys) {\n if (value && typeof value === 'object' && value !== null) {\n value = (value as Record<string, unknown>)[k];\n } else {\n return fullKey;\n }\n }\n\n if (typeof value !== 'string') {\n return fullKey;\n }\n\n // Simple parameter replacement\n if (params) {\n return value.replace(/\\{(\\w+)\\}/g, (_, param) =>\n String(params[param] ?? `{${param}}`),\n );\n }\n\n return value;\n };\n}\n","'use client';\n\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport { deepmerge } from 'deepmerge-ts';\nimport createFetchClient from 'openapi-fetch';\nimport createClient from 'openapi-react-query';\nimport type { ReactNode } from 'react';\nimport { createContext, useContext, useEffect, useState } from 'react';\nimport type { paths } from './data/openapi';\nimport { createTranslator } from './lib/translations';\nimport {\n type AuthClientConfig,\n type AuthResponse,\n defaultAuthClientConfig,\n type Session,\n type User,\n} from './types';\nimport { createCustomFetch } from './utils/custom-fetch';\n\ntype OpenApiHooks = any;\n\ntype SessionState = {\n user: User | null;\n session: Session | null;\n isLoading: boolean;\n isAuthenticated: boolean;\n error: Error | null;\n};\n\ntype SessionContextValue = SessionState & {\n refresh: () => Promise<void>;\n signOut: () => Promise<void>;\n};\n\ntype ApiContextValue = {\n hooks: OpenApiHooks;\n setAuth: (auth: AuthResponse) => void;\n clearAuth: () => void;\n refresh: () => Promise<void>;\n};\n\ntype ConfigContextValue = {\n config: AuthClientConfig;\n t: (key: string, params?: Record<string, string | number>) => string;\n};\n\nconst SessionContext = createContext<SessionContextValue | null>(null);\nconst ApiContext = createContext<ApiContextValue | null>(null);\nconst ConfigContext = createContext<ConfigContextValue | null>(null);\n\nconst queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 1000 * 60 * 5,\n gcTime: 1000 * 60 * 10,\n retry: 1,\n refetchOnWindowFocus: false,\n },\n },\n});\n\nexport function useSession() {\n const context = useContext(SessionContext);\n if (!context) {\n throw new Error('useSession must be used within MesobAuthProvider');\n }\n return context;\n}\n\nexport function useApi() {\n const context = useContext(ApiContext);\n if (!context) {\n throw new Error('useApi must be used within MesobAuthProvider');\n }\n return context;\n}\n\nexport function useConfig() {\n const context = useContext(ConfigContext);\n if (!context) {\n throw new Error('useConfig must be used within MesobAuthProvider');\n }\n return context;\n}\n\ntype MesobAuthProviderProps = {\n config: AuthClientConfig;\n children: ReactNode;\n};\n\nexport function MesobAuthProvider({\n config,\n children,\n}: MesobAuthProviderProps) {\n const mergedConfig = deepmerge(\n { ...defaultAuthClientConfig } as Partial<AuthClientConfig>,\n config,\n ) as AuthClientConfig;\n\n const api = createFetchClient<paths>({\n baseUrl: mergedConfig.baseURL,\n fetch: createCustomFetch(mergedConfig),\n });\n\n const hooks = createClient(api);\n\n return (\n <QueryClientProvider client={queryClient}>\n <AuthStateProvider config={mergedConfig} hooks={hooks}>\n {children}\n </AuthStateProvider>\n </QueryClientProvider>\n );\n}\n\ntype AuthStateProviderProps = {\n config: AuthClientConfig;\n hooks: OpenApiHooks;\n children: ReactNode;\n};\n\nfunction AuthStateProvider({\n config,\n hooks,\n children,\n}: AuthStateProviderProps) {\n const [authState, setAuthState] = useState<{\n user: User | null;\n session: Session | null;\n isLoading: boolean;\n error: Error | null;\n }>({\n user: null,\n session: null,\n isLoading: false,\n error: null,\n });\n\n const {\n data: sessionData,\n isLoading: sessionLoading,\n error: sessionError,\n refetch,\n } = hooks.useQuery(\n 'get',\n '/session',\n {},\n {\n enabled: true,\n refetchOnMount: true,\n refetchOnWindowFocus: false,\n refetchOnReconnect: false,\n retry: false,\n },\n );\n\n useEffect(() => {\n if (sessionLoading) {\n setAuthState((prev) => ({ ...prev, isLoading: true }));\n return;\n }\n\n if (sessionError) {\n setAuthState({\n user: null,\n session: null,\n isLoading: false,\n error: sessionError as Error,\n });\n return;\n }\n\n if (sessionData) {\n setAuthState({\n user: sessionData.user,\n session: sessionData.session,\n isLoading: false,\n error: null,\n });\n return;\n }\n\n setAuthState({\n user: null,\n session: null,\n isLoading: false,\n error: null,\n });\n }, [sessionData, sessionLoading, sessionError]);\n\n const refresh = async () => {\n await refetch();\n };\n\n const setAuth = (auth: AuthResponse) => {\n setAuthState({\n user: auth.user,\n session: auth.session,\n isLoading: false,\n error: null,\n });\n };\n\n const clearAuth = () => {\n setAuthState({\n user: null,\n session: null,\n isLoading: false,\n error: null,\n });\n };\n\n const signOutMutation = hooks.useMutation('post', '/sign-out');\n\n const signOut = async () => {\n try {\n await signOutMutation.mutateAsync({});\n } finally {\n clearAuth();\n }\n };\n\n const t = createTranslator(config.messages || {});\n\n return (\n <ConfigContext.Provider value={{ config, t }}>\n <ApiContext.Provider value={{ hooks, setAuth, clearAuth, refresh }}>\n <SessionContext.Provider\n value={{\n user: authState.user,\n session: authState.session,\n isLoading: authState.isLoading,\n isAuthenticated: !!authState.user && !!authState.session,\n error: authState.error,\n refresh,\n signOut,\n }}\n >\n {children}\n </SessionContext.Provider>\n </ApiContext.Provider>\n </ConfigContext.Provider>\n );\n}\n","import { createTranslator } from '../lib/translations';\nimport { useConfig } from '../provider';\n\nexport function useTranslator(namespace?: string) {\n const { config } = useConfig();\n return createTranslator(config.messages || {}, namespace);\n}\n","'use client';\n\nimport { Button } from '@mesob/ui/components/button';\nimport { Spinner } from '@mesob/ui/components/spinner';\nimport { useEffect, useState } from 'react';\nimport { useTranslator } from '../../hooks/use-translator';\n\ntype CountdownProps = {\n initialSeconds?: number;\n onResend: () => Promise<void> | void;\n resending?: boolean;\n};\n\nexport const Countdown = ({\n initialSeconds = 60,\n onResend,\n resending = false,\n}: CountdownProps) => {\n const t = useTranslator('Common');\n const [seconds, setSeconds] = useState(initialSeconds);\n const [isResending, setIsResending] = useState(false);\n\n useEffect(() => {\n if (seconds <= 0) {\n return;\n }\n\n const timer = setInterval(() => {\n setSeconds((prev) => {\n if (prev <= 1) {\n clearInterval(timer);\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n\n return () => clearInterval(timer);\n }, [seconds]);\n\n const handleResend = async () => {\n setIsResending(true);\n try {\n await onResend();\n setSeconds(initialSeconds);\n } catch (_error) {\n // Error handling is done by parent\n } finally {\n setIsResending(false);\n }\n };\n\n if (seconds > 0) {\n return (\n <Button variant=\"ghost\" disabled>\n {t('resendIn', { seconds })}\n </Button>\n );\n }\n\n return (\n <Button\n variant=\"ghost\"\n onClick={handleResend}\n disabled={isResending || resending}\n >\n {isResending || (resending && <Spinner />)}\n {t('resend')}\n </Button>\n );\n};\n"],"mappings":"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","names":["Button","Spinner","useEffect","useState","jsx","useState","useEffect","jsx","jsxs","Button","Spinner"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { ReactNode, Component, ErrorInfo } from 'react';
|
|
4
|
+
|
|
5
|
+
type ErrorBoundaryProps = {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
fallback?: (props: {
|
|
8
|
+
error: Error;
|
|
9
|
+
reset: () => void;
|
|
10
|
+
}) => ReactNode;
|
|
11
|
+
};
|
|
12
|
+
type ErrorBoundaryState = {
|
|
13
|
+
hasError: boolean;
|
|
14
|
+
error: Error | null;
|
|
15
|
+
};
|
|
16
|
+
declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
17
|
+
constructor(props: ErrorBoundaryProps);
|
|
18
|
+
static getDerivedStateFromError(error: Error): ErrorBoundaryState;
|
|
19
|
+
componentDidCatch(_error: Error, _errorInfo: ErrorInfo): void;
|
|
20
|
+
reset: () => void;
|
|
21
|
+
render(): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | react_jsx_runtime.JSX.Element | null | undefined;
|
|
22
|
+
}
|
|
23
|
+
declare function AuthErrorBoundary({ children }: {
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
}): react_jsx_runtime.JSX.Element;
|
|
26
|
+
|
|
27
|
+
export { AuthErrorBoundary, ErrorBoundary };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/components/error-boundary.tsx
|
|
4
|
+
import { Button } from "@mesob/ui/components/button";
|
|
5
|
+
import { Component } from "react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
var ErrorBoundary = class extends Component {
|
|
8
|
+
constructor(props) {
|
|
9
|
+
super(props);
|
|
10
|
+
this.state = { hasError: false, error: null };
|
|
11
|
+
}
|
|
12
|
+
static getDerivedStateFromError(error) {
|
|
13
|
+
return { hasError: true, error };
|
|
14
|
+
}
|
|
15
|
+
componentDidCatch(_error, _errorInfo) {
|
|
16
|
+
}
|
|
17
|
+
reset = () => {
|
|
18
|
+
this.setState({ hasError: false, error: null });
|
|
19
|
+
};
|
|
20
|
+
render() {
|
|
21
|
+
if (this.state.hasError && this.state.error) {
|
|
22
|
+
if (this.props.fallback) {
|
|
23
|
+
return this.props.fallback({
|
|
24
|
+
error: this.state.error,
|
|
25
|
+
reset: this.reset
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return /* @__PURE__ */ jsx(ErrorFallback, { error: this.state.error, reset: this.reset });
|
|
29
|
+
}
|
|
30
|
+
return this.props.children;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
function ErrorFallback({ error, reset }) {
|
|
34
|
+
return /* @__PURE__ */ jsx("div", { className: "flex flex-col items-center justify-center min-h-[400px] p-6", children: /* @__PURE__ */ jsxs("div", { className: "max-w-md w-full space-y-4 text-center", children: [
|
|
35
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
36
|
+
/* @__PURE__ */ jsx("h2", { className: "text-2xl font-bold text-destructive", children: "Something went wrong" }),
|
|
37
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: error.message || "An unexpected error occurred" })
|
|
38
|
+
] }),
|
|
39
|
+
/* @__PURE__ */ jsx(Button, { onClick: reset, variant: "outline", children: "Try again" })
|
|
40
|
+
] }) });
|
|
41
|
+
}
|
|
42
|
+
function AuthErrorBoundary({ children }) {
|
|
43
|
+
return /* @__PURE__ */ jsx(ErrorBoundary, { children });
|
|
44
|
+
}
|
|
45
|
+
export {
|
|
46
|
+
AuthErrorBoundary,
|
|
47
|
+
ErrorBoundary
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=error-boundary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/error-boundary.tsx"],"sourcesContent":["'use client';\n\nimport { Button } from '@mesob/ui/components/button';\nimport { Component, type ErrorInfo, type ReactNode } from 'react';\n\ntype ErrorBoundaryProps = {\n children: ReactNode;\n fallback?: (props: { error: Error; reset: () => void }) => ReactNode;\n};\n\ntype ErrorBoundaryState = {\n hasError: boolean;\n error: Error | null;\n};\n\nexport class ErrorBoundary extends Component<\n ErrorBoundaryProps,\n ErrorBoundaryState\n> {\n constructor(props: ErrorBoundaryProps) {\n super(props);\n this.state = { hasError: false, error: null };\n }\n\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\n return { hasError: true, error };\n }\n\n componentDidCatch(_error: Error, _errorInfo: ErrorInfo) {\n // Error logged via getDerivedStateFromError\n }\n\n reset = () => {\n this.setState({ hasError: false, error: null });\n };\n\n render() {\n if (this.state.hasError && this.state.error) {\n if (this.props.fallback) {\n return this.props.fallback({\n error: this.state.error,\n reset: this.reset,\n });\n }\n return <ErrorFallback error={this.state.error} reset={this.reset} />;\n }\n\n return this.props.children;\n }\n}\n\nfunction ErrorFallback({ error, reset }: { error: Error; reset: () => void }) {\n return (\n <div className=\"flex flex-col items-center justify-center min-h-[400px] p-6\">\n <div className=\"max-w-md w-full space-y-4 text-center\">\n <div className=\"space-y-2\">\n <h2 className=\"text-2xl font-bold text-destructive\">\n Something went wrong\n </h2>\n <p className=\"text-muted-foreground\">\n {error.message || 'An unexpected error occurred'}\n </p>\n </div>\n <Button onClick={reset} variant=\"outline\">\n Try again\n </Button>\n </div>\n </div>\n );\n}\n\nexport function AuthErrorBoundary({ children }: { children: ReactNode }) {\n return <ErrorBoundary>{children}</ErrorBoundary>;\n}\n"],"mappings":";;;AAEA,SAAS,cAAc;AACvB,SAAS,iBAAiD;AAyC7C,cAWL,YAXK;AA7BN,IAAM,gBAAN,cAA4B,UAGjC;AAAA,EACA,YAAY,OAA2B;AACrC,UAAM,KAAK;AACX,SAAK,QAAQ,EAAE,UAAU,OAAO,OAAO,KAAK;AAAA,EAC9C;AAAA,EAEA,OAAO,yBAAyB,OAAkC;AAChE,WAAO,EAAE,UAAU,MAAM,MAAM;AAAA,EACjC;AAAA,EAEA,kBAAkB,QAAe,YAAuB;AAAA,EAExD;AAAA,EAEA,QAAQ,MAAM;AACZ,SAAK,SAAS,EAAE,UAAU,OAAO,OAAO,KAAK,CAAC;AAAA,EAChD;AAAA,EAEA,SAAS;AACP,QAAI,KAAK,MAAM,YAAY,KAAK,MAAM,OAAO;AAC3C,UAAI,KAAK,MAAM,UAAU;AACvB,eAAO,KAAK,MAAM,SAAS;AAAA,UACzB,OAAO,KAAK,MAAM;AAAA,UAClB,OAAO,KAAK;AAAA,QACd,CAAC;AAAA,MACH;AACA,aAAO,oBAAC,iBAAc,OAAO,KAAK,MAAM,OAAO,OAAO,KAAK,OAAO;AAAA,IACpE;AAEA,WAAO,KAAK,MAAM;AAAA,EACpB;AACF;AAEA,SAAS,cAAc,EAAE,OAAO,MAAM,GAAwC;AAC5E,SACE,oBAAC,SAAI,WAAU,+DACb,+BAAC,SAAI,WAAU,yCACb;AAAA,yBAAC,SAAI,WAAU,aACb;AAAA,0BAAC,QAAG,WAAU,uCAAsC,kCAEpD;AAAA,MACA,oBAAC,OAAE,WAAU,yBACV,gBAAM,WAAW,gCACpB;AAAA,OACF;AAAA,IACA,oBAAC,UAAO,SAAS,OAAO,SAAQ,WAAU,uBAE1C;AAAA,KACF,GACF;AAEJ;AAEO,SAAS,kBAAkB,EAAE,SAAS,GAA4B;AACvE,SAAO,oBAAC,iBAAe,UAAS;AAClC;","names":[]}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/components/iam/permissions/permissions-page.tsx
|
|
4
|
+
import { Badge } from "@mesob/ui/components/badge";
|
|
5
|
+
import { Button } from "@mesob/ui/components/button";
|
|
6
|
+
import { useState as useState2 } from "react";
|
|
7
|
+
|
|
8
|
+
// src/provider.tsx
|
|
9
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
10
|
+
import { deepmerge } from "deepmerge-ts";
|
|
11
|
+
import createFetchClient from "openapi-fetch";
|
|
12
|
+
import createClient from "openapi-react-query";
|
|
13
|
+
import { createContext, useContext, useEffect, useState } from "react";
|
|
14
|
+
import { jsx } from "react/jsx-runtime";
|
|
15
|
+
var SessionContext = createContext(null);
|
|
16
|
+
var ApiContext = createContext(null);
|
|
17
|
+
var ConfigContext = createContext(null);
|
|
18
|
+
var queryClient = new QueryClient({
|
|
19
|
+
defaultOptions: {
|
|
20
|
+
queries: {
|
|
21
|
+
staleTime: 1e3 * 60 * 5,
|
|
22
|
+
gcTime: 1e3 * 60 * 10,
|
|
23
|
+
retry: 1,
|
|
24
|
+
refetchOnWindowFocus: false
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
function useApi() {
|
|
29
|
+
const context = useContext(ApiContext);
|
|
30
|
+
if (!context) {
|
|
31
|
+
throw new Error("useApi must be used within MesobAuthProvider");
|
|
32
|
+
}
|
|
33
|
+
return context;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/components/shared/data-table/data-table.tsx
|
|
37
|
+
import {
|
|
38
|
+
Table,
|
|
39
|
+
TableBody,
|
|
40
|
+
TableCell,
|
|
41
|
+
TableHead,
|
|
42
|
+
TableHeader,
|
|
43
|
+
TableRow
|
|
44
|
+
} from "@mesob/ui/components/table";
|
|
45
|
+
|
|
46
|
+
// src/components/skeletons/table-skeleton.tsx
|
|
47
|
+
import { Skeleton } from "@mesob/ui/components/skeleton";
|
|
48
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
49
|
+
function TableSkeleton({ columns = 5, rows = 10 }) {
|
|
50
|
+
const headerKeys = Array.from({ length: columns }, (_, i) => `header-${i}`);
|
|
51
|
+
const rowKeys = Array.from({ length: rows }, (_, i) => `row-${i}`);
|
|
52
|
+
const cellKeys = Array.from(
|
|
53
|
+
{ length: rows },
|
|
54
|
+
(_, rowIdx) => Array.from({ length: columns }, (_2, colIdx) => `cell-${rowIdx}-${colIdx}`)
|
|
55
|
+
);
|
|
56
|
+
return /* @__PURE__ */ jsxs("div", { className: "w-full space-y-4", children: [
|
|
57
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
|
|
58
|
+
/* @__PURE__ */ jsx2(Skeleton, { className: "h-8 w-48" }),
|
|
59
|
+
/* @__PURE__ */ jsx2(Skeleton, { className: "h-10 w-32" })
|
|
60
|
+
] }),
|
|
61
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-4", children: [
|
|
62
|
+
/* @__PURE__ */ jsx2(Skeleton, { className: "h-10 flex-1 max-w-sm" }),
|
|
63
|
+
/* @__PURE__ */ jsx2(Skeleton, { className: "h-10 w-24" }),
|
|
64
|
+
/* @__PURE__ */ jsx2(Skeleton, { className: "h-10 w-24" })
|
|
65
|
+
] }),
|
|
66
|
+
/* @__PURE__ */ jsxs("div", { className: "border rounded-lg overflow-hidden", children: [
|
|
67
|
+
/* @__PURE__ */ jsx2("div", { className: "flex gap-4 p-4 bg-muted", children: headerKeys.map((key) => /* @__PURE__ */ jsx2(Skeleton, { className: "h-4 flex-1" }, key)) }),
|
|
68
|
+
rowKeys.map((rowKey, rowIdx) => /* @__PURE__ */ jsx2("div", { className: "flex gap-4 p-4 border-t", children: cellKeys[rowIdx]?.map((cellKey) => /* @__PURE__ */ jsx2(Skeleton, { className: "h-4 flex-1" }, cellKey)) }, rowKey))
|
|
69
|
+
] }),
|
|
70
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
|
|
71
|
+
/* @__PURE__ */ jsx2(Skeleton, { className: "h-4 w-32" }),
|
|
72
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
73
|
+
/* @__PURE__ */ jsx2(Skeleton, { className: "h-10 w-20" }),
|
|
74
|
+
/* @__PURE__ */ jsx2(Skeleton, { className: "h-10 w-20" })
|
|
75
|
+
] })
|
|
76
|
+
] })
|
|
77
|
+
] });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/components/shared/data-table/data-table.tsx
|
|
81
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
82
|
+
function DataTable({
|
|
83
|
+
data,
|
|
84
|
+
columns,
|
|
85
|
+
isLoading,
|
|
86
|
+
onRowClick,
|
|
87
|
+
emptyMessage = "No data available",
|
|
88
|
+
actions
|
|
89
|
+
}) {
|
|
90
|
+
if (isLoading) {
|
|
91
|
+
return /* @__PURE__ */ jsx3(TableSkeleton, { columns: columns.length, rows: 5 });
|
|
92
|
+
}
|
|
93
|
+
if (data.length === 0) {
|
|
94
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center justify-center min-h-[400px] p-6", children: [
|
|
95
|
+
/* @__PURE__ */ jsx3("p", { className: "text-muted-foreground", children: emptyMessage }),
|
|
96
|
+
actions && /* @__PURE__ */ jsx3("div", { className: "mt-4", children: actions })
|
|
97
|
+
] });
|
|
98
|
+
}
|
|
99
|
+
return /* @__PURE__ */ jsxs2("div", { className: "w-full space-y-4", children: [
|
|
100
|
+
actions && /* @__PURE__ */ jsx3("div", { className: "flex justify-end", children: actions }),
|
|
101
|
+
/* @__PURE__ */ jsx3("div", { className: "border rounded-lg overflow-hidden", children: /* @__PURE__ */ jsxs2(Table, { children: [
|
|
102
|
+
/* @__PURE__ */ jsx3(TableHeader, { children: /* @__PURE__ */ jsx3(TableRow, { children: columns.map((column) => /* @__PURE__ */ jsx3(TableHead, { children: column.header }, column.key)) }) }),
|
|
103
|
+
/* @__PURE__ */ jsx3(TableBody, { children: data.map((row) => /* @__PURE__ */ jsx3(
|
|
104
|
+
TableRow,
|
|
105
|
+
{
|
|
106
|
+
onClick: () => onRowClick?.(row),
|
|
107
|
+
className: onRowClick ? "cursor-pointer hover:bg-muted/50" : "",
|
|
108
|
+
children: columns.map((column) => /* @__PURE__ */ jsx3(TableCell, { children: column.cell(row) }, `${row.id}-${column.key}`))
|
|
109
|
+
},
|
|
110
|
+
row.id
|
|
111
|
+
)) })
|
|
112
|
+
] }) })
|
|
113
|
+
] });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/components/iam/permissions/permissions-page.tsx
|
|
117
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
118
|
+
function PermissionsPage() {
|
|
119
|
+
const { hooks } = useApi();
|
|
120
|
+
const [page, setPage] = useState2(1);
|
|
121
|
+
const limit = 20;
|
|
122
|
+
const { data, isLoading, error } = hooks.useQuery("get", "/permissions", {
|
|
123
|
+
params: {
|
|
124
|
+
query: {
|
|
125
|
+
page: String(page),
|
|
126
|
+
limit: String(limit)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
const columns = [
|
|
131
|
+
{
|
|
132
|
+
key: "name",
|
|
133
|
+
header: "Permission",
|
|
134
|
+
cell: (permission) => /* @__PURE__ */ jsxs3("div", { children: [
|
|
135
|
+
/* @__PURE__ */ jsx4("p", { className: "font-medium", children: permission.name }),
|
|
136
|
+
/* @__PURE__ */ jsx4(Badge, { variant: "outline", className: "mt-1 font-mono text-xs", children: permission.code })
|
|
137
|
+
] })
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
key: "resource",
|
|
141
|
+
header: "Resource",
|
|
142
|
+
cell: (permission) => /* @__PURE__ */ jsx4(Badge, { variant: "secondary", children: permission.resource })
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
key: "action",
|
|
146
|
+
header: "Action",
|
|
147
|
+
cell: (permission) => /* @__PURE__ */ jsx4(Badge, { variant: "outline", children: permission.action })
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
key: "description",
|
|
151
|
+
header: "Description",
|
|
152
|
+
cell: (permission) => /* @__PURE__ */ jsx4("p", { className: "text-sm text-muted-foreground max-w-xs truncate", children: permission.description })
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
key: "actions",
|
|
156
|
+
header: "Actions",
|
|
157
|
+
cell: (_permission) => /* @__PURE__ */ jsx4(Button, { variant: "outline", size: "sm", children: "Edit" })
|
|
158
|
+
}
|
|
159
|
+
];
|
|
160
|
+
if (error) {
|
|
161
|
+
return /* @__PURE__ */ jsx4("div", { className: "p-6 text-center", children: /* @__PURE__ */ jsx4("p", { className: "text-destructive", children: "Error loading permissions" }) });
|
|
162
|
+
}
|
|
163
|
+
return /* @__PURE__ */ jsxs3("div", { className: "w-full p-6 space-y-4", children: [
|
|
164
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex justify-between items-center", children: [
|
|
165
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
166
|
+
/* @__PURE__ */ jsx4("h1", { className: "text-3xl font-bold", children: "Permissions" }),
|
|
167
|
+
/* @__PURE__ */ jsx4("p", { className: "text-muted-foreground", children: "Manage system permissions" })
|
|
168
|
+
] }),
|
|
169
|
+
/* @__PURE__ */ jsx4(Button, { children: "Create Permission" })
|
|
170
|
+
] }),
|
|
171
|
+
/* @__PURE__ */ jsx4(
|
|
172
|
+
DataTable,
|
|
173
|
+
{
|
|
174
|
+
data: data?.permissions || [],
|
|
175
|
+
columns,
|
|
176
|
+
isLoading,
|
|
177
|
+
emptyMessage: "No permissions found"
|
|
178
|
+
}
|
|
179
|
+
),
|
|
180
|
+
data && "permissions" in data && data.permissions && data.permissions.length >= limit && /* @__PURE__ */ jsxs3("div", { className: "flex justify-between items-center", children: [
|
|
181
|
+
/* @__PURE__ */ jsx4(
|
|
182
|
+
Button,
|
|
183
|
+
{
|
|
184
|
+
variant: "outline",
|
|
185
|
+
disabled: page === 1,
|
|
186
|
+
onClick: () => setPage(page - 1),
|
|
187
|
+
children: "Previous"
|
|
188
|
+
}
|
|
189
|
+
),
|
|
190
|
+
/* @__PURE__ */ jsxs3("span", { className: "text-sm text-muted-foreground", children: [
|
|
191
|
+
"Page ",
|
|
192
|
+
page
|
|
193
|
+
] }),
|
|
194
|
+
/* @__PURE__ */ jsx4(Button, { variant: "outline", onClick: () => setPage(page + 1), children: "Next" })
|
|
195
|
+
] })
|
|
196
|
+
] });
|
|
197
|
+
}
|
|
198
|
+
export {
|
|
199
|
+
PermissionsPage
|
|
200
|
+
};
|
|
201
|
+
//# sourceMappingURL=permissions-page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/iam/permissions/permissions-page.tsx","../../../../src/provider.tsx","../../../../src/components/shared/data-table/data-table.tsx","../../../../src/components/skeletons/table-skeleton.tsx"],"sourcesContent":["'use client';\n\nimport { Badge } from '@mesob/ui/components/badge';\nimport { Button } from '@mesob/ui/components/button';\nimport { useState } from 'react';\nimport { useApi } from '../../../provider';\nimport {\n DataTable,\n type DataTableColumn,\n} from '../../shared/data-table/data-table';\n\n// Permission type from OpenAPI schema\ntype Permission = {\n id: string;\n code: string;\n name: string;\n description: string;\n resource: string;\n action: string;\n createdAt: string;\n};\n\nexport function PermissionsPage() {\n const { hooks } = useApi();\n const [page, setPage] = useState(1);\n const limit = 20;\n\n // Use openapi-react-query hooks\n const { data, isLoading, error } = hooks.useQuery('get', '/permissions', {\n params: {\n query: {\n page: String(page),\n limit: String(limit),\n },\n },\n });\n\n const columns: DataTableColumn<Permission>[] = [\n {\n key: 'name',\n header: 'Permission',\n cell: (permission) => (\n <div>\n <p className=\"font-medium\">{permission.name}</p>\n <Badge variant=\"outline\" className=\"mt-1 font-mono text-xs\">\n {permission.code}\n </Badge>\n </div>\n ),\n },\n {\n key: 'resource',\n header: 'Resource',\n cell: (permission) => (\n <Badge variant=\"secondary\">{permission.resource}</Badge>\n ),\n },\n {\n key: 'action',\n header: 'Action',\n cell: (permission) => (\n <Badge variant=\"outline\">{permission.action}</Badge>\n ),\n },\n {\n key: 'description',\n header: 'Description',\n cell: (permission) => (\n <p className=\"text-sm text-muted-foreground max-w-xs truncate\">\n {permission.description}\n </p>\n ),\n },\n {\n key: 'actions',\n header: 'Actions',\n cell: (_permission) => (\n <Button variant=\"outline\" size=\"sm\">\n Edit\n </Button>\n ),\n },\n ];\n\n if (error) {\n return (\n <div className=\"p-6 text-center\">\n <p className=\"text-destructive\">Error loading permissions</p>\n </div>\n );\n }\n\n return (\n <div className=\"w-full p-6 space-y-4\">\n <div className=\"flex justify-between items-center\">\n <div>\n <h1 className=\"text-3xl font-bold\">Permissions</h1>\n <p className=\"text-muted-foreground\">Manage system permissions</p>\n </div>\n <Button>Create Permission</Button>\n </div>\n\n <DataTable\n data={(data as { permissions: Permission[] })?.permissions || []}\n columns={columns}\n isLoading={isLoading}\n emptyMessage=\"No permissions found\"\n />\n\n {data &&\n 'permissions' in data &&\n data.permissions &&\n (data.permissions as Permission[]).length >= limit && (\n <div className=\"flex justify-between items-center\">\n <Button\n variant=\"outline\"\n disabled={page === 1}\n onClick={() => setPage(page - 1)}\n >\n Previous\n </Button>\n <span className=\"text-sm text-muted-foreground\">Page {page}</span>\n <Button variant=\"outline\" onClick={() => setPage(page + 1)}>\n Next\n </Button>\n </div>\n )}\n </div>\n );\n}\n","'use client';\n\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport { deepmerge } from 'deepmerge-ts';\nimport createFetchClient from 'openapi-fetch';\nimport createClient from 'openapi-react-query';\nimport type { ReactNode } from 'react';\nimport { createContext, useContext, useEffect, useState } from 'react';\nimport type { paths } from './data/openapi';\nimport { createTranslator } from './lib/translations';\nimport {\n type AuthClientConfig,\n type AuthResponse,\n defaultAuthClientConfig,\n type Session,\n type User,\n} from './types';\nimport { createCustomFetch } from './utils/custom-fetch';\n\ntype OpenApiHooks = any;\n\ntype SessionState = {\n user: User | null;\n session: Session | null;\n isLoading: boolean;\n isAuthenticated: boolean;\n error: Error | null;\n};\n\ntype SessionContextValue = SessionState & {\n refresh: () => Promise<void>;\n signOut: () => Promise<void>;\n};\n\ntype ApiContextValue = {\n hooks: OpenApiHooks;\n setAuth: (auth: AuthResponse) => void;\n clearAuth: () => void;\n refresh: () => Promise<void>;\n};\n\ntype ConfigContextValue = {\n config: AuthClientConfig;\n t: (key: string, params?: Record<string, string | number>) => string;\n};\n\nconst SessionContext = createContext<SessionContextValue | null>(null);\nconst ApiContext = createContext<ApiContextValue | null>(null);\nconst ConfigContext = createContext<ConfigContextValue | null>(null);\n\nconst queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 1000 * 60 * 5,\n gcTime: 1000 * 60 * 10,\n retry: 1,\n refetchOnWindowFocus: false,\n },\n },\n});\n\nexport function useSession() {\n const context = useContext(SessionContext);\n if (!context) {\n throw new Error('useSession must be used within MesobAuthProvider');\n }\n return context;\n}\n\nexport function useApi() {\n const context = useContext(ApiContext);\n if (!context) {\n throw new Error('useApi must be used within MesobAuthProvider');\n }\n return context;\n}\n\nexport function useConfig() {\n const context = useContext(ConfigContext);\n if (!context) {\n throw new Error('useConfig must be used within MesobAuthProvider');\n }\n return context;\n}\n\ntype MesobAuthProviderProps = {\n config: AuthClientConfig;\n children: ReactNode;\n};\n\nexport function MesobAuthProvider({\n config,\n children,\n}: MesobAuthProviderProps) {\n const mergedConfig = deepmerge(\n { ...defaultAuthClientConfig } as Partial<AuthClientConfig>,\n config,\n ) as AuthClientConfig;\n\n const api = createFetchClient<paths>({\n baseUrl: mergedConfig.baseURL,\n fetch: createCustomFetch(mergedConfig),\n });\n\n const hooks = createClient(api);\n\n return (\n <QueryClientProvider client={queryClient}>\n <AuthStateProvider config={mergedConfig} hooks={hooks}>\n {children}\n </AuthStateProvider>\n </QueryClientProvider>\n );\n}\n\ntype AuthStateProviderProps = {\n config: AuthClientConfig;\n hooks: OpenApiHooks;\n children: ReactNode;\n};\n\nfunction AuthStateProvider({\n config,\n hooks,\n children,\n}: AuthStateProviderProps) {\n const [authState, setAuthState] = useState<{\n user: User | null;\n session: Session | null;\n isLoading: boolean;\n error: Error | null;\n }>({\n user: null,\n session: null,\n isLoading: false,\n error: null,\n });\n\n const {\n data: sessionData,\n isLoading: sessionLoading,\n error: sessionError,\n refetch,\n } = hooks.useQuery(\n 'get',\n '/session',\n {},\n {\n enabled: true,\n refetchOnMount: true,\n refetchOnWindowFocus: false,\n refetchOnReconnect: false,\n retry: false,\n },\n );\n\n useEffect(() => {\n if (sessionLoading) {\n setAuthState((prev) => ({ ...prev, isLoading: true }));\n return;\n }\n\n if (sessionError) {\n setAuthState({\n user: null,\n session: null,\n isLoading: false,\n error: sessionError as Error,\n });\n return;\n }\n\n if (sessionData) {\n setAuthState({\n user: sessionData.user,\n session: sessionData.session,\n isLoading: false,\n error: null,\n });\n return;\n }\n\n setAuthState({\n user: null,\n session: null,\n isLoading: false,\n error: null,\n });\n }, [sessionData, sessionLoading, sessionError]);\n\n const refresh = async () => {\n await refetch();\n };\n\n const setAuth = (auth: AuthResponse) => {\n setAuthState({\n user: auth.user,\n session: auth.session,\n isLoading: false,\n error: null,\n });\n };\n\n const clearAuth = () => {\n setAuthState({\n user: null,\n session: null,\n isLoading: false,\n error: null,\n });\n };\n\n const signOutMutation = hooks.useMutation('post', '/sign-out');\n\n const signOut = async () => {\n try {\n await signOutMutation.mutateAsync({});\n } finally {\n clearAuth();\n }\n };\n\n const t = createTranslator(config.messages || {});\n\n return (\n <ConfigContext.Provider value={{ config, t }}>\n <ApiContext.Provider value={{ hooks, setAuth, clearAuth, refresh }}>\n <SessionContext.Provider\n value={{\n user: authState.user,\n session: authState.session,\n isLoading: authState.isLoading,\n isAuthenticated: !!authState.user && !!authState.session,\n error: authState.error,\n refresh,\n signOut,\n }}\n >\n {children}\n </SessionContext.Provider>\n </ApiContext.Provider>\n </ConfigContext.Provider>\n );\n}\n","'use client';\n\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@mesob/ui/components/table';\nimport type { ReactNode } from 'react';\nimport { TableSkeleton } from '../../skeletons/table-skeleton';\n\nexport type DataTableColumn<T> = {\n key: string;\n header: string;\n cell: (row: T) => ReactNode;\n sortable?: boolean;\n};\n\ntype DataTableProps<T> = {\n data: T[];\n columns: DataTableColumn<T>[];\n isLoading?: boolean;\n onRowClick?: (row: T) => void;\n emptyMessage?: string;\n actions?: ReactNode;\n};\n\nexport function DataTable<T extends { id: string }>({\n data,\n columns,\n isLoading,\n onRowClick,\n emptyMessage = 'No data available',\n actions,\n}: DataTableProps<T>) {\n if (isLoading) {\n return <TableSkeleton columns={columns.length} rows={5} />;\n }\n\n if (data.length === 0) {\n return (\n <div className=\"flex flex-col items-center justify-center min-h-[400px] p-6\">\n <p className=\"text-muted-foreground\">{emptyMessage}</p>\n {actions && <div className=\"mt-4\">{actions}</div>}\n </div>\n );\n }\n\n return (\n <div className=\"w-full space-y-4\">\n {actions && <div className=\"flex justify-end\">{actions}</div>}\n\n <div className=\"border rounded-lg overflow-hidden\">\n <Table>\n <TableHeader>\n <TableRow>\n {columns.map((column) => (\n <TableHead key={column.key}>{column.header}</TableHead>\n ))}\n </TableRow>\n </TableHeader>\n <TableBody>\n {data.map((row) => (\n <TableRow\n key={row.id}\n onClick={() => onRowClick?.(row)}\n className={onRowClick ? 'cursor-pointer hover:bg-muted/50' : ''}\n >\n {columns.map((column) => (\n <TableCell key={`${row.id}-${column.key}`}>\n {column.cell(row)}\n </TableCell>\n ))}\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n </div>\n );\n}\n","'use client';\n\nimport { Skeleton } from '@mesob/ui/components/skeleton';\n\ntype TableSkeletonProps = {\n columns?: number;\n rows?: number;\n};\n\nexport function TableSkeleton({ columns = 5, rows = 10 }: TableSkeletonProps) {\n const headerKeys = Array.from({ length: columns }, (_, i) => `header-${i}`);\n const rowKeys = Array.from({ length: rows }, (_, i) => `row-${i}`);\n const cellKeys = Array.from({ length: rows }, (_, rowIdx) =>\n Array.from({ length: columns }, (_, colIdx) => `cell-${rowIdx}-${colIdx}`),\n );\n\n return (\n <div className=\"w-full space-y-4\">\n {/* Header */}\n <div className=\"flex justify-between items-center\">\n <Skeleton className=\"h-8 w-48\" />\n <Skeleton className=\"h-10 w-32\" />\n </div>\n\n {/* Search/filters */}\n <div className=\"flex gap-4\">\n <Skeleton className=\"h-10 flex-1 max-w-sm\" />\n <Skeleton className=\"h-10 w-24\" />\n <Skeleton className=\"h-10 w-24\" />\n </div>\n\n {/* Table */}\n <div className=\"border rounded-lg overflow-hidden\">\n {/* Table header */}\n <div className=\"flex gap-4 p-4 bg-muted\">\n {headerKeys.map((key) => (\n <Skeleton key={key} className=\"h-4 flex-1\" />\n ))}\n </div>\n\n {/* Table rows */}\n {rowKeys.map((rowKey, rowIdx) => (\n <div key={rowKey} className=\"flex gap-4 p-4 border-t\">\n {cellKeys[rowIdx]?.map((cellKey) => (\n <Skeleton key={cellKey} className=\"h-4 flex-1\" />\n ))}\n </div>\n ))}\n </div>\n\n {/* Pagination */}\n <div className=\"flex justify-between items-center\">\n <Skeleton className=\"h-4 w-32\" />\n <div className=\"flex gap-2\">\n <Skeleton className=\"h-10 w-20\" />\n <Skeleton className=\"h-10 w-20\" />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["useState","jsx","_","jsx","jsxs","jsx","jsxs","useState"]}
|