@greatapps/greatauth-ui 0.3.12 → 0.3.13

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/index.js CHANGED
@@ -1062,8 +1062,9 @@ function AppSidebar({ config }) {
1062
1062
  }
1063
1063
 
1064
1064
  // src/components/app-header.tsx
1065
- import { Fragment } from "react";
1065
+ import { Fragment, useMemo as useMemo2 } from "react";
1066
1066
  import { usePathname as usePathname2 } from "next/navigation";
1067
+ import Link2 from "next/link";
1067
1068
 
1068
1069
  // src/components/ui/breadcrumb.tsx
1069
1070
  import { Slot as Slot4 } from "radix-ui";
@@ -1103,6 +1104,21 @@ function BreadcrumbItem({ className, ...props }) {
1103
1104
  }
1104
1105
  );
1105
1106
  }
1107
+ function BreadcrumbLink({
1108
+ asChild,
1109
+ className,
1110
+ ...props
1111
+ }) {
1112
+ const Comp = asChild ? Slot4.Root : "a";
1113
+ return /* @__PURE__ */ jsx13(
1114
+ Comp,
1115
+ {
1116
+ "data-slot": "breadcrumb-link",
1117
+ className: cn("hover:text-foreground transition-colors", className),
1118
+ ...props
1119
+ }
1120
+ );
1121
+ }
1106
1122
  function BreadcrumbPage({ className, ...props }) {
1107
1123
  return /* @__PURE__ */ jsx13(
1108
1124
  "span",
@@ -1133,6 +1149,28 @@ function BreadcrumbSeparator({
1133
1149
  }
1134
1150
  );
1135
1151
  }
1152
+ function BreadcrumbEllipsis({
1153
+ className,
1154
+ ...props
1155
+ }) {
1156
+ return /* @__PURE__ */ jsxs6(
1157
+ "span",
1158
+ {
1159
+ "data-slot": "breadcrumb-ellipsis",
1160
+ role: "presentation",
1161
+ "aria-hidden": "true",
1162
+ className: cn(
1163
+ "size-5 [&>svg]:size-4 flex items-center justify-center",
1164
+ className
1165
+ ),
1166
+ ...props,
1167
+ children: [
1168
+ /* @__PURE__ */ jsx13(MoreHorizontal, {}),
1169
+ /* @__PURE__ */ jsx13("span", { className: "sr-only", children: "More" })
1170
+ ]
1171
+ }
1172
+ );
1173
+ }
1136
1174
 
1137
1175
  // src/components/theme-toggle.tsx
1138
1176
  import { useTheme } from "next-themes";
@@ -1170,14 +1208,30 @@ import { jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
1170
1208
  function AppHeader({ config }) {
1171
1209
  const pathname = usePathname2();
1172
1210
  const segments = pathname.split("/").filter(Boolean);
1173
- const breadcrumbs = segments.map((seg) => config.routeLabels[seg]).filter(Boolean);
1211
+ const breadcrumbs = useMemo2(() => {
1212
+ const items = [];
1213
+ for (let i = 0; i < segments.length; i++) {
1214
+ const label = config.routeLabels[segments[i]];
1215
+ if (label) {
1216
+ const href = "/" + segments.slice(0, i + 1).join("/");
1217
+ items.push({ label, href });
1218
+ }
1219
+ }
1220
+ return items;
1221
+ }, [segments, config.routeLabels]);
1222
+ const isLast = (i) => i === breadcrumbs.length - 1;
1223
+ const showEllipsis = breadcrumbs.length > 2;
1174
1224
  return /* @__PURE__ */ jsxs8("header", { className: "flex h-14 shrink-0 items-center gap-2 border-b px-4", children: [
1175
1225
  /* @__PURE__ */ jsx15(SidebarTrigger, { className: "-ml-1" }),
1176
1226
  /* @__PURE__ */ jsx15(Separator, { orientation: "vertical", className: "mr-2 !h-4" }),
1177
- /* @__PURE__ */ jsx15(Breadcrumb, { className: "flex-1", children: /* @__PURE__ */ jsx15(BreadcrumbList, { children: breadcrumbs.length > 0 ? breadcrumbs.map((label, i) => /* @__PURE__ */ jsxs8(Fragment, { children: [
1178
- i > 0 && /* @__PURE__ */ jsx15(BreadcrumbSeparator, {}),
1179
- /* @__PURE__ */ jsx15(BreadcrumbItem, { children: /* @__PURE__ */ jsx15(BreadcrumbPage, { children: label }) })
1180
- ] }, i)) : config.defaultBreadcrumb ? /* @__PURE__ */ jsx15(BreadcrumbItem, { children: /* @__PURE__ */ jsx15(BreadcrumbPage, { children: config.defaultBreadcrumb }) }) : null }) }),
1227
+ /* @__PURE__ */ jsx15(Breadcrumb, { className: "flex-1", children: /* @__PURE__ */ jsx15(BreadcrumbList, { children: breadcrumbs.length > 0 ? breadcrumbs.map((crumb, i) => {
1228
+ const hiddenOnMobile = showEllipsis && i < breadcrumbs.length - 2;
1229
+ return /* @__PURE__ */ jsxs8(Fragment, { children: [
1230
+ showEllipsis && i === breadcrumbs.length - 2 && /* @__PURE__ */ jsx15(BreadcrumbItem, { className: "sm:hidden", children: /* @__PURE__ */ jsx15(BreadcrumbEllipsis, {}) }),
1231
+ i > 0 && /* @__PURE__ */ jsx15(BreadcrumbSeparator, { className: hiddenOnMobile ? "hidden sm:flex" : void 0 }),
1232
+ /* @__PURE__ */ jsx15(BreadcrumbItem, { className: hiddenOnMobile ? "hidden sm:flex" : void 0, children: isLast(i) ? /* @__PURE__ */ jsx15(BreadcrumbPage, { children: crumb.label }) : /* @__PURE__ */ jsx15(BreadcrumbLink, { asChild: true, children: /* @__PURE__ */ jsx15(Link2, { href: crumb.href, children: crumb.label }) }) })
1233
+ ] }, i);
1234
+ }) : config.defaultBreadcrumb ? /* @__PURE__ */ jsx15(BreadcrumbItem, { children: /* @__PURE__ */ jsx15(BreadcrumbPage, { children: config.defaultBreadcrumb }) }) : null }) }),
1181
1235
  /* @__PURE__ */ jsx15(ThemeToggle, {})
1182
1236
  ] });
1183
1237
  }
@@ -1200,7 +1254,7 @@ function AppShell({ config, children, renderAbove }) {
1200
1254
  // src/components/login-form.tsx
1201
1255
  import { useState as useState3 } from "react";
1202
1256
  import { useRouter as useRouter2, useSearchParams } from "next/navigation";
1203
- import { Loader2, Mail, Lock, AlertCircle } from "lucide-react";
1257
+ import { Loader2, Mail, Lock, AlertCircle, Eye, EyeOff } from "lucide-react";
1204
1258
 
1205
1259
  // src/components/ui/label.tsx
1206
1260
  import { Label as LabelPrimitive } from "radix-ui";
@@ -1232,6 +1286,7 @@ function LoginForm({ config }) {
1232
1286
  const [password, setPassword] = useState3("");
1233
1287
  const [loading, setLoading] = useState3(false);
1234
1288
  const [error, setError] = useState3("");
1289
+ const [showPassword, setShowPassword] = useState3(false);
1235
1290
  const handleSubmit = async (e) => {
1236
1291
  e.preventDefault();
1237
1292
  if (loading) return;
@@ -1262,73 +1317,110 @@ function LoginForm({ config }) {
1262
1317
  setLoading(false);
1263
1318
  }
1264
1319
  };
1265
- return /* @__PURE__ */ jsx18("div", { className: "flex min-h-svh items-center justify-center bg-muted/30 p-4", children: /* @__PURE__ */ jsxs10("div", { className: "w-full max-w-[400px]", children: [
1266
- /* @__PURE__ */ jsxs10("div", { className: "mb-8 text-center", children: [
1267
- /* @__PURE__ */ jsx18("div", { className: "mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-xl bg-primary text-primary-foreground shadow-sm", children: config.icon }),
1268
- /* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-center gap-2", children: [
1269
- /* @__PURE__ */ jsx18("h1", { className: "text-2xl font-semibold tracking-tight", children: config.appName }),
1270
- config.appBadge && /* @__PURE__ */ jsx18(Badge, { variant: config.appBadge.variant, className: "text-xs", children: config.appBadge.text })
1271
- ] }),
1272
- /* @__PURE__ */ jsx18("p", { className: "mt-1.5 text-sm text-muted-foreground", children: config.description })
1273
- ] }),
1274
- /* @__PURE__ */ jsx18("div", { className: "rounded-xl border bg-card p-6 shadow-sm", children: /* @__PURE__ */ jsxs10("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
1275
- error && /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2.5 text-sm text-destructive", children: [
1276
- /* @__PURE__ */ jsx18(AlertCircle, { className: "h-4 w-4 shrink-0" }),
1277
- error
1278
- ] }),
1279
- /* @__PURE__ */ jsxs10("div", { className: "space-y-1.5", children: [
1280
- /* @__PURE__ */ jsx18(Label, { htmlFor: "login-email", className: "text-sm font-medium", children: "Email" }),
1281
- /* @__PURE__ */ jsxs10("div", { className: "relative", children: [
1282
- /* @__PURE__ */ jsx18(Mail, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
1283
- /* @__PURE__ */ jsx18(
1284
- Input,
1285
- {
1286
- id: "login-email",
1287
- type: "email",
1288
- placeholder: "email@exemplo.com",
1289
- value: email,
1290
- onChange: (e) => setEmail(e.target.value),
1291
- className: "pl-9",
1292
- autoComplete: "email",
1293
- required: true
1294
- }
1295
- )
1296
- ] })
1297
- ] }),
1298
- /* @__PURE__ */ jsxs10("div", { className: "space-y-1.5", children: [
1299
- /* @__PURE__ */ jsx18(Label, { htmlFor: "login-password", className: "text-sm font-medium", children: "Senha" }),
1300
- /* @__PURE__ */ jsxs10("div", { className: "relative", children: [
1301
- /* @__PURE__ */ jsx18(Lock, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
1302
- /* @__PURE__ */ jsx18(
1303
- Input,
1304
- {
1305
- id: "login-password",
1306
- type: "password",
1307
- placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
1308
- value: password,
1309
- onChange: (e) => setPassword(e.target.value),
1310
- className: "pl-9",
1311
- autoComplete: "current-password",
1312
- required: true
1313
- }
1314
- )
1315
- ] })
1320
+ return /* @__PURE__ */ jsxs10("div", { className: "relative flex min-h-svh", children: [
1321
+ /* @__PURE__ */ jsx18("div", { className: "fixed top-4 right-4 z-50", children: /* @__PURE__ */ jsx18(ThemeToggle, {}) }),
1322
+ /* @__PURE__ */ jsxs10(
1323
+ "div",
1324
+ {
1325
+ className: "relative hidden items-center justify-center overflow-hidden bg-primary md:flex md:w-1/2",
1326
+ style: {
1327
+ backgroundImage: "radial-gradient(circle, rgba(255,255,255,0.1) 1px, transparent 1px)",
1328
+ backgroundSize: "24px 24px"
1329
+ },
1330
+ children: [
1331
+ /* @__PURE__ */ jsx18("div", { className: "absolute inset-0 bg-gradient-to-br from-primary/80 via-primary to-primary/90" }),
1332
+ /* @__PURE__ */ jsxs10("div", { className: "relative z-10 flex flex-col items-center gap-4 px-8 text-center text-primary-foreground", children: [
1333
+ /* @__PURE__ */ jsx18("div", { className: "flex h-16 w-16 items-center justify-center rounded-2xl bg-primary-foreground/15 shadow-lg backdrop-blur-sm", children: config.icon }),
1334
+ /* @__PURE__ */ jsx18("h2", { className: "text-3xl font-bold tracking-tight", children: config.appName }),
1335
+ /* @__PURE__ */ jsx18("p", { className: "max-w-xs text-base text-primary-foreground/80", children: config.description })
1336
+ ] })
1337
+ ]
1338
+ }
1339
+ ),
1340
+ /* @__PURE__ */ jsx18("div", { className: "flex w-full items-center justify-center bg-background p-4 md:w-1/2", children: /* @__PURE__ */ jsxs10("div", { className: "w-full max-w-sm", children: [
1341
+ /* @__PURE__ */ jsxs10("div", { className: "mb-8 text-center", children: [
1342
+ /* @__PURE__ */ jsx18("div", { className: "mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-xl bg-primary text-primary-foreground shadow-sm md:hidden", children: config.icon }),
1343
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-center gap-2", children: [
1344
+ /* @__PURE__ */ jsx18("h1", { className: "text-3xl font-bold tracking-tight", children: config.appName }),
1345
+ config.appBadge && /* @__PURE__ */ jsx18(Badge, { variant: config.appBadge.variant, className: "text-xs", children: config.appBadge.text })
1346
+ ] }),
1347
+ /* @__PURE__ */ jsx18("p", { className: "mt-1.5 text-base text-muted-foreground", children: config.description })
1316
1348
  ] }),
1317
- /* @__PURE__ */ jsx18(
1318
- Button,
1319
- {
1320
- type: "submit",
1321
- className: cn("w-full", loading && "cursor-wait"),
1322
- disabled: loading,
1323
- children: loading ? /* @__PURE__ */ jsxs10(Fragment3, { children: [
1324
- /* @__PURE__ */ jsx18(Loader2, { className: "h-4 w-4 animate-spin" }),
1325
- "A entrar..."
1326
- ] }) : "Entrar"
1327
- }
1328
- )
1329
- ] }) }),
1330
- /* @__PURE__ */ jsx18("p", { className: "mt-6 text-center text-xs text-muted-foreground", children: config.footerText || "Acesso restrito a utilizadores autorizados" })
1331
- ] }) });
1349
+ /* @__PURE__ */ jsx18("div", { className: "rounded-xl border bg-card p-6 shadow-sm", children: /* @__PURE__ */ jsxs10("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
1350
+ error && /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2.5 text-sm text-destructive", children: [
1351
+ /* @__PURE__ */ jsx18(AlertCircle, { className: "h-4 w-4 shrink-0" }),
1352
+ error
1353
+ ] }),
1354
+ /* @__PURE__ */ jsxs10("div", { className: "space-y-1.5", children: [
1355
+ /* @__PURE__ */ jsx18(Label, { htmlFor: "login-email", className: "text-sm font-medium", children: "Email" }),
1356
+ /* @__PURE__ */ jsxs10("div", { className: "relative", children: [
1357
+ /* @__PURE__ */ jsx18(Mail, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
1358
+ /* @__PURE__ */ jsx18(
1359
+ Input,
1360
+ {
1361
+ id: "login-email",
1362
+ type: "email",
1363
+ placeholder: "email@exemplo.com",
1364
+ value: email,
1365
+ onChange: (e) => setEmail(e.target.value),
1366
+ className: "pl-9",
1367
+ autoComplete: "email",
1368
+ required: true
1369
+ }
1370
+ )
1371
+ ] })
1372
+ ] }),
1373
+ /* @__PURE__ */ jsxs10("div", { className: "space-y-1.5", children: [
1374
+ /* @__PURE__ */ jsx18(Label, { htmlFor: "login-password", className: "text-sm font-medium", children: "Senha" }),
1375
+ /* @__PURE__ */ jsxs10("div", { className: "relative", children: [
1376
+ /* @__PURE__ */ jsx18(Lock, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
1377
+ /* @__PURE__ */ jsx18(
1378
+ Input,
1379
+ {
1380
+ id: "login-password",
1381
+ type: showPassword ? "text" : "password",
1382
+ placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
1383
+ value: password,
1384
+ onChange: (e) => setPassword(e.target.value),
1385
+ className: "pl-9 pr-10",
1386
+ autoComplete: "current-password",
1387
+ required: true
1388
+ }
1389
+ ),
1390
+ /* @__PURE__ */ jsx18(
1391
+ "button",
1392
+ {
1393
+ type: "button",
1394
+ onClick: () => setShowPassword(!showPassword),
1395
+ className: "absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors",
1396
+ "aria-label": showPassword ? "Ocultar senha" : "Mostrar senha",
1397
+ children: showPassword ? /* @__PURE__ */ jsx18(EyeOff, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx18(Eye, { className: "h-4 w-4" })
1398
+ }
1399
+ )
1400
+ ] })
1401
+ ] }),
1402
+ /* @__PURE__ */ jsx18(
1403
+ Button,
1404
+ {
1405
+ type: "submit",
1406
+ className: cn("w-full", loading && "cursor-wait"),
1407
+ disabled: loading,
1408
+ children: loading ? /* @__PURE__ */ jsxs10(Fragment3, { children: [
1409
+ /* @__PURE__ */ jsx18(Loader2, { className: "h-4 w-4 animate-spin" }),
1410
+ "A entrar..."
1411
+ ] }) : "Entrar"
1412
+ }
1413
+ )
1414
+ ] }) }),
1415
+ /* @__PURE__ */ jsx18("p", { className: "mt-6 text-center text-xs text-muted-foreground", children: config.footerText || "Acesso restrito a utilizadores autorizados" }),
1416
+ /* @__PURE__ */ jsxs10("p", { className: "mt-1 text-center text-xs text-muted-foreground", children: [
1417
+ "\xA9 ",
1418
+ (/* @__PURE__ */ new Date()).getFullYear(),
1419
+ " ",
1420
+ config.appName
1421
+ ] })
1422
+ ] }) })
1423
+ ] });
1332
1424
  }
