@greatapps/greatauth-ui 0.3.5 → 0.3.7

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.d.ts CHANGED
@@ -1078,4 +1078,31 @@ declare function LoginForm({ config }: LoginFormProps): react_jsx_runtime.JSX.El
1078
1078
 
1079
1079
  declare function ThemeToggle(): react_jsx_runtime.JSX.Element;
1080
1080
 
1081
- export { AppHeader, AppShell, type AppShellConfig, AppSidebar, type HeaderConfig, LoginForm, type LoginFormConfig, type MenuGroup, type MenuItem, ThemeToggle, authClient, createUseAuth, signIn, signOut, signUp, useSession };
1081
+ declare const SIZE_MAP: {
1082
+ readonly xs: "h-5 w-5 text-[9px]";
1083
+ readonly sm: "h-6 w-6 text-[10px]";
1084
+ readonly md: "h-8 w-8 text-xs";
1085
+ readonly lg: "h-10 w-10 text-sm";
1086
+ readonly xl: "h-16 w-16 text-lg";
1087
+ };
1088
+ interface EntityAvatarProps {
1089
+ photo?: string | null;
1090
+ name: string | null;
1091
+ size?: keyof typeof SIZE_MAP;
1092
+ className?: string;
1093
+ }
1094
+ declare function EntityAvatar({ photo, name, size, className, }: EntityAvatarProps): react_jsx_runtime.JSX.Element;
1095
+
1096
+ interface ImageCropUploadProps {
1097
+ value?: string | null;
1098
+ onChange: (url: string) => void;
1099
+ onRemove?: () => void;
1100
+ entityType: string;
1101
+ entityId?: number | string;
1102
+ idAccount: number;
1103
+ name?: string | null;
1104
+ disabled?: boolean;
1105
+ }
1106
+ declare function ImageCropUpload({ value, onChange, onRemove, entityType, entityId, idAccount, name, disabled, }: ImageCropUploadProps): react_jsx_runtime.JSX.Element;
1107
+
1108
+ export { AppHeader, AppShell, type AppShellConfig, AppSidebar, EntityAvatar, type EntityAvatarProps, type HeaderConfig, ImageCropUpload, type ImageCropUploadProps, LoginForm, type LoginFormConfig, type MenuGroup, type MenuItem, ThemeToggle, authClient, createUseAuth, signIn, signOut, signUp, useSession };
package/dist/index.js CHANGED
@@ -954,13 +954,13 @@ function Badge({
954
954
 
955
955
  // src/components/app-sidebar.tsx
956
956
  import { jsx as jsx12, jsxs as jsxs5 } from "react/jsx-runtime";
957
- function getUserInitials(name, email, showLastName) {
957
+ function getUserInitials(name, email) {
958
958
  if (!name) return email?.[0]?.toUpperCase() || "?";
959
959
  const parts = name.trim().split(/\s+/);
960
- if (showLastName && parts.length > 1) {
960
+ if (parts.length > 1) {
961
961
  return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
962
962
  }
963
- return (parts[0][0] + (email?.[0] || "")).toUpperCase();
963
+ return parts[0][0].toUpperCase();
964
964
  }
965
965
  function SimpleMenuItem({ item, pathname }) {
966
966
  const isActive = pathname.startsWith(item.href);
@@ -993,7 +993,7 @@ function AppSidebar({ config }) {
993
993
  const userName = session?.user?.name || "";
994
994
  const userEmail = session?.user?.email || "";
995
995
  const userImage = session?.user?.image || "";
996
- const initials = getUserInitials(userName, userEmail, config.userDisplayFields?.showLastName);
996
+ const initials = getUserInitials(userName, userEmail);
997
997
  return /* @__PURE__ */ jsxs5(Sidebar, { children: [
998
998
  /* @__PURE__ */ jsx12(SidebarHeader, { children: /* @__PURE__ */ jsx12(SidebarMenu, { children: /* @__PURE__ */ jsx12(SidebarMenuItem, { children: /* @__PURE__ */ jsx12(SidebarMenuButton, { size: "lg", asChild: true, children: /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", children: [
999
999
  config.appIcon,
@@ -1312,10 +1312,497 @@ function LoginForm({ config }) {
1312
1312
  /* @__PURE__ */ jsx18("p", { className: "mt-6 text-center text-xs text-muted-foreground", children: config.footerText || "Acesso restrito a utilizadores autorizados" })
1313
1313
  ] }) });
1314
1314
  }
1315
+
1316
+ // src/components/entity-avatar.tsx
1317
+ import { jsx as jsx19, jsxs as jsxs11 } from "react/jsx-runtime";
1318
+ var COLORS = [
1319
+ "bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300",
1320
+ "bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-300",
1321
+ "bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300",
1322
+ "bg-emerald-100 text-emerald-700 dark:bg-emerald-900 dark:text-emerald-300",
1323
+ "bg-cyan-100 text-cyan-700 dark:bg-cyan-900 dark:text-cyan-300",
1324
+ "bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300",
1325
+ "bg-violet-100 text-violet-700 dark:bg-violet-900 dark:text-violet-300",
1326
+ "bg-pink-100 text-pink-700 dark:bg-pink-900 dark:text-pink-300"
1327
+ ];
1328
+ var SIZE_MAP = {
1329
+ xs: "h-5 w-5 text-[9px]",
1330
+ sm: "h-6 w-6 text-[10px]",
1331
+ md: "h-8 w-8 text-xs",
1332
+ lg: "h-10 w-10 text-sm",
1333
+ xl: "h-16 w-16 text-lg"
1334
+ };
1335
+ function hashCode(str) {
1336
+ let hash = 0;
1337
+ for (let i = 0; i < str.length; i++) {
1338
+ hash = str.charCodeAt(i) + ((hash << 5) - hash);
1339
+ }
1340
+ return Math.abs(hash);
1341
+ }
1342
+ function getInitials(name) {
1343
+ const parts = name.trim().split(/\s+/);
1344
+ if (parts.length >= 2) {
1345
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
1346
+ }
1347
+ return (name[0] || "?").toUpperCase();
1348
+ }
1349
+ function EntityAvatar({
1350
+ photo,
1351
+ name,
1352
+ size = "md",
1353
+ className
1354
+ }) {
1355
+ const displayName = name || "?";
1356
+ const color = COLORS[hashCode(displayName) % COLORS.length];
1357
+ const initials = getInitials(displayName);
1358
+ return /* @__PURE__ */ jsxs11(Avatar, { className: cn(SIZE_MAP[size], className), children: [
1359
+ photo && /* @__PURE__ */ jsx19(
1360
+ AvatarImage,
1361
+ {
1362
+ src: photo,
1363
+ alt: displayName,
1364
+ className: "object-cover"
1365
+ }
1366
+ ),
1367
+ /* @__PURE__ */ jsx19(AvatarFallback, { className: cn("font-medium", color), children: initials })
1368
+ ] });
1369
+ }
1370
+
1371
+ // src/components/image-crop-upload.tsx
1372
+ import { useCallback as useCallback3, useRef, useState as useState4 } from "react";
1373
+ import Cropper from "react-easy-crop";
1374
+
1375
+ // src/components/ui/dialog.tsx
1376
+ import { Dialog as DialogPrimitive } from "radix-ui";
1377
+ import { X as X2 } from "lucide-react";
1378
+ import { jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
1379
+ function Dialog({
1380
+ ...props
1381
+ }) {
1382
+ return /* @__PURE__ */ jsx20(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
1383
+ }
1384
+ function DialogPortal({
1385
+ ...props
1386
+ }) {
1387
+ return /* @__PURE__ */ jsx20(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props });
1388
+ }
1389
+ function DialogOverlay({
1390
+ className,
1391
+ ...props
1392
+ }) {
1393
+ return /* @__PURE__ */ jsx20(
1394
+ DialogPrimitive.Overlay,
1395
+ {
1396
+ "data-slot": "dialog-overlay",
1397
+ className: cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50", className),
1398
+ ...props
1399
+ }
1400
+ );
1401
+ }
1402
+ function DialogContent({
1403
+ className,
1404
+ children,
1405
+ showCloseButton = true,
1406
+ ...props
1407
+ }) {
1408
+ return /* @__PURE__ */ jsxs12(DialogPortal, { children: [
1409
+ /* @__PURE__ */ jsx20(DialogOverlay, {}),
1410
+ /* @__PURE__ */ jsxs12(
1411
+ DialogPrimitive.Content,
1412
+ {
1413
+ "data-slot": "dialog-content",
1414
+ className: cn(
1415
+ "bg-background data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 grid max-w-[calc(100%-2rem)] gap-6 rounded-xl p-6 text-sm ring-1 duration-100 sm:max-w-md fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 outline-none",
1416
+ className
1417
+ ),
1418
+ ...props,
1419
+ children: [
1420
+ children,
1421
+ showCloseButton && /* @__PURE__ */ jsx20(DialogPrimitive.Close, { "data-slot": "dialog-close", asChild: true, children: /* @__PURE__ */ jsxs12(Button, { variant: "ghost", className: "absolute top-4 right-4", size: "icon-sm", children: [
1422
+ /* @__PURE__ */ jsx20(X2, {}),
1423
+ /* @__PURE__ */ jsx20("span", { className: "sr-only", children: "Close" })
1424
+ ] }) })
1425
+ ]
1426
+ }
1427
+ )
1428
+ ] });
1429
+ }
1430
+ function DialogHeader({ className, ...props }) {
1431
+ return /* @__PURE__ */ jsx20(
1432
+ "div",
1433
+ {
1434
+ "data-slot": "dialog-header",
1435
+ className: cn("gap-2 flex flex-col", className),
1436
+ ...props
1437
+ }
1438
+ );
1439
+ }
1440
+ function DialogFooter({
1441
+ className,
1442
+ children,
1443
+ ...props
1444
+ }) {
1445
+ return /* @__PURE__ */ jsx20(
1446
+ "div",
1447
+ {
1448
+ "data-slot": "dialog-footer",
1449
+ className: cn(
1450
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
1451
+ className
1452
+ ),
1453
+ ...props,
1454
+ children
1455
+ }
1456
+ );
1457
+ }
1458
+ function DialogTitle({
1459
+ className,
1460
+ ...props
1461
+ }) {
1462
+ return /* @__PURE__ */ jsx20(
1463
+ DialogPrimitive.Title,
1464
+ {
1465
+ "data-slot": "dialog-title",
1466
+ className: cn("leading-none font-medium", className),
1467
+ ...props
1468
+ }
1469
+ );
1470
+ }
1471
+
1472
+ // src/components/ui/slider.tsx
1473
+ import * as React2 from "react";
1474
+ import { Slider as SliderPrimitive } from "radix-ui";
1475
+ import { jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
1476
+ function Slider({
1477
+ className,
1478
+ defaultValue,
1479
+ value,
1480
+ min = 0,
1481
+ max = 100,
1482
+ ...props
1483
+ }) {
1484
+ const _values = React2.useMemo(
1485
+ () => Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max],
1486
+ [value, defaultValue, min, max]
1487
+ );
1488
+ return /* @__PURE__ */ jsxs13(
1489
+ SliderPrimitive.Root,
1490
+ {
1491
+ "data-slot": "slider",
1492
+ defaultValue,
1493
+ value,
1494
+ min,
1495
+ max,
1496
+ className: cn(
1497
+ "data-vertical:min-h-40 relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-vertical:h-full data-vertical:w-auto data-vertical:flex-col",
1498
+ className
1499
+ ),
1500
+ ...props,
1501
+ children: [
1502
+ /* @__PURE__ */ jsx21(
1503
+ SliderPrimitive.Track,
1504
+ {
1505
+ "data-slot": "slider-track",
1506
+ className: "bg-muted rounded-full data-horizontal:h-1.5 data-vertical:w-1.5 relative grow overflow-hidden data-horizontal:w-full data-vertical:h-full",
1507
+ children: /* @__PURE__ */ jsx21(
1508
+ SliderPrimitive.Range,
1509
+ {
1510
+ "data-slot": "slider-range",
1511
+ className: "bg-primary absolute select-none data-horizontal:h-full data-vertical:w-full"
1512
+ }
1513
+ )
1514
+ }
1515
+ ),
1516
+ Array.from({ length: _values.length }, (_, index) => /* @__PURE__ */ jsx21(
1517
+ SliderPrimitive.Thumb,
1518
+ {
1519
+ "data-slot": "slider-thumb",
1520
+ className: "border-primary ring-ring/50 size-4 rounded-full border bg-white shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden block shrink-0 select-none disabled:pointer-events-none disabled:opacity-50"
1521
+ },
1522
+ index
1523
+ ))
1524
+ ]
1525
+ }
1526
+ );
1527
+ }
1528
+
1529
+ // src/components/image-crop-upload.tsx
1530
+ import { toast } from "sonner";
1531
+ import { Camera, Trash2 } from "lucide-react";
1532
+
1533
+ // src/lib/image-utils.ts
1534
+ async function getCroppedImage(imageSrc, cropArea, outputSize = 400, quality = 0.85) {
1535
+ const image = await loadImage(imageSrc);
1536
+ const canvas = document.createElement("canvas");
1537
+ canvas.width = outputSize;
1538
+ canvas.height = outputSize;
1539
+ const ctx = canvas.getContext("2d");
1540
+ if (!ctx) throw new Error("Canvas context not available");
1541
+ ctx.drawImage(
1542
+ image,
1543
+ cropArea.x,
1544
+ cropArea.y,
1545
+ cropArea.width,
1546
+ cropArea.height,
1547
+ 0,
1548
+ 0,
1549
+ outputSize,
1550
+ outputSize
1551
+ );
1552
+ const blob = await new Promise((resolve, reject) => {
1553
+ canvas.toBlob(
1554
+ (b) => b ? resolve(b) : reject(new Error("Failed to create blob")),
1555
+ "image/webp",
1556
+ quality
1557
+ );
1558
+ });
1559
+ return blobToBase64(blob);
1560
+ }
1561
+ function loadImage(src) {
1562
+ return new Promise((resolve, reject) => {
1563
+ const img = new Image();
1564
+ img.crossOrigin = "anonymous";
1565
+ img.onload = () => resolve(img);
1566
+ img.onerror = reject;
1567
+ img.src = src;
1568
+ });
1569
+ }
1570
+ function blobToBase64(blob) {
1571
+ return new Promise((resolve, reject) => {
1572
+ const reader = new FileReader();
1573
+ reader.onload = () => {
1574
+ const result = reader.result;
1575
+ resolve(result.split(",")[1]);
1576
+ };
1577
+ reader.onerror = reject;
1578
+ reader.readAsDataURL(blob);
1579
+ });
1580
+ }
1581
+ var ACCEPTED_TYPES = ["image/png", "image/jpeg", "image/jpg", "image/webp"];
1582
+ var MAX_SIZE_BYTES = 5 * 1024 * 1024;
1583
+ function validateImageFile(file) {
1584
+ if (!ACCEPTED_TYPES.includes(file.type)) {
1585
+ return "Formato n\xE3o suportado. Use PNG, JPG ou WEBP";
1586
+ }
1587
+ if (file.size > MAX_SIZE_BYTES) {
1588
+ return "Imagem muito grande. M\xE1ximo: 5MB";
1589
+ }
1590
+ return null;
1591
+ }
1592
+ function fileToDataUrl(file) {
1593
+ return new Promise((resolve, reject) => {
1594
+ const reader = new FileReader();
1595
+ reader.onload = () => resolve(reader.result);
1596
+ reader.onerror = reject;
1597
+ reader.readAsDataURL(file);
1598
+ });
1599
+ }
1600
+
1601
+ // src/components/image-crop-upload.tsx
1602
+ import { jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
1603
+ var STORAGE_URL_MAP = {
1604
+ users: "https://gauth-r2-storage.greatlabs.workers.dev",
1605
+ agents: "https://gagents-r2-storage.greatlabs.workers.dev",
1606
+ contacts: "https://gchat-r2-storage.greatlabs.workers.dev",
1607
+ professionals: "https://gclinic-r2-storage.greatlabs.workers.dev",
1608
+ patients: "https://gclinic-r2-storage.greatlabs.workers.dev",
1609
+ clinics: "https://gclinic-r2-storage.greatlabs.workers.dev"
1610
+ };
1611
+ var BUCKET_MAP = {
1612
+ users: "gauth-r1-storage",
1613
+ agents: "gagents-r1-storage",
1614
+ contacts: "gchat-r1-storage",
1615
+ professionals: "gclinic-r1-storage",
1616
+ patients: "gclinic-r1-storage",
1617
+ clinics: "gclinic-r1-storage"
1618
+ };
1619
+ var DEFAULT_STORAGE_URL = "https://gauth-r2-storage.greatlabs.workers.dev";
1620
+ var DEFAULT_BUCKET = "gauth-r1-storage";
1621
+ function ImageCropUpload({
1622
+ value,
1623
+ onChange,
1624
+ onRemove,
1625
+ entityType,
1626
+ entityId,
1627
+ idAccount,
1628
+ name,
1629
+ disabled = false
1630
+ }) {
1631
+ const [imageSrc, setImageSrc] = useState4(null);
1632
+ const [crop, setCrop] = useState4({ x: 0, y: 0 });
1633
+ const [zoom, setZoom] = useState4(1);
1634
+ const [croppedArea, setCroppedArea] = useState4(null);
1635
+ const [isOpen, setIsOpen] = useState4(false);
1636
+ const [isUploading, setIsUploading] = useState4(false);
1637
+ const inputRef = useRef(null);
1638
+ const onCropComplete = useCallback3((_, croppedAreaPixels) => {
1639
+ setCroppedArea(croppedAreaPixels);
1640
+ }, []);
1641
+ const handleFileSelect = useCallback3(
1642
+ async (e) => {
1643
+ const file = e.target.files?.[0];
1644
+ if (!file) return;
1645
+ const error = validateImageFile(file);
1646
+ if (error) {
1647
+ toast.error(error);
1648
+ if (inputRef.current) inputRef.current.value = "";
1649
+ return;
1650
+ }
1651
+ const dataUrl = await fileToDataUrl(file);
1652
+ setImageSrc(dataUrl);
1653
+ setCrop({ x: 0, y: 0 });
1654
+ setZoom(1);
1655
+ setIsOpen(true);
1656
+ if (inputRef.current) inputRef.current.value = "";
1657
+ },
1658
+ []
1659
+ );
1660
+ const handleConfirmCrop = useCallback3(async () => {
1661
+ if (!imageSrc || !croppedArea) return;
1662
+ setIsUploading(true);
1663
+ try {
1664
+ const base64 = await getCroppedImage(imageSrc, croppedArea);
1665
+ const path = `${entityType}/${idAccount}/${entityId || "temp"}.webp`;
1666
+ const storageUrl = STORAGE_URL_MAP[entityType] || DEFAULT_STORAGE_URL;
1667
+ const bucket = BUCKET_MAP[entityType] || DEFAULT_BUCKET;
1668
+ const res = await fetch(storageUrl, {
1669
+ method: "POST",
1670
+ headers: { "Content-Type": "application/json" },
1671
+ body: JSON.stringify({ file: base64, bucket, path })
1672
+ });
1673
+ const data = await res.json();
1674
+ if (data.status !== 1) {
1675
+ throw new Error(data.message || "Erro ao enviar imagem");
1676
+ }
1677
+ const publicUrl = `${storageUrl}/${path}?t=${Date.now()}`;
1678
+ onChange(publicUrl);
1679
+ toast.success("Imagem atualizada");
1680
+ } catch (err) {
1681
+ toast.error(
1682
+ err instanceof Error ? err.message : "Erro ao enviar imagem"
1683
+ );
1684
+ } finally {
1685
+ setIsUploading(false);
1686
+ setIsOpen(false);
1687
+ setImageSrc(null);
1688
+ }
1689
+ }, [imageSrc, croppedArea, entityType, entityId, idAccount, onChange]);
1690
+ const handleRemove = useCallback3(async () => {
1691
+ if (!value) return;
1692
+ try {
1693
+ const path = `${entityType}/${idAccount}/${entityId || "temp"}.webp`;
1694
+ const storageUrl = STORAGE_URL_MAP[entityType] || DEFAULT_STORAGE_URL;
1695
+ const bucket = BUCKET_MAP[entityType] || DEFAULT_BUCKET;
1696
+ await fetch(storageUrl, {
1697
+ method: "DELETE",
1698
+ headers: { "Content-Type": "application/json" },
1699
+ body: JSON.stringify({ bucket, path })
1700
+ });
1701
+ onRemove?.();
1702
+ toast.success("Imagem removida");
1703
+ } catch {
1704
+ toast.error("Erro ao remover imagem");
1705
+ }
1706
+ }, [value, entityType, entityId, idAccount, onRemove]);
1707
+ return /* @__PURE__ */ jsxs14("div", { className: "flex flex-col items-center gap-2", children: [
1708
+ /* @__PURE__ */ jsxs14("div", { className: "relative group", children: [
1709
+ /* @__PURE__ */ jsx22(EntityAvatar, { photo: value, name: name ?? null, size: "xl" }),
1710
+ !disabled && /* @__PURE__ */ jsx22(
1711
+ "button",
1712
+ {
1713
+ type: "button",
1714
+ onClick: () => inputRef.current?.click(),
1715
+ className: "absolute inset-0 flex items-center justify-center rounded-full bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer",
1716
+ children: /* @__PURE__ */ jsx22(Camera, { className: "h-5 w-5 text-white" })
1717
+ }
1718
+ )
1719
+ ] }),
1720
+ /* @__PURE__ */ jsx22(
1721
+ "input",
1722
+ {
1723
+ ref: inputRef,
1724
+ type: "file",
1725
+ accept: "image/png,image/jpeg,image/webp",
1726
+ onChange: handleFileSelect,
1727
+ className: "hidden",
1728
+ disabled
1729
+ }
1730
+ ),
1731
+ !disabled && value && /* @__PURE__ */ jsxs14(
1732
+ Button,
1733
+ {
1734
+ type: "button",
1735
+ variant: "ghost",
1736
+ size: "sm",
1737
+ onClick: handleRemove,
1738
+ className: "text-destructive hover:text-destructive",
1739
+ children: [
1740
+ /* @__PURE__ */ jsx22(Trash2, { className: "h-4 w-4 mr-1" }),
1741
+ "Remover"
1742
+ ]
1743
+ }
1744
+ ),
1745
+ /* @__PURE__ */ jsx22(Dialog, { open: isOpen, onOpenChange: setIsOpen, children: /* @__PURE__ */ jsxs14(DialogContent, { className: "sm:max-w-md", children: [
1746
+ /* @__PURE__ */ jsx22(DialogHeader, { children: /* @__PURE__ */ jsx22(DialogTitle, { children: "Recortar imagem" }) }),
1747
+ /* @__PURE__ */ jsx22("div", { className: "relative w-full h-72 bg-muted rounded-md overflow-hidden", children: imageSrc && /* @__PURE__ */ jsx22(
1748
+ Cropper,
1749
+ {
1750
+ image: imageSrc,
1751
+ crop,
1752
+ zoom,
1753
+ aspect: 1,
1754
+ onCropChange: setCrop,
1755
+ onZoomChange: setZoom,
1756
+ onCropComplete,
1757
+ cropShape: "round",
1758
+ showGrid: false
1759
+ }
1760
+ ) }),
1761
+ /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-3 px-1", children: [
1762
+ /* @__PURE__ */ jsx22("span", { className: "text-sm text-muted-foreground whitespace-nowrap", children: "Zoom" }),
1763
+ /* @__PURE__ */ jsx22(
1764
+ Slider,
1765
+ {
1766
+ value: [zoom],
1767
+ onValueChange: (v) => setZoom(v[0]),
1768
+ min: 1,
1769
+ max: 3,
1770
+ step: 0.1,
1771
+ className: "flex-1"
1772
+ }
1773
+ )
1774
+ ] }),
1775
+ /* @__PURE__ */ jsxs14(DialogFooter, { children: [
1776
+ /* @__PURE__ */ jsx22(
1777
+ Button,
1778
+ {
1779
+ variant: "outline",
1780
+ onClick: () => {
1781
+ setIsOpen(false);
1782
+ setImageSrc(null);
1783
+ },
1784
+ disabled: isUploading,
1785
+ children: "Cancelar"
1786
+ }
1787
+ ),
1788
+ /* @__PURE__ */ jsx22(
1789
+ Button,
1790
+ {
1791
+ onClick: handleConfirmCrop,
1792
+ disabled: isUploading || !croppedArea,
1793
+ children: isUploading ? "Enviando..." : "Confirmar"
1794
+ }
1795
+ )
1796
+ ] })
1797
+ ] }) })
1798
+ ] });
1799
+ }
1315
1800
  export {
1316
1801
  AppHeader,
1317
1802
  AppShell,
1318
1803
  AppSidebar,
1804
+ EntityAvatar,
1805
+ ImageCropUpload,
1319
1806
  LoginForm,
1320
1807
  SidebarInset,
1321
1808
  SidebarProvider,