@greatapps/common 1.1.459 → 1.1.460
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/widgets/ImageUpload/ImageCropModal.mjs +77 -32
- package/dist/components/widgets/ImageUpload/ImageCropModal.mjs.map +1 -1
- package/dist/components/widgets/ImageUpload/ImageTooSmallModal.mjs +44 -0
- package/dist/components/widgets/ImageUpload/ImageTooSmallModal.mjs.map +1 -0
- package/dist/components/widgets/ImageUpload/index.mjs +2 -0
- package/dist/components/widgets/ImageUpload/index.mjs.map +1 -1
- package/dist/index.mjs +9 -2
- package/dist/index.mjs.map +1 -1
- package/dist/modules/images/hooks/use-image-upload.hook.mjs +51 -2
- package/dist/modules/images/hooks/use-image-upload.hook.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/widgets/ImageUpload/ImageCropModal.tsx +82 -32
- package/src/components/widgets/ImageUpload/ImageTooSmallModal.tsx +58 -0
- package/src/components/widgets/ImageUpload/index.ts +1 -0
- package/src/index.ts +9 -2
- package/src/modules/images/hooks/use-image-upload.hook.ts +72 -2
|
@@ -70,6 +70,41 @@ const baseCropStyles = `
|
|
|
70
70
|
border-radius: 50%;
|
|
71
71
|
pointer-events: none;
|
|
72
72
|
}
|
|
73
|
+
|
|
74
|
+
/* Bolinhas centrais nas bordas */
|
|
75
|
+
.custom-crop-area .ReactCrop__crop-selection .crop-handle {
|
|
76
|
+
position: absolute;
|
|
77
|
+
width: 10px;
|
|
78
|
+
height: 10px;
|
|
79
|
+
background: white;
|
|
80
|
+
border-radius: 50%;
|
|
81
|
+
pointer-events: none;
|
|
82
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.custom-crop-area .ReactCrop__crop-selection .crop-handle.top {
|
|
86
|
+
top: -5px;
|
|
87
|
+
left: 50%;
|
|
88
|
+
transform: translateX(-50%);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.custom-crop-area .ReactCrop__crop-selection .crop-handle.bottom {
|
|
92
|
+
bottom: -5px;
|
|
93
|
+
left: 50%;
|
|
94
|
+
transform: translateX(-50%);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.custom-crop-area .ReactCrop__crop-selection .crop-handle.left {
|
|
98
|
+
left: -5px;
|
|
99
|
+
top: 50%;
|
|
100
|
+
transform: translateY(-50%);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.custom-crop-area .ReactCrop__crop-selection .crop-handle.right {
|
|
104
|
+
right: -5px;
|
|
105
|
+
top: 50%;
|
|
106
|
+
transform: translateY(-50%);
|
|
107
|
+
}
|
|
73
108
|
`;
|
|
74
109
|
function ImageCropModal({
|
|
75
110
|
open,
|
|
@@ -77,12 +112,12 @@ function ImageCropModal({
|
|
|
77
112
|
imageSrc,
|
|
78
113
|
aspectRatio,
|
|
79
114
|
onConfirm,
|
|
115
|
+
onCancel,
|
|
80
116
|
showCircleGuide,
|
|
81
117
|
minWidth,
|
|
82
118
|
minHeight
|
|
83
119
|
}) {
|
|
84
120
|
const imgRef = useRef(null);
|
|
85
|
-
const containerRef = useRef(null);
|
|
86
121
|
const [crop, setCrop] = useState();
|
|
87
122
|
const [imageSize, setImageSize] = useState({ width: 0, height: 0 });
|
|
88
123
|
const onImageLoad = (e) => {
|
|
@@ -100,13 +135,17 @@ function ImageCropModal({
|
|
|
100
135
|
);
|
|
101
136
|
setCrop(initialCrop);
|
|
102
137
|
};
|
|
103
|
-
const minCropWidth = minWidth && imageSize.width > 0 ? minWidth / imageSize.width * 100 : void 0;
|
|
104
|
-
const minCropHeight = minHeight && imageSize.height > 0 ? minHeight / imageSize.height * 100 : void 0;
|
|
138
|
+
const minCropWidth = minWidth && imageSize.width > 0 ? Math.min(minWidth / imageSize.width * 100, 100) : void 0;
|
|
139
|
+
const minCropHeight = minHeight && imageSize.height > 0 ? Math.min(minHeight / imageSize.height * 100, 100) : void 0;
|
|
105
140
|
const handleConfirm = () => {
|
|
106
141
|
if (imgRef.current && crop) {
|
|
107
142
|
onConfirm(imgRef.current, crop);
|
|
108
143
|
}
|
|
109
144
|
};
|
|
145
|
+
const handleCancel = () => {
|
|
146
|
+
onCancel?.();
|
|
147
|
+
onOpenChange(false);
|
|
148
|
+
};
|
|
110
149
|
const shouldShowCircle = showCircleGuide ?? aspectRatio === 1;
|
|
111
150
|
const renderSelectionAddon = () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
112
151
|
/* @__PURE__ */ jsxs("div", { className: "crop-grid", children: [
|
|
@@ -115,38 +154,44 @@ function ImageCropModal({
|
|
|
115
154
|
/* @__PURE__ */ jsx("div", { className: "crop-grid-line vertical v1" }),
|
|
116
155
|
/* @__PURE__ */ jsx("div", { className: "crop-grid-line vertical v2" })
|
|
117
156
|
] }),
|
|
118
|
-
shouldShowCircle && /* @__PURE__ */ jsx("div", { className: "crop-circle" })
|
|
157
|
+
shouldShowCircle && /* @__PURE__ */ jsx("div", { className: "crop-circle" }),
|
|
158
|
+
/* @__PURE__ */ jsx("div", { className: "crop-handle top" }),
|
|
159
|
+
/* @__PURE__ */ jsx("div", { className: "crop-handle bottom" }),
|
|
160
|
+
/* @__PURE__ */ jsx("div", { className: "crop-handle left" }),
|
|
161
|
+
/* @__PURE__ */ jsx("div", { className: "crop-handle right" })
|
|
119
162
|
] });
|
|
120
163
|
return /* @__PURE__ */ jsxs(Dialog, { open, onOpenChange, children: [
|
|
121
164
|
/* @__PURE__ */ jsx("style", { children: baseCropStyles }),
|
|
122
|
-
/* @__PURE__ */ jsxs(DialogContent, { className: "max-w-
|
|
123
|
-
/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: "Ajustar imagem" }) }),
|
|
124
|
-
/* @__PURE__ */
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
165
|
+
/* @__PURE__ */ jsxs(DialogContent, { className: "w-full md:w-[650px]! lg:w-[650px]! max-w-[650px]! max-[650px]:w-[calc(100%-32px)] p-0 gap-0", children: [
|
|
166
|
+
/* @__PURE__ */ jsx(DialogHeader, { className: "p-5", children: /* @__PURE__ */ jsx(DialogTitle, { className: "text-center text-[16px]!", children: "Ajustar imagem" }) }),
|
|
167
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 px-5 pb-5", children: [
|
|
168
|
+
/* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx(
|
|
169
|
+
ReactCrop,
|
|
170
|
+
{
|
|
171
|
+
crop,
|
|
172
|
+
onChange: (c) => setCrop(c),
|
|
173
|
+
aspect: aspectRatio,
|
|
174
|
+
className: `max-h-[50vh] custom-crop-area ${shouldShowCircle ? "with-circle" : ""}`,
|
|
175
|
+
keepSelection: true,
|
|
176
|
+
minWidth: minCropWidth,
|
|
177
|
+
minHeight: minCropHeight,
|
|
178
|
+
renderSelectionAddon,
|
|
179
|
+
children: /* @__PURE__ */ jsx(
|
|
180
|
+
"img",
|
|
181
|
+
{
|
|
182
|
+
ref: imgRef,
|
|
183
|
+
src: imageSrc,
|
|
184
|
+
alt: "Crop",
|
|
185
|
+
onLoad: onImageLoad,
|
|
186
|
+
className: "max-h-[50vh] object-contain"
|
|
187
|
+
}
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
) }),
|
|
191
|
+
/* @__PURE__ */ jsx("p", { className: "paragraph-small-regular text-zinc-500 text-center", children: "Arraste para ajustar a \xE1rea de corte" })
|
|
192
|
+
] }),
|
|
193
|
+
/* @__PURE__ */ jsxs(DialogFooter, { className: "flex-row items-center justify-end gap-3 p-5 border-t border-zinc-200", children: [
|
|
194
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: handleCancel, children: "Cancelar" }),
|
|
150
195
|
/* @__PURE__ */ jsx(Button, { onClick: handleConfirm, children: "Confirmar" })
|
|
151
196
|
] })
|
|
152
197
|
] })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/components/widgets/ImageUpload/ImageCropModal.tsx"],"sourcesContent":["'use client'\n\nimport { useRef, useState } from 'react'\nimport ReactCrop, {\n type Crop,\n centerCrop,\n makeAspectCrop,\n} from 'react-image-crop'\nimport 'react-image-crop/dist/ReactCrop.css'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../../ui/overlay/Dialog'\nimport { Button } from '../../ui/buttons/Button'\n\n// CSS customizado para grid pontilhado fino e círculo\nconst baseCropStyles = `\n .custom-crop-area .ReactCrop__crop-selection {\n border: 2px solid white !important;\n box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5) !important;\n }\n\n /* Grid de 9 quadrados - linhas pontilhadas finas */\n .custom-crop-area .ReactCrop__crop-selection .crop-grid {\n position: absolute;\n inset: 0;\n pointer-events: none;\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line {\n position: absolute;\n background: repeating-linear-gradient(\n to right,\n rgba(255, 255, 255, 0.5) 0,\n rgba(255, 255, 255, 0.5) 4px,\n transparent 4px,\n transparent 8px\n );\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.horizontal {\n left: 0;\n right: 0;\n height: 1px;\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.vertical {\n top: 0;\n bottom: 0;\n width: 1px;\n background: repeating-linear-gradient(\n to bottom,\n rgba(255, 255, 255, 0.5) 0,\n rgba(255, 255, 255, 0.5) 4px,\n transparent 4px,\n transparent 8px\n );\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.h1 { top: 33.33%; }\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.h2 { top: 66.66%; }\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.v1 { left: 33.33%; }\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.v2 { left: 66.66%; }\n\n /* Círculo inscrito */\n .custom-crop-area.with-circle .ReactCrop__crop-selection .crop-circle {\n position: absolute;\n inset: 0;\n border: 1px dashed rgba(255, 255, 255, 0.6);\n border-radius: 50%;\n pointer-events: none;\n }\n`\n\ninterface ImageCropModalProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n imageSrc: string\n aspectRatio: number\n onConfirm: (image: HTMLImageElement, crop: Crop) => void\n showCircleGuide?: boolean\n minWidth?: number\n minHeight?: number\n}\n\nexport function ImageCropModal({\n open,\n onOpenChange,\n imageSrc,\n aspectRatio,\n onConfirm,\n showCircleGuide,\n minWidth,\n minHeight,\n}: ImageCropModalProps) {\n const imgRef = useRef<HTMLImageElement>(null)\n const containerRef = useRef<HTMLDivElement>(null)\n const [crop, setCrop] = useState<Crop>()\n const [imageSize, setImageSize] = useState({ width: 0, height: 0 })\n\n const onImageLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {\n const { naturalWidth, naturalHeight } = e.currentTarget\n setImageSize({ width: naturalWidth, height: naturalHeight })\n\n const initialCrop = centerCrop(\n makeAspectCrop(\n { unit: '%', width: 90 },\n aspectRatio,\n naturalWidth,\n naturalHeight\n ),\n naturalWidth,\n naturalHeight\n )\n\n setCrop(initialCrop)\n }\n\n // Calcula tamanho mínimo em porcentagem baseado nas dimensões mínimas em pixels\n const minCropWidth = minWidth && imageSize.width > 0\n ? (minWidth / imageSize.width) * 100\n : undefined\n const minCropHeight = minHeight && imageSize.height > 0\n ? (minHeight / imageSize.height) * 100\n : undefined\n\n const handleConfirm = () => {\n if (imgRef.current && crop) {\n onConfirm(imgRef.current, crop)\n }\n }\n\n // Determina se deve mostrar círculo (aspect ratio 1:1 por padrão)\n const shouldShowCircle = showCircleGuide ?? aspectRatio === 1\n\n // Renderiza grid e círculo dentro da área de crop\n const renderSelectionAddon = () => (\n <>\n {/* Grid de 9 quadrados com linhas pontilhadas */}\n <div className=\"crop-grid\">\n <div className=\"crop-grid-line horizontal h1\" />\n <div className=\"crop-grid-line horizontal h2\" />\n <div className=\"crop-grid-line vertical v1\" />\n <div className=\"crop-grid-line vertical v2\" />\n </div>\n {/* Círculo inscrito */}\n {shouldShowCircle && <div className=\"crop-circle\" />}\n </>\n )\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <style>{baseCropStyles}</style>\n <DialogContent className=\"max-w-2xl\">\n <DialogHeader>\n <DialogTitle>Ajustar imagem</DialogTitle>\n </DialogHeader>\n\n <div ref={containerRef} className=\"flex justify-center py-4 relative\">\n <ReactCrop\n crop={crop}\n onChange={(c) => setCrop(c)}\n aspect={aspectRatio}\n className={`max-h-[60vh] custom-crop-area ${shouldShowCircle ? 'with-circle' : ''}`}\n keepSelection\n minWidth={minCropWidth}\n minHeight={minCropHeight}\n renderSelectionAddon={renderSelectionAddon}\n >\n <img\n ref={imgRef}\n src={imageSrc}\n alt=\"Crop\"\n onLoad={onImageLoad}\n className=\"max-h-[60vh] object-contain\"\n />\n </ReactCrop>\n </div>\n\n <p className=\"paragraph-small-regular text-zinc-500 text-center\">\n Arraste para ajustar a área de corte\n </p>\n\n <DialogFooter>\n <Button variant=\"secondary\" onClick={() => onOpenChange(false)}>\n Cancelar\n </Button>\n <Button onClick={handleConfirm}>Confirmar</Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/widgets/ImageUpload/ImageCropModal.tsx"],"sourcesContent":["'use client'\n\nimport { useRef, useState } from 'react'\nimport ReactCrop, {\n type Crop,\n centerCrop,\n makeAspectCrop,\n} from 'react-image-crop'\nimport 'react-image-crop/dist/ReactCrop.css'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../../ui/overlay/Dialog'\nimport { Button } from '../../ui/buttons/Button'\n\n// CSS customizado para grid pontilhado fino e círculo\nconst baseCropStyles = `\n .custom-crop-area .ReactCrop__crop-selection {\n border: 2px solid white !important;\n box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5) !important;\n }\n\n /* Grid de 9 quadrados - linhas pontilhadas finas */\n .custom-crop-area .ReactCrop__crop-selection .crop-grid {\n position: absolute;\n inset: 0;\n pointer-events: none;\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line {\n position: absolute;\n background: repeating-linear-gradient(\n to right,\n rgba(255, 255, 255, 0.5) 0,\n rgba(255, 255, 255, 0.5) 4px,\n transparent 4px,\n transparent 8px\n );\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.horizontal {\n left: 0;\n right: 0;\n height: 1px;\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.vertical {\n top: 0;\n bottom: 0;\n width: 1px;\n background: repeating-linear-gradient(\n to bottom,\n rgba(255, 255, 255, 0.5) 0,\n rgba(255, 255, 255, 0.5) 4px,\n transparent 4px,\n transparent 8px\n );\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.h1 { top: 33.33%; }\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.h2 { top: 66.66%; }\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.v1 { left: 33.33%; }\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.v2 { left: 66.66%; }\n\n /* Círculo inscrito */\n .custom-crop-area.with-circle .ReactCrop__crop-selection .crop-circle {\n position: absolute;\n inset: 0;\n border: 1px dashed rgba(255, 255, 255, 0.6);\n border-radius: 50%;\n pointer-events: none;\n }\n\n /* Bolinhas centrais nas bordas */\n .custom-crop-area .ReactCrop__crop-selection .crop-handle {\n position: absolute;\n width: 10px;\n height: 10px;\n background: white;\n border-radius: 50%;\n pointer-events: none;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-handle.top {\n top: -5px;\n left: 50%;\n transform: translateX(-50%);\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-handle.bottom {\n bottom: -5px;\n left: 50%;\n transform: translateX(-50%);\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-handle.left {\n left: -5px;\n top: 50%;\n transform: translateY(-50%);\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-handle.right {\n right: -5px;\n top: 50%;\n transform: translateY(-50%);\n }\n`\n\ninterface ImageCropModalProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n imageSrc: string\n aspectRatio: number\n onConfirm: (image: HTMLImageElement, crop: Crop) => void\n onCancel?: () => void\n showCircleGuide?: boolean\n minWidth?: number\n minHeight?: number\n}\n\nexport function ImageCropModal({\n open,\n onOpenChange,\n imageSrc,\n aspectRatio,\n onConfirm,\n onCancel,\n showCircleGuide,\n minWidth,\n minHeight,\n}: ImageCropModalProps) {\n const imgRef = useRef<HTMLImageElement>(null)\n const [crop, setCrop] = useState<Crop>()\n const [imageSize, setImageSize] = useState({ width: 0, height: 0 })\n\n const onImageLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {\n const { naturalWidth, naturalHeight } = e.currentTarget\n setImageSize({ width: naturalWidth, height: naturalHeight })\n\n const initialCrop = centerCrop(\n makeAspectCrop(\n { unit: '%', width: 90 },\n aspectRatio,\n naturalWidth,\n naturalHeight\n ),\n naturalWidth,\n naturalHeight\n )\n\n setCrop(initialCrop)\n }\n\n // Calcula tamanho mínimo em porcentagem baseado nas dimensões mínimas em pixels\n const minCropWidth = minWidth && imageSize.width > 0\n ? Math.min((minWidth / imageSize.width) * 100, 100)\n : undefined\n const minCropHeight = minHeight && imageSize.height > 0\n ? Math.min((minHeight / imageSize.height) * 100, 100)\n : undefined\n\n const handleConfirm = () => {\n if (imgRef.current && crop) {\n onConfirm(imgRef.current, crop)\n }\n }\n\n const handleCancel = () => {\n onCancel?.()\n onOpenChange(false)\n }\n\n // Determina se deve mostrar círculo (aspect ratio 1:1 por padrão)\n const shouldShowCircle = showCircleGuide ?? aspectRatio === 1\n\n // Renderiza grid, círculo e bolinhas dentro da área de crop\n const renderSelectionAddon = () => (\n <>\n {/* Grid de 9 quadrados com linhas pontilhadas */}\n <div className=\"crop-grid\">\n <div className=\"crop-grid-line horizontal h1\" />\n <div className=\"crop-grid-line horizontal h2\" />\n <div className=\"crop-grid-line vertical v1\" />\n <div className=\"crop-grid-line vertical v2\" />\n </div>\n {/* Círculo inscrito */}\n {shouldShowCircle && <div className=\"crop-circle\" />}\n {/* Bolinhas centrais nas bordas */}\n <div className=\"crop-handle top\" />\n <div className=\"crop-handle bottom\" />\n <div className=\"crop-handle left\" />\n <div className=\"crop-handle right\" />\n </>\n )\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <style>{baseCropStyles}</style>\n <DialogContent className=\"w-full md:w-[650px]! lg:w-[650px]! max-w-[650px]! max-[650px]:w-[calc(100%-32px)] p-0 gap-0\">\n <DialogHeader className=\"p-5\">\n <DialogTitle className=\"text-center text-[16px]!\">\n Ajustar imagem\n </DialogTitle>\n </DialogHeader>\n\n <div className=\"flex flex-col gap-4 px-5 pb-5\">\n <div className=\"flex justify-center\">\n <ReactCrop\n crop={crop}\n onChange={(c) => setCrop(c)}\n aspect={aspectRatio}\n className={`max-h-[50vh] custom-crop-area ${shouldShowCircle ? 'with-circle' : ''}`}\n keepSelection\n minWidth={minCropWidth}\n minHeight={minCropHeight}\n renderSelectionAddon={renderSelectionAddon}\n >\n <img\n ref={imgRef}\n src={imageSrc}\n alt=\"Crop\"\n onLoad={onImageLoad}\n className=\"max-h-[50vh] object-contain\"\n />\n </ReactCrop>\n </div>\n\n <p className=\"paragraph-small-regular text-zinc-500 text-center\">\n Arraste para ajustar a área de corte\n </p>\n </div>\n\n <DialogFooter className=\"flex-row items-center justify-end gap-3 p-5 border-t border-zinc-200\">\n <Button variant=\"secondary\" onClick={handleCancel}>\n Cancelar\n </Button>\n <Button onClick={handleConfirm}>Confirmar</Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import {
|
|
4
|
+
Dialog,
|
|
5
|
+
DialogContent,
|
|
6
|
+
DialogFooter,
|
|
7
|
+
DialogHeader,
|
|
8
|
+
DialogTitle
|
|
9
|
+
} from "../../ui/overlay/Dialog";
|
|
10
|
+
import { Button } from "../../ui/buttons/Button";
|
|
11
|
+
function ImageTooSmallModal({
|
|
12
|
+
open,
|
|
13
|
+
onOpenChange,
|
|
14
|
+
fileName,
|
|
15
|
+
minWidth,
|
|
16
|
+
minHeight,
|
|
17
|
+
onSelectAnother,
|
|
18
|
+
onCancel
|
|
19
|
+
}) {
|
|
20
|
+
const dimensionText = `${minWidth}x${minHeight}px`;
|
|
21
|
+
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { className: "w-full md:w-[480px]! lg:w-[480px]! max-w-[480px]! max-[480px]:w-[calc(100%-32px)] p-0 gap-0", children: [
|
|
22
|
+
/* @__PURE__ */ jsx(DialogHeader, { className: "p-5", children: /* @__PURE__ */ jsxs(DialogTitle, { className: "text-center text-[16px]!", children: [
|
|
23
|
+
"Foto menor que ",
|
|
24
|
+
dimensionText
|
|
25
|
+
] }) }),
|
|
26
|
+
/* @__PURE__ */ jsx("div", { className: "px-5 pb-5", children: /* @__PURE__ */ jsxs("p", { className: "paragraph-small-regular text-zinc-600 text-center", children: [
|
|
27
|
+
'O arquivo "',
|
|
28
|
+
fileName,
|
|
29
|
+
'" \xE9 menor que ',
|
|
30
|
+
dimensionText,
|
|
31
|
+
". Fa\xE7a envio de uma imagem com pelo menos ",
|
|
32
|
+
dimensionText,
|
|
33
|
+
" para continuar."
|
|
34
|
+
] }) }),
|
|
35
|
+
/* @__PURE__ */ jsxs(DialogFooter, { className: "flex-row items-center justify-end gap-3 p-5 border-t border-zinc-200", children: [
|
|
36
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onCancel, children: "Cancelar" }),
|
|
37
|
+
/* @__PURE__ */ jsx(Button, { onClick: onSelectAnother, children: "Enviar outra" })
|
|
38
|
+
] })
|
|
39
|
+
] }) });
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
ImageTooSmallModal
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=ImageTooSmallModal.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/widgets/ImageUpload/ImageTooSmallModal.tsx"],"sourcesContent":["'use client'\n\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../../ui/overlay/Dialog'\nimport { Button } from '../../ui/buttons/Button'\n\ninterface ImageTooSmallModalProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n fileName: string\n minWidth: number\n minHeight: number\n onSelectAnother: () => void\n onCancel: () => void\n}\n\nexport function ImageTooSmallModal({\n open,\n onOpenChange,\n fileName,\n minWidth,\n minHeight,\n onSelectAnother,\n onCancel,\n}: ImageTooSmallModalProps) {\n const dimensionText = `${minWidth}x${minHeight}px`\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"w-full md:w-[480px]! lg:w-[480px]! max-w-[480px]! max-[480px]:w-[calc(100%-32px)] p-0 gap-0\">\n <DialogHeader className=\"p-5\">\n <DialogTitle className=\"text-center text-[16px]!\">\n Foto menor que {dimensionText}\n </DialogTitle>\n </DialogHeader>\n\n <div className=\"px-5 pb-5\">\n <p className=\"paragraph-small-regular text-zinc-600 text-center\">\n O arquivo \"{fileName}\" é menor que {dimensionText}. Faça envio de\n uma imagem com pelo menos {dimensionText} para continuar.\n </p>\n </div>\n\n <DialogFooter className=\"flex-row items-center justify-end gap-3 p-5 border-t border-zinc-200\">\n <Button variant=\"secondary\" onClick={onCancel}>\n Cancelar\n </Button>\n <Button onClick={onSelectAnother}>Enviar outra</Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n"],"mappings":";AAmCQ,cACE,YADF;AAjCR;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,cAAc;AAYhB,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA4B;AAC1B,QAAM,gBAAgB,GAAG,QAAQ,IAAI,SAAS;AAE9C,SACE,oBAAC,UAAO,MAAY,cAClB,+BAAC,iBAAc,WAAU,+FACvB;AAAA,wBAAC,gBAAa,WAAU,OACtB,+BAAC,eAAY,WAAU,4BAA2B;AAAA;AAAA,MAChC;AAAA,OAClB,GACF;AAAA,IAEA,oBAAC,SAAI,WAAU,aACb,+BAAC,OAAE,WAAU,qDAAoD;AAAA;AAAA,MACnD;AAAA,MAAS;AAAA,MAAe;AAAA,MAAc;AAAA,MACvB;AAAA,MAAc;AAAA,OAC3C,GACF;AAAA,IAEA,qBAAC,gBAAa,WAAU,wEACtB;AAAA,0BAAC,UAAO,SAAQ,aAAY,SAAS,UAAU,sBAE/C;AAAA,MACA,oBAAC,UAAO,SAAS,iBAAiB,0BAAY;AAAA,OAChD;AAAA,KACF,GACF;AAEJ;","names":[]}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { ImageUpload } from "./ImageUpload";
|
|
2
2
|
import { ImageCropModal } from "./ImageCropModal";
|
|
3
|
+
import { ImageTooSmallModal } from "./ImageTooSmallModal";
|
|
3
4
|
export {
|
|
4
5
|
ImageCropModal,
|
|
6
|
+
ImageTooSmallModal,
|
|
5
7
|
ImageUpload
|
|
6
8
|
};
|
|
7
9
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/components/widgets/ImageUpload/index.ts"],"sourcesContent":["export { ImageUpload } from './ImageUpload'\nexport { ImageCropModal } from './ImageCropModal'\n"],"mappings":"AAAA,SAAS,mBAAmB;AAC5B,SAAS,sBAAsB;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/widgets/ImageUpload/index.ts"],"sourcesContent":["export { ImageUpload } from './ImageUpload'\nexport { ImageCropModal } from './ImageCropModal'\nexport { ImageTooSmallModal } from './ImageTooSmallModal'\n"],"mappings":"AAAA,SAAS,mBAAmB;AAC5B,SAAS,sBAAsB;AAC/B,SAAS,0BAA0B;","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -282,8 +282,14 @@ import {
|
|
|
282
282
|
NOTIFICATION_TYPES,
|
|
283
283
|
LANGUAGE_OPTIONS as LANGUAGE_OPTIONS2
|
|
284
284
|
} from "./components/account/constants";
|
|
285
|
-
import {
|
|
286
|
-
|
|
285
|
+
import {
|
|
286
|
+
ImageUpload,
|
|
287
|
+
ImageCropModal,
|
|
288
|
+
ImageTooSmallModal
|
|
289
|
+
} from "./components/widgets/ImageUpload";
|
|
290
|
+
import {
|
|
291
|
+
useImageUpload
|
|
292
|
+
} from "./modules/images/hooks/use-image-upload.hook";
|
|
287
293
|
import {
|
|
288
294
|
ACCEPTED_IMAGE_FORMATS,
|
|
289
295
|
MAX_FILE_SIZE_MB
|
|
@@ -371,6 +377,7 @@ export {
|
|
|
371
377
|
FrillEmbed,
|
|
372
378
|
GENDER_OPTIONS,
|
|
373
379
|
ImageCropModal,
|
|
380
|
+
ImageTooSmallModal,
|
|
374
381
|
ImageUpload,
|
|
375
382
|
Input,
|
|
376
383
|
InputOTP,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Types\nexport * from \"./modules/auth/schema\";\nexport * from \"./modules/users/schema\";\nexport * from \"./modules/whitelabel/schema\";\n// Style types now come from whitelabel schema directly\nexport * from \"./infra/api/types\";\n\n// Contexts\nexport * from \"./providers/query.provider\";\nexport * from \"./providers/auth.provider\";\nexport * from \"./providers/whitelabel.provider\";\n\n// Hooks\nexport {\n useListProjectUsers,\n LIST_PROJECT_USERS_QUERY_KEY,\n LIST_PROJECT_USERS_BASE_KEY,\n} from \"./modules/projects/hooks/list-project-users.hook\";\nexport {\n useListAvailableUsers,\n LIST_AVAILABLE_USERS_QUERY_KEY,\n LIST_AVAILABLE_USERS_BASE_KEY,\n} from \"./modules/projects/hooks/list-available-users.hook\";\nexport type {\n ProjectUser,\n AccountUser,\n AddRemoveProjectUsersParams,\n ListUsersParams,\n UsersPage,\n} from \"./modules/projects/types\";\nexport {\n useUserQuery,\n useUserValidateSession,\n useInvalidateUser,\n useSetUserData,\n useTwoFactorVerify,\n USER_QUERY_KEY,\n} from \"./modules/auth/hooks/useUserQuery\";\nexport { useManagementPermissions } from \"./modules/auth/hooks/useManagementPermissions\";\nexport type { ManagementPermission } from \"./modules/auth/types/management-permission.type\";\nexport { useSubscriptions } from \"./modules/subscriptions/hooks/list-subscriptions.hook\";\nexport { SUBSCRIPTIONS_QUERY_KEY } from \"./modules/subscriptions/constants/query-keys.constants\";\nexport { useActiveSubscription } from \"./modules/subscriptions/hooks/find-active-subscription.hook\";\nexport { useCancelledSubscriptionGuard } from \"./modules/subscriptions/hooks/use-cancelled-subscription-guard\";\nexport { usePlanById } from \"./modules/plans/hooks/use-plan-by-id.hook\";\nexport { useHasPlanAddon } from \"./modules/plans/hooks/use-has-plan-addon.hook\";\nexport { useCalculateSubscription } from \"./modules/subscriptions/hooks/calculate-subscription.hook\";\nexport { useUpdateSubscriptionPlan } from \"./modules/subscriptions/hooks/update-subscription-plan.hook\";\nexport { useCards, CARDS_QUERY_KEY } from \"./modules/cards/hooks/cards.hook\";\nexport { useCreateCard } from \"./modules/cards/hooks/create-card.hook\";\nexport { useIaCredits } from \"./modules/ia-credits/hooks/ia-credits.hook\";\nexport type {\n IaCreditsSummary,\n IaCreditOperation,\n} from \"./modules/ia-credits/types\";\nexport type {\n Subscription,\n SubscriptionItem,\n FindSubscriptionsParams,\n} from \"./modules/subscriptions/types/subscription.type\";\nexport {\n SubscriptionSchema,\n SubscriptionItemSchema,\n} from \"./modules/subscriptions/types/subscription.type\";\nexport { ADDON_IDS, AI_CREDIT_OPTIONS, AI_CREDIT_VALUES } from \"./modules/subscriptions/constants/addons.constants\";\nexport type { AddonKindEnum, AiCreditOption } from \"./modules/subscriptions/constants/addons.constants\";\nexport type { Plan, PlanItem, UiPlan, PlanFeature, PlanMainFeatures, PlanPricingByPeriod, PlanTooltips } from \"./modules/plans/types/plan.type\";\nexport { PlanSchema, PlanItemSchema } from \"./modules/plans/types/plan.type\";\nexport { mapApiPlanToUiPlan } from \"./modules/plans/utils/map-api-plan-to-ui\";\nexport type { PlanBillingPeriod } from \"./modules/plans/utils/map-api-plan-to-ui\";\nexport { usePlans, PLANS_QUERY_KEY } from \"./modules/plans/hooks/list-plans.hook\";\nexport type { BillingPeriod } from \"./modules/subscriptions/types/billing-period.type\";\nexport type {\n CalculateSubscriptionRequest,\n CalculateSubscriptionResponse,\n UpdateSubscriptionPlanRequest,\n} from \"./modules/subscriptions/types/calculate-subscription.type\";\nexport type { Card as PaymentCard, CreateCardRequest } from \"./modules/cards/types\";\nexport { CardSchema as PaymentCardSchema, CreateCardRequestSchema } from \"./modules/cards/types\";\nexport { buildPlanExtras } from \"./modules/subscriptions/utils/build-plan-extras\";\nexport { hasSubscriptionExpired } from \"./modules/subscriptions/utils/has-subscription-expired\";\nexport { getPriceFromCalculatedData, periodicityToBillingPeriod } from \"./modules/subscriptions/utils/periodicity\";\nexport { useBuyCreditsModal } from \"./store/useBuyCreditsModal\";\nexport { usePaidPlanRequiredModal } from \"./store/usePaidPlanRequiredModal\";\nexport { default as BuyCreditsModal } from \"./components/modals/BuyCreditsModal\";\nexport { default as PaidPlanRequiredModal } from \"./components/modals/PaidPlanRequiredModal\";\nexport { default as AddCardModal } from \"./components/modals/cards/AddCardModal\";\nexport { CardFormFields, cardFormSchema } from \"./components/modals/cards/CardFormFields\";\nexport type { CardFormData } from \"./components/modals/cards/CardFormFields\";\nexport { Skeleton } from \"./components/ui/feedback/Skeleton\";\nexport { PaymentInfoCard } from \"./components/ui/data-display/PaymentInfoCard\";\nexport { CardBrandIcon } from \"./components/ui/data-display/CardBrandIcons\";\nexport { CardItem } from \"./components/ui/data-display/CardItem\";\n\n// Providers\nexport { QueryProvider } from \"./providers/query.provider\";\n\n// Middleware\nexport { createAuthMiddleware } from \"./middlewares/create-auth-middleware\";\nexport { createMiddlewareChain } from \"./middlewares/chain\";\nexport { continueChain, stopChain } from \"./middlewares/types\";\nexport type {\n MiddlewareFunction,\n MiddlewareConfig,\n MiddlewareResult,\n} from \"./middlewares/types\";\n\n// Utils\nexport { cn } from \"./infra/utils/clsx\";\nexport { formatShortDate, formatDateTime } from \"./infra/utils/date\";\nexport { buildQueryParams } from \"./infra/utils/params\";\nexport { parseSchema, parseResult } from \"./infra/utils/parser\";\nexport { withAction } from \"./utils/withAction\";\n\n// Layout\nexport {\n MainLayout,\n MainLayoutContent,\n MainLayoutSpacer,\n MainLayoutMain,\n} from \"./components/layouts/MainLayout\";\nexport { NavBar } from \"./components/layouts/NavBar\";\nexport type { NavBarProps } from \"./components/layouts/NavBar\";\nexport { AppNavBar } from \"./components/layouts/AppNavBar\";\nexport type { AppNavBarProps } from \"./components/layouts/AppNavBar\";\nexport { AppMobileNavBar } from \"./components/layouts/AppMobileNavBar\";\nexport type { AppMobileNavBarProps } from \"./components/layouts/AppMobileNavBar\";\nexport { SideBarNavigation } from \"./components/layouts/SideBarNavigation\";\nexport { MdSideBarNavigation } from \"./components/layouts/MdSideBarNavigation\";\nexport { default as NotificationCard } from \"./components/widgets/notifications/NotificationCard\";\nexport type { NotificationCardProps } from \"./components/widgets/notifications/NotificationCard\";\nexport { NotificationsPopover } from \"./components/layouts/NotificationsPopover\";\nexport type { NotificationsPopoverProps } from \"./components/layouts/NotificationsPopover\";\nexport { NotificationPageContent } from \"./components/pages/notifications/Notifications\";\nexport { NotFoundPage } from \"./components/pages/NotFoundPage\";\nexport { UsersSelectorPopover } from \"./components/layouts/UsersSelectorPopover\";\nexport type { UsersSelectorPopoverProps } from \"./components/layouts/UsersSelectorPopover\";\nexport { ProfilePopover } from \"./components/layouts/ProfilePopover\";\nexport type {\n ProfilePopoverProps,\n ProfileMenuItem,\n} from \"./components/layouts/ProfilePopover\";\nexport { default as WhitelabelCodes } from \"./components/layouts/WhitelabelCodes\";\nexport { NavBarItem } from \"./components/layouts/NavBarItem\";\nexport type { NavBarItemProps } from \"./components/layouts/NavBarItem\";\n\n// Navigation\nexport { AppNavigation } from './components/navigation/AppNavigation';\nexport { CancelledSubscriptionBanner } from './components/navigation/CancelledSubscriptionBanner';\nexport type { NavItemConfig } from './components/navigation/subcomponents/NavItems';\nexport type { NavigationProject } from './components/navigation/types';\nexport { useMobileNavbarSheet } from './store/useMobileNavbarSheet';\n\n// Hooks\nexport { default as useCopyToClipboard } from \"./hooks/copy-to-clipboard.hook\";\n\n// UI - Buttons\nexport { Button, buttonVariants } from \"./components/ui/buttons/Button\";\nexport { CopyButton } from \"./components/ui/buttons/CopyButton\";\nexport { default as CancelButton } from \"./components/ui/buttons/CancelButton\";\n\n// UI - Data Display\nexport {\n Accordion,\n AccordionItem,\n AccordionTrigger,\n AccordionContent,\n} from \"./components/ui/data-display/Accordion\";\nexport { Badge, badgeVariants } from \"./components/ui/data-display/Badge\";\nexport {\n Card,\n CardHeader,\n CardFooter,\n CardTitle,\n CardAction,\n CardDescription,\n CardContent,\n} from \"./components/ui/data-display/Card\";\nexport {\n Pagination,\n PaginationContent,\n PaginationLink,\n PaginationItem,\n PaginationPrevious,\n PaginationNext,\n PaginationEllipsis,\n} from \"./components/ui/data-display/Pagination\";\nexport { ScrollArea, ScrollBar } from \"./components/ui/data-display/ScrollArea\";\nexport { Separator } from \"./components/ui/data-display/Separator\";\nexport {\n Table,\n TableHeader,\n TableBody,\n TableFooter,\n TableHead,\n TableRow,\n TableCell,\n TableCaption,\n} from \"./components/ui/data-display/Table\";\nexport {\n Tabs,\n TabsList,\n TabsTrigger,\n TabsContent,\n} from \"./components/ui/data-display/Tabs\";\nexport { UserAvatar } from \"./components/ui/data-display/UserAvatar\";\nexport type { UserAvatarProps } from \"./components/ui/data-display/UserAvatar\";\n\n// UI - Feedback\nexport { default as CircularProgress } from \"./components/ui/feedback/CircularProgress\";\nexport { default as DefaultCircularProgress } from \"./components/ui/feedback/DefaultCircularProgress\";\nexport { Progress } from \"./components/ui/feedback/Progress\";\nexport { LoadingOverlay } from \"./components/ui/feedback/LoadingOverlay\";\nexport {\n Toast,\n toastVariants,\n toastIconContainerVariants,\n} from \"./components/ui/feedback/Toast\";\nexport type { ToastProps } from \"./components/ui/feedback/Toast\";\n\n// UI - Form\nexport { Calendar, CalendarDayButton } from \"./components/ui/form/Calendar\";\nexport { Checkbox } from \"./components/ui/form/Checkbox\";\nexport { DatePicker } from \"./components/ui/form/DatePicker\";\nexport { DateRangePicker } from \"./components/ui/form/DateRangePicker\";\nexport { Input } from \"./components/ui/form/Input\";\nexport {\n InputOTP,\n InputOTPGroup,\n InputOTPSlot,\n InputOTPSeparator,\n} from \"./components/ui/form/InputOtp\";\nexport { RadioGroup, RadioGroupItem } from \"./components/ui/form/RadioGroup\";\nexport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectScrollDownButton,\n SelectScrollUpButton,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n} from \"./components/ui/form/Select\";\nexport { Switch } from \"./components/ui/form/Switch\";\nexport { Textarea } from \"./components/ui/form/Textarea\";\n\n// UI - Overlay\nexport {\n Command,\n CommandDialog,\n CommandInput,\n CommandList,\n CommandEmpty,\n CommandGroup,\n CommandItem,\n CommandShortcut,\n CommandSeparator,\n} from \"./components/ui/overlay/Command\";\nexport {\n Dialog,\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogOverlay,\n DialogPortal,\n DialogTitle,\n DialogTrigger,\n} from \"./components/ui/overlay/Dialog\";\nexport {\n Popover,\n PopoverTrigger,\n PopoverContent,\n PopoverAnchor,\n} from \"./components/ui/overlay/Popover\";\nexport {\n Sheet,\n SheetTrigger,\n SheetClose,\n SheetContent,\n SheetHeader,\n SheetFooter,\n SheetTitle,\n SheetDescription,\n} from \"./components/ui/overlay/Sheet\";\nexport {\n Tooltip,\n TooltipTrigger,\n TooltipContent,\n TooltipProvider,\n} from \"./components/ui/overlay/Tooltip\";\n\n// Embeds\nexport { FrillEmbed } from \"./components/embeds/FrillEmbed\";\nexport { CrispEmbed, openCrispHelpdesk, hideCrisp, showCrisp, updateCrispUser } from \"./components/embeds/CrispEmbed\";\nexport { EmbedWidgets } from \"./components/embeds/EmbedWidgets\";\n\n// Store\nexport { useMdSidebarStore } from \"./store/useMdSidebarStore\";\nexport { useDesktopSidebarStore } from \"./store/useDesktopSidebarStore\";\nexport { useModalManager } from \"./store/useModalManager\";\nexport type { ModalData } from \"./store/useModalManager\";\n\n// Enums\nexport { AccountSectionType } from \"./enums/AccountSectionType\";\n\n// Hooks\nexport { default as usePasswordVisibility } from \"./hooks/usePasswordVisibility\";\nexport { default as useCountdownTimer } from \"./hooks/useCountdownTimer\";\nexport { default as useIsMobile } from \"./hooks/useIsMobile\";\nexport { useDebounce } from \"./hooks/useDebounce\";\nexport { useDebouncedEffect } from \"./hooks/useDebouncedEffect\";\nexport { useDebounceState } from \"./hooks/useDebounceState\";\n\n// Utils\nexport {\n formatPhone,\n formatTimer,\n formatFullName,\n formatCPF,\n formatCNPJ,\n formatCardNumber,\n} from \"./utils/format/masks\";\nexport { formatCurrencyNumber } from \"./utils/format/currency\";\nexport { BR_STATE_OPTIONS } from \"./utils/constants/br-states\";\nexport type { TimezoneOption } from \"./modules/accounts/services/timezone.service\";\nexport {\n buildLocaleOptions,\n LANGUAGE_OPTIONS as LOCALE_LANGUAGE_OPTIONS,\n} from \"./utils/intl/locales\";\nexport type { LocaleOption } from \"./utils/intl/locales\";\nexport { copyToClipboard, readFromClipboard } from \"./utils/browser/clipboard\";\n\n// UI - Form (new widgets)\nexport { FormField } from \"./components/ui/form/FormField\";\nexport { SelectField } from \"./components/ui/form/SelectField\";\nexport { ComboboxField } from \"./components/ui/form/ComboboxField\";\nexport type { ComboboxOption } from \"./components/ui/form/ComboboxField\";\nexport { default as PhoneInput } from \"./components/ui/form/PhoneInput\";\nexport { default as SwitchOptionFieldWithIcon } from \"./components/ui/form/SwitchOptionFieldWithIcon\";\nexport { TextAreaField } from \"./components/ui/form/TextAreaField\";\n\n// Account Module - Hooks\nexport {\n useCurrentAccount,\n ACCOUNT_QUERY_KEY,\n} from \"./modules/accounts/hooks/current-account.hook\";\nexport {\n useUpdateAccount,\n useUpdateAccountUser,\n useUpdateAccountUserById,\n useDeleteAccountUser,\n useDeleteAccount,\n} from \"./modules/accounts/hooks/useAccountManagement\";\nexport { useViaCep } from \"./modules/accounts/hooks/useViaCep\";\n\n// Account Types\nexport type {\n UpdateAccountRequest,\n UpdateAccountUserRequest,\n ChangePasswordRequest,\n DeleteAccountActionResult,\n DeleteUserActionResult,\n UpdateAccountActionResult,\n UpdateUserActionResult,\n TwoFactorGenerateResult,\n TwoFactorActionResult,\n ContactResetResult,\n} from \"./modules/accounts/types\";\n\nexport { default as AccountModals } from \"./components/account/AccountModals\";\nexport { useAccountModals } from \"./store/useAccountModals\";\nexport type { AccountModalsConfig } from \"./store/useAccountModals\";\n\nexport { ModalManager } from \"./components/modals/ModalManager\";\nexport { Modals } from \"./components/modals/Modals\";\n\nexport { default as TwoFactorAuthModal } from \"./components/account/TwoFactorAuthModal\";\nexport { default as DisableTwoFactorAuthModal } from \"./components/account/DisableTwoFactorAuthModal\";\nexport { default as ConfirmGlobalPreferencesModal } from \"./components/account/ConfirmGlobalPreferencesModal\";\nexport { MyProfileSection } from \"./components/account/sections/MyProfileSection\";\nexport { PreferencesSection } from \"./components/account/sections/PreferencesSection\";\nexport { SecuritySection } from \"./components/account/sections/SecuritySection\";\nexport { ChangePasswordSection } from \"./components/account/sections/ChangePasswordSection\";\nexport { ChangeEmailModal } from \"./components/account/sections/ChangeEmailModal\";\nexport { ChangePhoneModal } from \"./components/account/sections/ChangePhoneModal\";\n\n// Account Constants\nexport {\n GENDER_OPTIONS,\n CURRENCY_OPTIONS,\n TIME_FORMAT_OPTIONS,\n NOTIFICATION_TYPES,\n LANGUAGE_OPTIONS,\n} from \"./components/account/constants\";\n\n// Image Upload\nexport { ImageUpload, ImageCropModal } from \"./components/widgets/ImageUpload\";\nexport { useImageUpload } from \"./modules/images/hooks/use-image-upload.hook\";\nexport {\n ACCEPTED_IMAGE_FORMATS,\n MAX_FILE_SIZE_MB,\n} from \"./modules/images/constants/image.constants\";\nexport type {\n ImageConfig,\n CropArea,\n ProcessedImage,\n CompressImageOptions,\n} from \"./modules/images/types/image.type\";\nexport { compressImage } from \"./modules/images/utils/compress-image\";\nexport { cropImageToCanvas } from \"./modules/images/utils/crop-image\";\nexport { base64ToFile, fileToBase64 } from \"./modules/images/utils/base64\";\nexport {\n validateImage,\n validateImageFormat,\n validateImageSize,\n getImageDimensions,\n} from \"./modules/images/utils/validate-image\";\n"],"mappings":"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","names":["default","LANGUAGE_OPTIONS"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Types\nexport * from \"./modules/auth/schema\";\nexport * from \"./modules/users/schema\";\nexport * from \"./modules/whitelabel/schema\";\n// Style types now come from whitelabel schema directly\nexport * from \"./infra/api/types\";\n\n// Contexts\nexport * from \"./providers/query.provider\";\nexport * from \"./providers/auth.provider\";\nexport * from \"./providers/whitelabel.provider\";\n\n// Hooks\nexport {\n useListProjectUsers,\n LIST_PROJECT_USERS_QUERY_KEY,\n LIST_PROJECT_USERS_BASE_KEY,\n} from \"./modules/projects/hooks/list-project-users.hook\";\nexport {\n useListAvailableUsers,\n LIST_AVAILABLE_USERS_QUERY_KEY,\n LIST_AVAILABLE_USERS_BASE_KEY,\n} from \"./modules/projects/hooks/list-available-users.hook\";\nexport type {\n ProjectUser,\n AccountUser,\n AddRemoveProjectUsersParams,\n ListUsersParams,\n UsersPage,\n} from \"./modules/projects/types\";\nexport {\n useUserQuery,\n useUserValidateSession,\n useInvalidateUser,\n useSetUserData,\n useTwoFactorVerify,\n USER_QUERY_KEY,\n} from \"./modules/auth/hooks/useUserQuery\";\nexport { useManagementPermissions } from \"./modules/auth/hooks/useManagementPermissions\";\nexport type { ManagementPermission } from \"./modules/auth/types/management-permission.type\";\nexport { useSubscriptions } from \"./modules/subscriptions/hooks/list-subscriptions.hook\";\nexport { SUBSCRIPTIONS_QUERY_KEY } from \"./modules/subscriptions/constants/query-keys.constants\";\nexport { useActiveSubscription } from \"./modules/subscriptions/hooks/find-active-subscription.hook\";\nexport { useCancelledSubscriptionGuard } from \"./modules/subscriptions/hooks/use-cancelled-subscription-guard\";\nexport { usePlanById } from \"./modules/plans/hooks/use-plan-by-id.hook\";\nexport { useHasPlanAddon } from \"./modules/plans/hooks/use-has-plan-addon.hook\";\nexport { useCalculateSubscription } from \"./modules/subscriptions/hooks/calculate-subscription.hook\";\nexport { useUpdateSubscriptionPlan } from \"./modules/subscriptions/hooks/update-subscription-plan.hook\";\nexport { useCards, CARDS_QUERY_KEY } from \"./modules/cards/hooks/cards.hook\";\nexport { useCreateCard } from \"./modules/cards/hooks/create-card.hook\";\nexport { useIaCredits } from \"./modules/ia-credits/hooks/ia-credits.hook\";\nexport type {\n IaCreditsSummary,\n IaCreditOperation,\n} from \"./modules/ia-credits/types\";\nexport type {\n Subscription,\n SubscriptionItem,\n FindSubscriptionsParams,\n} from \"./modules/subscriptions/types/subscription.type\";\nexport {\n SubscriptionSchema,\n SubscriptionItemSchema,\n} from \"./modules/subscriptions/types/subscription.type\";\nexport { ADDON_IDS, AI_CREDIT_OPTIONS, AI_CREDIT_VALUES } from \"./modules/subscriptions/constants/addons.constants\";\nexport type { AddonKindEnum, AiCreditOption } from \"./modules/subscriptions/constants/addons.constants\";\nexport type { Plan, PlanItem, UiPlan, PlanFeature, PlanMainFeatures, PlanPricingByPeriod, PlanTooltips } from \"./modules/plans/types/plan.type\";\nexport { PlanSchema, PlanItemSchema } from \"./modules/plans/types/plan.type\";\nexport { mapApiPlanToUiPlan } from \"./modules/plans/utils/map-api-plan-to-ui\";\nexport type { PlanBillingPeriod } from \"./modules/plans/utils/map-api-plan-to-ui\";\nexport { usePlans, PLANS_QUERY_KEY } from \"./modules/plans/hooks/list-plans.hook\";\nexport type { BillingPeriod } from \"./modules/subscriptions/types/billing-period.type\";\nexport type {\n CalculateSubscriptionRequest,\n CalculateSubscriptionResponse,\n UpdateSubscriptionPlanRequest,\n} from \"./modules/subscriptions/types/calculate-subscription.type\";\nexport type { Card as PaymentCard, CreateCardRequest } from \"./modules/cards/types\";\nexport { CardSchema as PaymentCardSchema, CreateCardRequestSchema } from \"./modules/cards/types\";\nexport { buildPlanExtras } from \"./modules/subscriptions/utils/build-plan-extras\";\nexport { hasSubscriptionExpired } from \"./modules/subscriptions/utils/has-subscription-expired\";\nexport { getPriceFromCalculatedData, periodicityToBillingPeriod } from \"./modules/subscriptions/utils/periodicity\";\nexport { useBuyCreditsModal } from \"./store/useBuyCreditsModal\";\nexport { usePaidPlanRequiredModal } from \"./store/usePaidPlanRequiredModal\";\nexport { default as BuyCreditsModal } from \"./components/modals/BuyCreditsModal\";\nexport { default as PaidPlanRequiredModal } from \"./components/modals/PaidPlanRequiredModal\";\nexport { default as AddCardModal } from \"./components/modals/cards/AddCardModal\";\nexport { CardFormFields, cardFormSchema } from \"./components/modals/cards/CardFormFields\";\nexport type { CardFormData } from \"./components/modals/cards/CardFormFields\";\nexport { Skeleton } from \"./components/ui/feedback/Skeleton\";\nexport { PaymentInfoCard } from \"./components/ui/data-display/PaymentInfoCard\";\nexport { CardBrandIcon } from \"./components/ui/data-display/CardBrandIcons\";\nexport { CardItem } from \"./components/ui/data-display/CardItem\";\n\n// Providers\nexport { QueryProvider } from \"./providers/query.provider\";\n\n// Middleware\nexport { createAuthMiddleware } from \"./middlewares/create-auth-middleware\";\nexport { createMiddlewareChain } from \"./middlewares/chain\";\nexport { continueChain, stopChain } from \"./middlewares/types\";\nexport type {\n MiddlewareFunction,\n MiddlewareConfig,\n MiddlewareResult,\n} from \"./middlewares/types\";\n\n// Utils\nexport { cn } from \"./infra/utils/clsx\";\nexport { formatShortDate, formatDateTime } from \"./infra/utils/date\";\nexport { buildQueryParams } from \"./infra/utils/params\";\nexport { parseSchema, parseResult } from \"./infra/utils/parser\";\nexport { withAction } from \"./utils/withAction\";\n\n// Layout\nexport {\n MainLayout,\n MainLayoutContent,\n MainLayoutSpacer,\n MainLayoutMain,\n} from \"./components/layouts/MainLayout\";\nexport { NavBar } from \"./components/layouts/NavBar\";\nexport type { NavBarProps } from \"./components/layouts/NavBar\";\nexport { AppNavBar } from \"./components/layouts/AppNavBar\";\nexport type { AppNavBarProps } from \"./components/layouts/AppNavBar\";\nexport { AppMobileNavBar } from \"./components/layouts/AppMobileNavBar\";\nexport type { AppMobileNavBarProps } from \"./components/layouts/AppMobileNavBar\";\nexport { SideBarNavigation } from \"./components/layouts/SideBarNavigation\";\nexport { MdSideBarNavigation } from \"./components/layouts/MdSideBarNavigation\";\nexport { default as NotificationCard } from \"./components/widgets/notifications/NotificationCard\";\nexport type { NotificationCardProps } from \"./components/widgets/notifications/NotificationCard\";\nexport { NotificationsPopover } from \"./components/layouts/NotificationsPopover\";\nexport type { NotificationsPopoverProps } from \"./components/layouts/NotificationsPopover\";\nexport { NotificationPageContent } from \"./components/pages/notifications/Notifications\";\nexport { NotFoundPage } from \"./components/pages/NotFoundPage\";\nexport { UsersSelectorPopover } from \"./components/layouts/UsersSelectorPopover\";\nexport type { UsersSelectorPopoverProps } from \"./components/layouts/UsersSelectorPopover\";\nexport { ProfilePopover } from \"./components/layouts/ProfilePopover\";\nexport type {\n ProfilePopoverProps,\n ProfileMenuItem,\n} from \"./components/layouts/ProfilePopover\";\nexport { default as WhitelabelCodes } from \"./components/layouts/WhitelabelCodes\";\nexport { NavBarItem } from \"./components/layouts/NavBarItem\";\nexport type { NavBarItemProps } from \"./components/layouts/NavBarItem\";\n\n// Navigation\nexport { AppNavigation } from './components/navigation/AppNavigation';\nexport { CancelledSubscriptionBanner } from './components/navigation/CancelledSubscriptionBanner';\nexport type { NavItemConfig } from './components/navigation/subcomponents/NavItems';\nexport type { NavigationProject } from './components/navigation/types';\nexport { useMobileNavbarSheet } from './store/useMobileNavbarSheet';\n\n// Hooks\nexport { default as useCopyToClipboard } from \"./hooks/copy-to-clipboard.hook\";\n\n// UI - Buttons\nexport { Button, buttonVariants } from \"./components/ui/buttons/Button\";\nexport { CopyButton } from \"./components/ui/buttons/CopyButton\";\nexport { default as CancelButton } from \"./components/ui/buttons/CancelButton\";\n\n// UI - Data Display\nexport {\n Accordion,\n AccordionItem,\n AccordionTrigger,\n AccordionContent,\n} from \"./components/ui/data-display/Accordion\";\nexport { Badge, badgeVariants } from \"./components/ui/data-display/Badge\";\nexport {\n Card,\n CardHeader,\n CardFooter,\n CardTitle,\n CardAction,\n CardDescription,\n CardContent,\n} from \"./components/ui/data-display/Card\";\nexport {\n Pagination,\n PaginationContent,\n PaginationLink,\n PaginationItem,\n PaginationPrevious,\n PaginationNext,\n PaginationEllipsis,\n} from \"./components/ui/data-display/Pagination\";\nexport { ScrollArea, ScrollBar } from \"./components/ui/data-display/ScrollArea\";\nexport { Separator } from \"./components/ui/data-display/Separator\";\nexport {\n Table,\n TableHeader,\n TableBody,\n TableFooter,\n TableHead,\n TableRow,\n TableCell,\n TableCaption,\n} from \"./components/ui/data-display/Table\";\nexport {\n Tabs,\n TabsList,\n TabsTrigger,\n TabsContent,\n} from \"./components/ui/data-display/Tabs\";\nexport { UserAvatar } from \"./components/ui/data-display/UserAvatar\";\nexport type { UserAvatarProps } from \"./components/ui/data-display/UserAvatar\";\n\n// UI - Feedback\nexport { default as CircularProgress } from \"./components/ui/feedback/CircularProgress\";\nexport { default as DefaultCircularProgress } from \"./components/ui/feedback/DefaultCircularProgress\";\nexport { Progress } from \"./components/ui/feedback/Progress\";\nexport { LoadingOverlay } from \"./components/ui/feedback/LoadingOverlay\";\nexport {\n Toast,\n toastVariants,\n toastIconContainerVariants,\n} from \"./components/ui/feedback/Toast\";\nexport type { ToastProps } from \"./components/ui/feedback/Toast\";\n\n// UI - Form\nexport { Calendar, CalendarDayButton } from \"./components/ui/form/Calendar\";\nexport { Checkbox } from \"./components/ui/form/Checkbox\";\nexport { DatePicker } from \"./components/ui/form/DatePicker\";\nexport { DateRangePicker } from \"./components/ui/form/DateRangePicker\";\nexport { Input } from \"./components/ui/form/Input\";\nexport {\n InputOTP,\n InputOTPGroup,\n InputOTPSlot,\n InputOTPSeparator,\n} from \"./components/ui/form/InputOtp\";\nexport { RadioGroup, RadioGroupItem } from \"./components/ui/form/RadioGroup\";\nexport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectScrollDownButton,\n SelectScrollUpButton,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n} from \"./components/ui/form/Select\";\nexport { Switch } from \"./components/ui/form/Switch\";\nexport { Textarea } from \"./components/ui/form/Textarea\";\n\n// UI - Overlay\nexport {\n Command,\n CommandDialog,\n CommandInput,\n CommandList,\n CommandEmpty,\n CommandGroup,\n CommandItem,\n CommandShortcut,\n CommandSeparator,\n} from \"./components/ui/overlay/Command\";\nexport {\n Dialog,\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogOverlay,\n DialogPortal,\n DialogTitle,\n DialogTrigger,\n} from \"./components/ui/overlay/Dialog\";\nexport {\n Popover,\n PopoverTrigger,\n PopoverContent,\n PopoverAnchor,\n} from \"./components/ui/overlay/Popover\";\nexport {\n Sheet,\n SheetTrigger,\n SheetClose,\n SheetContent,\n SheetHeader,\n SheetFooter,\n SheetTitle,\n SheetDescription,\n} from \"./components/ui/overlay/Sheet\";\nexport {\n Tooltip,\n TooltipTrigger,\n TooltipContent,\n TooltipProvider,\n} from \"./components/ui/overlay/Tooltip\";\n\n// Embeds\nexport { FrillEmbed } from \"./components/embeds/FrillEmbed\";\nexport { CrispEmbed, openCrispHelpdesk, hideCrisp, showCrisp, updateCrispUser } from \"./components/embeds/CrispEmbed\";\nexport { EmbedWidgets } from \"./components/embeds/EmbedWidgets\";\n\n// Store\nexport { useMdSidebarStore } from \"./store/useMdSidebarStore\";\nexport { useDesktopSidebarStore } from \"./store/useDesktopSidebarStore\";\nexport { useModalManager } from \"./store/useModalManager\";\nexport type { ModalData } from \"./store/useModalManager\";\n\n// Enums\nexport { AccountSectionType } from \"./enums/AccountSectionType\";\n\n// Hooks\nexport { default as usePasswordVisibility } from \"./hooks/usePasswordVisibility\";\nexport { default as useCountdownTimer } from \"./hooks/useCountdownTimer\";\nexport { default as useIsMobile } from \"./hooks/useIsMobile\";\nexport { useDebounce } from \"./hooks/useDebounce\";\nexport { useDebouncedEffect } from \"./hooks/useDebouncedEffect\";\nexport { useDebounceState } from \"./hooks/useDebounceState\";\n\n// Utils\nexport {\n formatPhone,\n formatTimer,\n formatFullName,\n formatCPF,\n formatCNPJ,\n formatCardNumber,\n} from \"./utils/format/masks\";\nexport { formatCurrencyNumber } from \"./utils/format/currency\";\nexport { BR_STATE_OPTIONS } from \"./utils/constants/br-states\";\nexport type { TimezoneOption } from \"./modules/accounts/services/timezone.service\";\nexport {\n buildLocaleOptions,\n LANGUAGE_OPTIONS as LOCALE_LANGUAGE_OPTIONS,\n} from \"./utils/intl/locales\";\nexport type { LocaleOption } from \"./utils/intl/locales\";\nexport { copyToClipboard, readFromClipboard } from \"./utils/browser/clipboard\";\n\n// UI - Form (new widgets)\nexport { FormField } from \"./components/ui/form/FormField\";\nexport { SelectField } from \"./components/ui/form/SelectField\";\nexport { ComboboxField } from \"./components/ui/form/ComboboxField\";\nexport type { ComboboxOption } from \"./components/ui/form/ComboboxField\";\nexport { default as PhoneInput } from \"./components/ui/form/PhoneInput\";\nexport { default as SwitchOptionFieldWithIcon } from \"./components/ui/form/SwitchOptionFieldWithIcon\";\nexport { TextAreaField } from \"./components/ui/form/TextAreaField\";\n\n// Account Module - Hooks\nexport {\n useCurrentAccount,\n ACCOUNT_QUERY_KEY,\n} from \"./modules/accounts/hooks/current-account.hook\";\nexport {\n useUpdateAccount,\n useUpdateAccountUser,\n useUpdateAccountUserById,\n useDeleteAccountUser,\n useDeleteAccount,\n} from \"./modules/accounts/hooks/useAccountManagement\";\nexport { useViaCep } from \"./modules/accounts/hooks/useViaCep\";\n\n// Account Types\nexport type {\n UpdateAccountRequest,\n UpdateAccountUserRequest,\n ChangePasswordRequest,\n DeleteAccountActionResult,\n DeleteUserActionResult,\n UpdateAccountActionResult,\n UpdateUserActionResult,\n TwoFactorGenerateResult,\n TwoFactorActionResult,\n ContactResetResult,\n} from \"./modules/accounts/types\";\n\nexport { default as AccountModals } from \"./components/account/AccountModals\";\nexport { useAccountModals } from \"./store/useAccountModals\";\nexport type { AccountModalsConfig } from \"./store/useAccountModals\";\n\nexport { ModalManager } from \"./components/modals/ModalManager\";\nexport { Modals } from \"./components/modals/Modals\";\n\nexport { default as TwoFactorAuthModal } from \"./components/account/TwoFactorAuthModal\";\nexport { default as DisableTwoFactorAuthModal } from \"./components/account/DisableTwoFactorAuthModal\";\nexport { default as ConfirmGlobalPreferencesModal } from \"./components/account/ConfirmGlobalPreferencesModal\";\nexport { MyProfileSection } from \"./components/account/sections/MyProfileSection\";\nexport { PreferencesSection } from \"./components/account/sections/PreferencesSection\";\nexport { SecuritySection } from \"./components/account/sections/SecuritySection\";\nexport { ChangePasswordSection } from \"./components/account/sections/ChangePasswordSection\";\nexport { ChangeEmailModal } from \"./components/account/sections/ChangeEmailModal\";\nexport { ChangePhoneModal } from \"./components/account/sections/ChangePhoneModal\";\n\n// Account Constants\nexport {\n GENDER_OPTIONS,\n CURRENCY_OPTIONS,\n TIME_FORMAT_OPTIONS,\n NOTIFICATION_TYPES,\n LANGUAGE_OPTIONS,\n} from \"./components/account/constants\";\n\n// Image Upload\nexport {\n ImageUpload,\n ImageCropModal,\n ImageTooSmallModal,\n} from \"./components/widgets/ImageUpload\";\nexport {\n useImageUpload,\n type ImageTooSmallError,\n} from \"./modules/images/hooks/use-image-upload.hook\";\nexport {\n ACCEPTED_IMAGE_FORMATS,\n MAX_FILE_SIZE_MB,\n} from \"./modules/images/constants/image.constants\";\nexport type {\n ImageConfig,\n CropArea,\n ProcessedImage,\n CompressImageOptions,\n} from \"./modules/images/types/image.type\";\nexport { compressImage } from \"./modules/images/utils/compress-image\";\nexport { cropImageToCanvas } from \"./modules/images/utils/crop-image\";\nexport { base64ToFile, fileToBase64 } from \"./modules/images/utils/base64\";\nexport {\n validateImage,\n validateImageFormat,\n validateImageSize,\n getImageDimensions,\n} from \"./modules/images/utils/validate-image\";\n"],"mappings":"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","names":["default","LANGUAGE_OPTIONS"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useState, useCallback } from "react";
|
|
2
|
+
import { useState, useCallback, useRef } from "react";
|
|
3
3
|
import { compressImage } from "../utils/compress-image";
|
|
4
4
|
import { cropImageToCanvas } from "../utils/crop-image";
|
|
5
5
|
import { validateImage } from "../utils/validate-image";
|
|
@@ -8,7 +8,11 @@ function useImageUpload(options) {
|
|
|
8
8
|
const [originalImage, setOriginalImage] = useState(null);
|
|
9
9
|
const [croppedPreview, setCroppedPreview] = useState(null);
|
|
10
10
|
const [showCropModal, setShowCropModal] = useState(false);
|
|
11
|
+
const [showTooSmallModal, setShowTooSmallModal] = useState(false);
|
|
12
|
+
const [tooSmallError, setTooSmallError] = useState(null);
|
|
11
13
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
14
|
+
const fileInputRef = useRef(null);
|
|
15
|
+
const lastCompressedFile = useRef(null);
|
|
12
16
|
const handleFileSelect = useCallback(
|
|
13
17
|
async (file) => {
|
|
14
18
|
const validation = validateImage(file);
|
|
@@ -18,6 +22,34 @@ function useImageUpload(options) {
|
|
|
18
22
|
}
|
|
19
23
|
try {
|
|
20
24
|
const compressed = await compressImage(file);
|
|
25
|
+
lastCompressedFile.current = compressed;
|
|
26
|
+
const imageDimensions = await new Promise((resolve, reject) => {
|
|
27
|
+
const img = new Image();
|
|
28
|
+
img.onload = () => resolve({ width: img.naturalWidth, height: img.naturalHeight });
|
|
29
|
+
img.onerror = reject;
|
|
30
|
+
img.src = URL.createObjectURL(compressed);
|
|
31
|
+
});
|
|
32
|
+
const { width: minW, height: minH, aspectRatio } = config;
|
|
33
|
+
const imgAspect = imageDimensions.width / imageDimensions.height;
|
|
34
|
+
let canFitMinCrop = false;
|
|
35
|
+
if (imgAspect >= aspectRatio) {
|
|
36
|
+
const cropHeight = imageDimensions.height;
|
|
37
|
+
const cropWidth = cropHeight * aspectRatio;
|
|
38
|
+
canFitMinCrop = cropWidth >= minW && cropHeight >= minH;
|
|
39
|
+
} else {
|
|
40
|
+
const cropWidth = imageDimensions.width;
|
|
41
|
+
const cropHeight = cropWidth / aspectRatio;
|
|
42
|
+
canFitMinCrop = cropWidth >= minW && cropHeight >= minH;
|
|
43
|
+
}
|
|
44
|
+
if (!canFitMinCrop) {
|
|
45
|
+
setTooSmallError({
|
|
46
|
+
fileName: file.name,
|
|
47
|
+
minWidth: minW,
|
|
48
|
+
minHeight: minH
|
|
49
|
+
});
|
|
50
|
+
setShowTooSmallModal(true);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
21
53
|
const reader = new FileReader();
|
|
22
54
|
reader.onload = (e) => {
|
|
23
55
|
setOriginalImage(e.target?.result);
|
|
@@ -28,8 +60,19 @@ function useImageUpload(options) {
|
|
|
28
60
|
onError?.("Erro ao processar imagem");
|
|
29
61
|
}
|
|
30
62
|
},
|
|
31
|
-
[onError]
|
|
63
|
+
[config, onError]
|
|
32
64
|
);
|
|
65
|
+
const handleSelectAnotherFile = useCallback(() => {
|
|
66
|
+
setShowTooSmallModal(false);
|
|
67
|
+
setTooSmallError(null);
|
|
68
|
+
fileInputRef.current?.click();
|
|
69
|
+
}, []);
|
|
70
|
+
const handleCancelTooSmall = useCallback(() => {
|
|
71
|
+
setShowTooSmallModal(false);
|
|
72
|
+
setShowCropModal(false);
|
|
73
|
+
setTooSmallError(null);
|
|
74
|
+
setOriginalImage(null);
|
|
75
|
+
}, []);
|
|
33
76
|
const handleCropConfirm = useCallback(
|
|
34
77
|
async (imageElement, cropArea) => {
|
|
35
78
|
setIsProcessing(true);
|
|
@@ -59,11 +102,17 @@ function useImageUpload(options) {
|
|
|
59
102
|
originalImage,
|
|
60
103
|
croppedPreview,
|
|
61
104
|
showCropModal,
|
|
105
|
+
showTooSmallModal,
|
|
106
|
+
tooSmallError,
|
|
62
107
|
config,
|
|
63
108
|
isProcessing,
|
|
109
|
+
fileInputRef,
|
|
64
110
|
handleFileSelect,
|
|
65
111
|
handleCropConfirm,
|
|
112
|
+
handleSelectAnotherFile,
|
|
113
|
+
handleCancelTooSmall,
|
|
66
114
|
setShowCropModal,
|
|
115
|
+
setShowTooSmallModal,
|
|
67
116
|
clear
|
|
68
117
|
};
|
|
69
118
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/modules/images/hooks/use-image-upload.hook.ts"],"sourcesContent":["'use client'\n\nimport { useState, useCallback } from 'react'\nimport type { Crop } from 'react-image-crop'\nimport { compressImage } from '../utils/compress-image'\nimport { cropImageToCanvas } from '../utils/crop-image'\nimport { validateImage } from '../utils/validate-image'\nimport type { ImageConfig } from '../types/image.type'\n\nexport interface UseImageUploadOptions {\n config: ImageConfig\n onSuccess?: (base64Image: string) => void\n onError?: (error: string) => void\n}\n\nexport function useImageUpload(options: UseImageUploadOptions) {\n const { config, onSuccess, onError } = options\n\n const [originalImage, setOriginalImage] = useState<string | null>(null)\n const [croppedPreview, setCroppedPreview] = useState<string | null>(null)\n const [showCropModal, setShowCropModal] = useState(false)\n const [isProcessing, setIsProcessing] = useState(false)\n\n const handleFileSelect = useCallback(\n async (file: File) => {\n const validation = validateImage(file)\n if (!validation.valid) {\n onError?.(validation.error || 'Arquivo inválido')\n return\n }\n\n try {\n const compressed = await compressImage(file)\n const reader = new FileReader()\n reader.onload = (e) => {\n setOriginalImage(e.target?.result as string)\n setShowCropModal(true)\n }\n reader.readAsDataURL(compressed)\n } catch {\n onError?.('Erro ao processar imagem')\n }\n },\n [onError]\n )\n\n const handleCropConfirm = useCallback(\n async (imageElement: HTMLImageElement, cropArea: Crop) => {\n setIsProcessing(true)\n try {\n // Crop é feito inteiramente no cliente via Canvas\n // Já produz imagem no tamanho final (config.width x config.height)\n const croppedBase64 = await cropImageToCanvas(\n imageElement,\n cropArea,\n config.width,\n config.height\n )\n\n setCroppedPreview(croppedBase64)\n setShowCropModal(false)\n onSuccess?.(croppedBase64)\n } catch {\n onError?.('Erro ao aplicar crop')\n } finally {\n setIsProcessing(false)\n }\n },\n [config, onSuccess, onError]\n )\n\n const clear = useCallback(() => {\n setOriginalImage(null)\n setCroppedPreview(null)\n }, [])\n\n return {\n originalImage,\n croppedPreview,\n showCropModal,\n config,\n isProcessing,\n handleFileSelect,\n handleCropConfirm,\n setShowCropModal,\n clear,\n }\n}\n"],"mappings":";AAEA,SAAS,UAAU,
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/images/hooks/use-image-upload.hook.ts"],"sourcesContent":["'use client'\n\nimport { useState, useCallback, useRef } from 'react'\nimport type { Crop } from 'react-image-crop'\nimport { compressImage } from '../utils/compress-image'\nimport { cropImageToCanvas } from '../utils/crop-image'\nimport { validateImage } from '../utils/validate-image'\nimport type { ImageConfig } from '../types/image.type'\n\nexport interface ImageTooSmallError {\n fileName: string\n minWidth: number\n minHeight: number\n}\n\nexport interface UseImageUploadOptions {\n config: ImageConfig\n onSuccess?: (base64Image: string) => void\n onError?: (error: string) => void\n}\n\nexport function useImageUpload(options: UseImageUploadOptions) {\n const { config, onSuccess, onError } = options\n\n const [originalImage, setOriginalImage] = useState<string | null>(null)\n const [croppedPreview, setCroppedPreview] = useState<string | null>(null)\n const [showCropModal, setShowCropModal] = useState(false)\n const [showTooSmallModal, setShowTooSmallModal] = useState(false)\n const [tooSmallError, setTooSmallError] = useState<ImageTooSmallError | null>(null)\n const [isProcessing, setIsProcessing] = useState(false)\n const fileInputRef = useRef<HTMLInputElement>(null)\n const lastCompressedFile = useRef<File | null>(null)\n\n const handleFileSelect = useCallback(\n async (file: File) => {\n const validation = validateImage(file)\n if (!validation.valid) {\n onError?.(validation.error || 'Arquivo inválido')\n return\n }\n\n try {\n const compressed = await compressImage(file)\n lastCompressedFile.current = compressed\n\n // Verificar dimensões da imagem antes de abrir modal\n const imageDimensions = await new Promise<{\n width: number\n height: number\n }>((resolve, reject) => {\n const img = new Image()\n img.onload = () =>\n resolve({ width: img.naturalWidth, height: img.naturalHeight })\n img.onerror = reject\n img.src = URL.createObjectURL(compressed)\n })\n\n // Verificar se a imagem atende às dimensões mínimas considerando o aspect ratio\n const { width: minW, height: minH, aspectRatio } = config\n const imgAspect = imageDimensions.width / imageDimensions.height\n\n let canFitMinCrop = false\n if (imgAspect >= aspectRatio) {\n // Imagem mais larga - altura é o limitante\n const cropHeight = imageDimensions.height\n const cropWidth = cropHeight * aspectRatio\n canFitMinCrop = cropWidth >= minW && cropHeight >= minH\n } else {\n // Imagem mais alta - largura é o limitante\n const cropWidth = imageDimensions.width\n const cropHeight = cropWidth / aspectRatio\n canFitMinCrop = cropWidth >= minW && cropHeight >= minH\n }\n\n if (!canFitMinCrop) {\n setTooSmallError({\n fileName: file.name,\n minWidth: minW,\n minHeight: minH,\n })\n setShowTooSmallModal(true)\n return\n }\n\n const reader = new FileReader()\n reader.onload = (e) => {\n setOriginalImage(e.target?.result as string)\n setShowCropModal(true)\n }\n reader.readAsDataURL(compressed)\n } catch {\n onError?.('Erro ao processar imagem')\n }\n },\n [config, onError]\n )\n\n const handleSelectAnotherFile = useCallback(() => {\n setShowTooSmallModal(false)\n setTooSmallError(null)\n fileInputRef.current?.click()\n }, [])\n\n const handleCancelTooSmall = useCallback(() => {\n setShowTooSmallModal(false)\n setShowCropModal(false)\n setTooSmallError(null)\n setOriginalImage(null)\n }, [])\n\n const handleCropConfirm = useCallback(\n async (imageElement: HTMLImageElement, cropArea: Crop) => {\n setIsProcessing(true)\n try {\n // Crop é feito inteiramente no cliente via Canvas\n // Já produz imagem no tamanho final (config.width x config.height)\n const croppedBase64 = await cropImageToCanvas(\n imageElement,\n cropArea,\n config.width,\n config.height\n )\n\n setCroppedPreview(croppedBase64)\n setShowCropModal(false)\n onSuccess?.(croppedBase64)\n } catch {\n onError?.('Erro ao aplicar crop')\n } finally {\n setIsProcessing(false)\n }\n },\n [config, onSuccess, onError]\n )\n\n const clear = useCallback(() => {\n setOriginalImage(null)\n setCroppedPreview(null)\n }, [])\n\n return {\n originalImage,\n croppedPreview,\n showCropModal,\n showTooSmallModal,\n tooSmallError,\n config,\n isProcessing,\n fileInputRef,\n handleFileSelect,\n handleCropConfirm,\n handleSelectAnotherFile,\n handleCancelTooSmall,\n setShowCropModal,\n setShowTooSmallModal,\n clear,\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -73,6 +73,41 @@ const baseCropStyles = `
|
|
|
73
73
|
border-radius: 50%;
|
|
74
74
|
pointer-events: none;
|
|
75
75
|
}
|
|
76
|
+
|
|
77
|
+
/* Bolinhas centrais nas bordas */
|
|
78
|
+
.custom-crop-area .ReactCrop__crop-selection .crop-handle {
|
|
79
|
+
position: absolute;
|
|
80
|
+
width: 10px;
|
|
81
|
+
height: 10px;
|
|
82
|
+
background: white;
|
|
83
|
+
border-radius: 50%;
|
|
84
|
+
pointer-events: none;
|
|
85
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.custom-crop-area .ReactCrop__crop-selection .crop-handle.top {
|
|
89
|
+
top: -5px;
|
|
90
|
+
left: 50%;
|
|
91
|
+
transform: translateX(-50%);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.custom-crop-area .ReactCrop__crop-selection .crop-handle.bottom {
|
|
95
|
+
bottom: -5px;
|
|
96
|
+
left: 50%;
|
|
97
|
+
transform: translateX(-50%);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.custom-crop-area .ReactCrop__crop-selection .crop-handle.left {
|
|
101
|
+
left: -5px;
|
|
102
|
+
top: 50%;
|
|
103
|
+
transform: translateY(-50%);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.custom-crop-area .ReactCrop__crop-selection .crop-handle.right {
|
|
107
|
+
right: -5px;
|
|
108
|
+
top: 50%;
|
|
109
|
+
transform: translateY(-50%);
|
|
110
|
+
}
|
|
76
111
|
`
|
|
77
112
|
|
|
78
113
|
interface ImageCropModalProps {
|
|
@@ -81,6 +116,7 @@ interface ImageCropModalProps {
|
|
|
81
116
|
imageSrc: string
|
|
82
117
|
aspectRatio: number
|
|
83
118
|
onConfirm: (image: HTMLImageElement, crop: Crop) => void
|
|
119
|
+
onCancel?: () => void
|
|
84
120
|
showCircleGuide?: boolean
|
|
85
121
|
minWidth?: number
|
|
86
122
|
minHeight?: number
|
|
@@ -92,12 +128,12 @@ export function ImageCropModal({
|
|
|
92
128
|
imageSrc,
|
|
93
129
|
aspectRatio,
|
|
94
130
|
onConfirm,
|
|
131
|
+
onCancel,
|
|
95
132
|
showCircleGuide,
|
|
96
133
|
minWidth,
|
|
97
134
|
minHeight,
|
|
98
135
|
}: ImageCropModalProps) {
|
|
99
136
|
const imgRef = useRef<HTMLImageElement>(null)
|
|
100
|
-
const containerRef = useRef<HTMLDivElement>(null)
|
|
101
137
|
const [crop, setCrop] = useState<Crop>()
|
|
102
138
|
const [imageSize, setImageSize] = useState({ width: 0, height: 0 })
|
|
103
139
|
|
|
@@ -121,10 +157,10 @@ export function ImageCropModal({
|
|
|
121
157
|
|
|
122
158
|
// Calcula tamanho mínimo em porcentagem baseado nas dimensões mínimas em pixels
|
|
123
159
|
const minCropWidth = minWidth && imageSize.width > 0
|
|
124
|
-
? (minWidth / imageSize.width) * 100
|
|
160
|
+
? Math.min((minWidth / imageSize.width) * 100, 100)
|
|
125
161
|
: undefined
|
|
126
162
|
const minCropHeight = minHeight && imageSize.height > 0
|
|
127
|
-
? (minHeight / imageSize.height) * 100
|
|
163
|
+
? Math.min((minHeight / imageSize.height) * 100, 100)
|
|
128
164
|
: undefined
|
|
129
165
|
|
|
130
166
|
const handleConfirm = () => {
|
|
@@ -133,10 +169,15 @@ export function ImageCropModal({
|
|
|
133
169
|
}
|
|
134
170
|
}
|
|
135
171
|
|
|
172
|
+
const handleCancel = () => {
|
|
173
|
+
onCancel?.()
|
|
174
|
+
onOpenChange(false)
|
|
175
|
+
}
|
|
176
|
+
|
|
136
177
|
// Determina se deve mostrar círculo (aspect ratio 1:1 por padrão)
|
|
137
178
|
const shouldShowCircle = showCircleGuide ?? aspectRatio === 1
|
|
138
179
|
|
|
139
|
-
// Renderiza grid e
|
|
180
|
+
// Renderiza grid, círculo e bolinhas dentro da área de crop
|
|
140
181
|
const renderSelectionAddon = () => (
|
|
141
182
|
<>
|
|
142
183
|
{/* Grid de 9 quadrados com linhas pontilhadas */}
|
|
@@ -148,44 +189,53 @@ export function ImageCropModal({
|
|
|
148
189
|
</div>
|
|
149
190
|
{/* Círculo inscrito */}
|
|
150
191
|
{shouldShowCircle && <div className="crop-circle" />}
|
|
192
|
+
{/* Bolinhas centrais nas bordas */}
|
|
193
|
+
<div className="crop-handle top" />
|
|
194
|
+
<div className="crop-handle bottom" />
|
|
195
|
+
<div className="crop-handle left" />
|
|
196
|
+
<div className="crop-handle right" />
|
|
151
197
|
</>
|
|
152
198
|
)
|
|
153
199
|
|
|
154
200
|
return (
|
|
155
201
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
156
202
|
<style>{baseCropStyles}</style>
|
|
157
|
-
<DialogContent className="max-w-
|
|
158
|
-
<DialogHeader>
|
|
159
|
-
<DialogTitle
|
|
203
|
+
<DialogContent className="w-full md:w-[650px]! lg:w-[650px]! max-w-[650px]! max-[650px]:w-[calc(100%-32px)] p-0 gap-0">
|
|
204
|
+
<DialogHeader className="p-5">
|
|
205
|
+
<DialogTitle className="text-center text-[16px]!">
|
|
206
|
+
Ajustar imagem
|
|
207
|
+
</DialogTitle>
|
|
160
208
|
</DialogHeader>
|
|
161
209
|
|
|
162
|
-
<div
|
|
163
|
-
<
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
210
|
+
<div className="flex flex-col gap-4 px-5 pb-5">
|
|
211
|
+
<div className="flex justify-center">
|
|
212
|
+
<ReactCrop
|
|
213
|
+
crop={crop}
|
|
214
|
+
onChange={(c) => setCrop(c)}
|
|
215
|
+
aspect={aspectRatio}
|
|
216
|
+
className={`max-h-[50vh] custom-crop-area ${shouldShowCircle ? 'with-circle' : ''}`}
|
|
217
|
+
keepSelection
|
|
218
|
+
minWidth={minCropWidth}
|
|
219
|
+
minHeight={minCropHeight}
|
|
220
|
+
renderSelectionAddon={renderSelectionAddon}
|
|
221
|
+
>
|
|
222
|
+
<img
|
|
223
|
+
ref={imgRef}
|
|
224
|
+
src={imageSrc}
|
|
225
|
+
alt="Crop"
|
|
226
|
+
onLoad={onImageLoad}
|
|
227
|
+
className="max-h-[50vh] object-contain"
|
|
228
|
+
/>
|
|
229
|
+
</ReactCrop>
|
|
230
|
+
</div>
|
|
231
|
+
|
|
232
|
+
<p className="paragraph-small-regular text-zinc-500 text-center">
|
|
233
|
+
Arraste para ajustar a área de corte
|
|
234
|
+
</p>
|
|
181
235
|
</div>
|
|
182
236
|
|
|
183
|
-
<
|
|
184
|
-
|
|
185
|
-
</p>
|
|
186
|
-
|
|
187
|
-
<DialogFooter>
|
|
188
|
-
<Button variant="secondary" onClick={() => onOpenChange(false)}>
|
|
237
|
+
<DialogFooter className="flex-row items-center justify-end gap-3 p-5 border-t border-zinc-200">
|
|
238
|
+
<Button variant="secondary" onClick={handleCancel}>
|
|
189
239
|
Cancelar
|
|
190
240
|
</Button>
|
|
191
241
|
<Button onClick={handleConfirm}>Confirmar</Button>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Dialog,
|
|
5
|
+
DialogContent,
|
|
6
|
+
DialogFooter,
|
|
7
|
+
DialogHeader,
|
|
8
|
+
DialogTitle,
|
|
9
|
+
} from '../../ui/overlay/Dialog'
|
|
10
|
+
import { Button } from '../../ui/buttons/Button'
|
|
11
|
+
|
|
12
|
+
interface ImageTooSmallModalProps {
|
|
13
|
+
open: boolean
|
|
14
|
+
onOpenChange: (open: boolean) => void
|
|
15
|
+
fileName: string
|
|
16
|
+
minWidth: number
|
|
17
|
+
minHeight: number
|
|
18
|
+
onSelectAnother: () => void
|
|
19
|
+
onCancel: () => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function ImageTooSmallModal({
|
|
23
|
+
open,
|
|
24
|
+
onOpenChange,
|
|
25
|
+
fileName,
|
|
26
|
+
minWidth,
|
|
27
|
+
minHeight,
|
|
28
|
+
onSelectAnother,
|
|
29
|
+
onCancel,
|
|
30
|
+
}: ImageTooSmallModalProps) {
|
|
31
|
+
const dimensionText = `${minWidth}x${minHeight}px`
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
35
|
+
<DialogContent className="w-full md:w-[480px]! lg:w-[480px]! max-w-[480px]! max-[480px]:w-[calc(100%-32px)] p-0 gap-0">
|
|
36
|
+
<DialogHeader className="p-5">
|
|
37
|
+
<DialogTitle className="text-center text-[16px]!">
|
|
38
|
+
Foto menor que {dimensionText}
|
|
39
|
+
</DialogTitle>
|
|
40
|
+
</DialogHeader>
|
|
41
|
+
|
|
42
|
+
<div className="px-5 pb-5">
|
|
43
|
+
<p className="paragraph-small-regular text-zinc-600 text-center">
|
|
44
|
+
O arquivo "{fileName}" é menor que {dimensionText}. Faça envio de
|
|
45
|
+
uma imagem com pelo menos {dimensionText} para continuar.
|
|
46
|
+
</p>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<DialogFooter className="flex-row items-center justify-end gap-3 p-5 border-t border-zinc-200">
|
|
50
|
+
<Button variant="secondary" onClick={onCancel}>
|
|
51
|
+
Cancelar
|
|
52
|
+
</Button>
|
|
53
|
+
<Button onClick={onSelectAnother}>Enviar outra</Button>
|
|
54
|
+
</DialogFooter>
|
|
55
|
+
</DialogContent>
|
|
56
|
+
</Dialog>
|
|
57
|
+
)
|
|
58
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -398,8 +398,15 @@ export {
|
|
|
398
398
|
} from "./components/account/constants";
|
|
399
399
|
|
|
400
400
|
// Image Upload
|
|
401
|
-
export {
|
|
402
|
-
|
|
401
|
+
export {
|
|
402
|
+
ImageUpload,
|
|
403
|
+
ImageCropModal,
|
|
404
|
+
ImageTooSmallModal,
|
|
405
|
+
} from "./components/widgets/ImageUpload";
|
|
406
|
+
export {
|
|
407
|
+
useImageUpload,
|
|
408
|
+
type ImageTooSmallError,
|
|
409
|
+
} from "./modules/images/hooks/use-image-upload.hook";
|
|
403
410
|
export {
|
|
404
411
|
ACCEPTED_IMAGE_FORMATS,
|
|
405
412
|
MAX_FILE_SIZE_MB,
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useState, useCallback } from 'react'
|
|
3
|
+
import { useState, useCallback, useRef } from 'react'
|
|
4
4
|
import type { Crop } from 'react-image-crop'
|
|
5
5
|
import { compressImage } from '../utils/compress-image'
|
|
6
6
|
import { cropImageToCanvas } from '../utils/crop-image'
|
|
7
7
|
import { validateImage } from '../utils/validate-image'
|
|
8
8
|
import type { ImageConfig } from '../types/image.type'
|
|
9
9
|
|
|
10
|
+
export interface ImageTooSmallError {
|
|
11
|
+
fileName: string
|
|
12
|
+
minWidth: number
|
|
13
|
+
minHeight: number
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
export interface UseImageUploadOptions {
|
|
11
17
|
config: ImageConfig
|
|
12
18
|
onSuccess?: (base64Image: string) => void
|
|
@@ -19,7 +25,11 @@ export function useImageUpload(options: UseImageUploadOptions) {
|
|
|
19
25
|
const [originalImage, setOriginalImage] = useState<string | null>(null)
|
|
20
26
|
const [croppedPreview, setCroppedPreview] = useState<string | null>(null)
|
|
21
27
|
const [showCropModal, setShowCropModal] = useState(false)
|
|
28
|
+
const [showTooSmallModal, setShowTooSmallModal] = useState(false)
|
|
29
|
+
const [tooSmallError, setTooSmallError] = useState<ImageTooSmallError | null>(null)
|
|
22
30
|
const [isProcessing, setIsProcessing] = useState(false)
|
|
31
|
+
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
32
|
+
const lastCompressedFile = useRef<File | null>(null)
|
|
23
33
|
|
|
24
34
|
const handleFileSelect = useCallback(
|
|
25
35
|
async (file: File) => {
|
|
@@ -31,6 +41,47 @@ export function useImageUpload(options: UseImageUploadOptions) {
|
|
|
31
41
|
|
|
32
42
|
try {
|
|
33
43
|
const compressed = await compressImage(file)
|
|
44
|
+
lastCompressedFile.current = compressed
|
|
45
|
+
|
|
46
|
+
// Verificar dimensões da imagem antes de abrir modal
|
|
47
|
+
const imageDimensions = await new Promise<{
|
|
48
|
+
width: number
|
|
49
|
+
height: number
|
|
50
|
+
}>((resolve, reject) => {
|
|
51
|
+
const img = new Image()
|
|
52
|
+
img.onload = () =>
|
|
53
|
+
resolve({ width: img.naturalWidth, height: img.naturalHeight })
|
|
54
|
+
img.onerror = reject
|
|
55
|
+
img.src = URL.createObjectURL(compressed)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// Verificar se a imagem atende às dimensões mínimas considerando o aspect ratio
|
|
59
|
+
const { width: minW, height: minH, aspectRatio } = config
|
|
60
|
+
const imgAspect = imageDimensions.width / imageDimensions.height
|
|
61
|
+
|
|
62
|
+
let canFitMinCrop = false
|
|
63
|
+
if (imgAspect >= aspectRatio) {
|
|
64
|
+
// Imagem mais larga - altura é o limitante
|
|
65
|
+
const cropHeight = imageDimensions.height
|
|
66
|
+
const cropWidth = cropHeight * aspectRatio
|
|
67
|
+
canFitMinCrop = cropWidth >= minW && cropHeight >= minH
|
|
68
|
+
} else {
|
|
69
|
+
// Imagem mais alta - largura é o limitante
|
|
70
|
+
const cropWidth = imageDimensions.width
|
|
71
|
+
const cropHeight = cropWidth / aspectRatio
|
|
72
|
+
canFitMinCrop = cropWidth >= minW && cropHeight >= minH
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!canFitMinCrop) {
|
|
76
|
+
setTooSmallError({
|
|
77
|
+
fileName: file.name,
|
|
78
|
+
minWidth: minW,
|
|
79
|
+
minHeight: minH,
|
|
80
|
+
})
|
|
81
|
+
setShowTooSmallModal(true)
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
34
85
|
const reader = new FileReader()
|
|
35
86
|
reader.onload = (e) => {
|
|
36
87
|
setOriginalImage(e.target?.result as string)
|
|
@@ -41,9 +92,22 @@ export function useImageUpload(options: UseImageUploadOptions) {
|
|
|
41
92
|
onError?.('Erro ao processar imagem')
|
|
42
93
|
}
|
|
43
94
|
},
|
|
44
|
-
[onError]
|
|
95
|
+
[config, onError]
|
|
45
96
|
)
|
|
46
97
|
|
|
98
|
+
const handleSelectAnotherFile = useCallback(() => {
|
|
99
|
+
setShowTooSmallModal(false)
|
|
100
|
+
setTooSmallError(null)
|
|
101
|
+
fileInputRef.current?.click()
|
|
102
|
+
}, [])
|
|
103
|
+
|
|
104
|
+
const handleCancelTooSmall = useCallback(() => {
|
|
105
|
+
setShowTooSmallModal(false)
|
|
106
|
+
setShowCropModal(false)
|
|
107
|
+
setTooSmallError(null)
|
|
108
|
+
setOriginalImage(null)
|
|
109
|
+
}, [])
|
|
110
|
+
|
|
47
111
|
const handleCropConfirm = useCallback(
|
|
48
112
|
async (imageElement: HTMLImageElement, cropArea: Crop) => {
|
|
49
113
|
setIsProcessing(true)
|
|
@@ -78,11 +142,17 @@ export function useImageUpload(options: UseImageUploadOptions) {
|
|
|
78
142
|
originalImage,
|
|
79
143
|
croppedPreview,
|
|
80
144
|
showCropModal,
|
|
145
|
+
showTooSmallModal,
|
|
146
|
+
tooSmallError,
|
|
81
147
|
config,
|
|
82
148
|
isProcessing,
|
|
149
|
+
fileInputRef,
|
|
83
150
|
handleFileSelect,
|
|
84
151
|
handleCropConfirm,
|
|
152
|
+
handleSelectAnotherFile,
|
|
153
|
+
handleCancelTooSmall,
|
|
85
154
|
setShowCropModal,
|
|
155
|
+
setShowTooSmallModal,
|
|
86
156
|
clear,
|
|
87
157
|
}
|
|
88
158
|
}
|