@greatapps/greatauth-ui 0.3.6 → 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 +28 -1
- package/dist/index.js +487 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/components/entity-avatar.tsx +72 -0
- package/src/components/image-crop-upload.tsx +253 -0
- package/src/components/index.ts +4 -0
- package/src/index.ts +4 -0
- package/src/lib/image-utils.ts +82 -0
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
|
-
|
|
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
|
@@ -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,
|