1333
1425
 
1334
1426
  // src/components/entity-avatar.tsx
@@ -1891,7 +1983,7 @@ function useResetPassword(config) {
1891
1983
  }
1892
1984
 
1893
1985
  // src/components/users/users-page.tsx
1894
- import { useMemo as useMemo3, useState as useState7 } from "react";
1986
+ import { useMemo as useMemo4, useState as useState7 } from "react";
1895
1987
 
1896
1988
  // src/components/users/user-form-dialog.tsx
1897
1989
  import { useEffect as useEffect3, useState as useState5 } from "react";
@@ -2580,7 +2672,7 @@ function UsersPage({ config, renderPhones }) {
2580
2672
  const [search, setSearch] = useState7("");
2581
2673
  const [profileFilter, setProfileFilter] = useState7("all");
2582
2674
  const [page, setPage] = useState7(1);
2583
- const queryParams = useMemo3(() => {
2675
+ const queryParams = useMemo4(() => {
2584
2676
  const params = {
2585
2677
  limit: String(PAGE_SIZE),
2586
2678
  page: String(page)
@@ -2597,7 +2689,7 @@ function UsersPage({ config, renderPhones }) {
2597
2689
  const [resetUser, setResetUser] = useState7(null);
2598
2690
  const users = data?.data || [];
2599
2691
  const total = data?.total || 0;
2600
- const filtered = useMemo3(() => {
2692
+ const filtered = useMemo4(() => {
2601
2693
  if (!search) return users;
2602
2694
  const term = search.toLowerCase();
2603
2695
  return users.filter(
@@ -2635,7 +2727,7 @@ function UsersPage({ config, renderPhones }) {
2635
2727
  }
2636
2728
  });
2637
2729
  }
2638
- return /* @__PURE__ */ jsxs19("div", { className: "flex flex-col gap-4 p-4", children: [
2730
+ return /* @__PURE__ */ jsxs19("div", { className: "flex flex-col gap-4 p-4 md:p-6", children: [
2639
2731
  /* @__PURE__ */ jsxs19("div", { className: "flex items-center justify-between", children: [
2640
2732
  /* @__PURE__ */ jsxs19("div", { children: [
2641
2733
  /* @__PURE__ */ jsx29("h1", { className: "text-xl font-semibold", children: "Usu\xE1rios" }),