@greatapps/common 1.1.447 → 1.1.449
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/modals/cards/AddCardModal.mjs +5 -2
- package/dist/components/modals/cards/AddCardModal.mjs.map +1 -1
- package/dist/components/widgets/ImageUpload/ImageCropModal.mjs +76 -0
- package/dist/components/widgets/ImageUpload/ImageCropModal.mjs.map +1 -0
- package/dist/components/widgets/ImageUpload/ImageUpload.mjs +121 -0
- package/dist/components/widgets/ImageUpload/ImageUpload.mjs.map +1 -0
- package/dist/components/widgets/ImageUpload/index.mjs +7 -0
- package/dist/components/widgets/ImageUpload/index.mjs.map +1 -0
- package/dist/index.mjs +29 -1
- package/dist/index.mjs.map +1 -1
- package/dist/infra/utils/date.mjs +11 -0
- package/dist/infra/utils/date.mjs.map +1 -1
- package/dist/modules/images/actions/process-image.action.mjs +31 -0
- package/dist/modules/images/actions/process-image.action.mjs.map +1 -0
- package/dist/modules/images/constants/image.constants.mjs +14 -0
- package/dist/modules/images/constants/image.constants.mjs.map +1 -0
- package/dist/modules/images/hooks/use-image-upload.hook.mjs +96 -0
- package/dist/modules/images/hooks/use-image-upload.hook.mjs.map +1 -0
- package/dist/modules/images/services/image-processing.service.mjs +70 -0
- package/dist/modules/images/services/image-processing.service.mjs.map +1 -0
- package/dist/modules/images/types/image.type.mjs +1 -0
- package/dist/modules/images/types/image.type.mjs.map +1 -0
- package/dist/modules/images/utils/compress-image.mjs +18 -0
- package/dist/modules/images/utils/compress-image.mjs.map +1 -0
- package/dist/modules/images/utils/crop-image.mjs +29 -0
- package/dist/modules/images/utils/crop-image.mjs.map +1 -0
- package/dist/modules/images/utils/validate-image.mjs +50 -0
- package/dist/modules/images/utils/validate-image.mjs.map +1 -0
- package/dist/modules/plans/services/plans.service.mjs +15 -11
- package/dist/modules/plans/services/plans.service.mjs.map +1 -1
- package/dist/server.mjs +4 -0
- package/dist/server.mjs.map +1 -1
- package/package.json +4 -1
- package/src/components/modals/cards/AddCardModal.tsx +2 -1
- package/src/components/widgets/ImageUpload/ImageCropModal.tsx +97 -0
- package/src/components/widgets/ImageUpload/ImageUpload.tsx +125 -0
- package/src/components/widgets/ImageUpload/index.ts +2 -0
- package/src/index.ts +24 -1
- package/src/infra/utils/date.ts +11 -0
- package/src/modules/images/actions/process-image.action.ts +38 -0
- package/src/modules/images/constants/image.constants.ts +9 -0
- package/src/modules/images/hooks/use-image-upload.hook.ts +111 -0
- package/src/modules/images/services/image-processing.service.ts +76 -0
- package/src/modules/images/types/image.type.ts +54 -0
- package/src/modules/images/utils/compress-image.ts +21 -0
- package/src/modules/images/utils/crop-image.ts +41 -0
- package/src/modules/images/utils/validate-image.ts +59 -0
- package/src/modules/plans/services/plans.service.ts +48 -38
- package/src/server.ts +10 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useState, useCallback } from "react";
|
|
3
|
+
import { useMutation } from "@tanstack/react-query";
|
|
4
|
+
import { compressImage } from "../utils/compress-image";
|
|
5
|
+
import { cropImageToCanvas } from "../utils/crop-image";
|
|
6
|
+
import { validateImage } from "../utils/validate-image";
|
|
7
|
+
import { processImageAction } from "../actions/process-image.action";
|
|
8
|
+
function useImageUpload(options) {
|
|
9
|
+
const { config, onSuccess, onError } = options;
|
|
10
|
+
const [originalImage, setOriginalImage] = useState(null);
|
|
11
|
+
const [croppedPreview, setCroppedPreview] = useState(null);
|
|
12
|
+
const [showCropModal, setShowCropModal] = useState(false);
|
|
13
|
+
const processMutation = useMutation({
|
|
14
|
+
mutationFn: processImageAction,
|
|
15
|
+
onSuccess: (result) => {
|
|
16
|
+
if (result.success && result.base64Image) {
|
|
17
|
+
setCroppedPreview(result.base64Image);
|
|
18
|
+
onSuccess?.(result.base64Image);
|
|
19
|
+
} else {
|
|
20
|
+
onError?.(result.error || "Erro desconhecido");
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
onError: (error) => {
|
|
24
|
+
onError?.(error instanceof Error ? error.message : "Erro ao processar");
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
const handleFileSelect = useCallback(
|
|
28
|
+
async (file) => {
|
|
29
|
+
const validation = validateImage(file);
|
|
30
|
+
if (!validation.valid) {
|
|
31
|
+
onError?.(validation.error || "Arquivo inv\xE1lido");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const compressed = await compressImage(file);
|
|
36
|
+
const reader = new FileReader();
|
|
37
|
+
reader.onload = (e) => {
|
|
38
|
+
setOriginalImage(e.target?.result);
|
|
39
|
+
setShowCropModal(true);
|
|
40
|
+
};
|
|
41
|
+
reader.readAsDataURL(compressed);
|
|
42
|
+
} catch {
|
|
43
|
+
onError?.("Erro ao processar imagem");
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
[onError]
|
|
47
|
+
);
|
|
48
|
+
const handleCropConfirm = useCallback(
|
|
49
|
+
async (imageElement, cropArea) => {
|
|
50
|
+
try {
|
|
51
|
+
const croppedBase64 = await cropImageToCanvas(
|
|
52
|
+
imageElement,
|
|
53
|
+
cropArea,
|
|
54
|
+
config.width,
|
|
55
|
+
config.height
|
|
56
|
+
);
|
|
57
|
+
setCroppedPreview(croppedBase64);
|
|
58
|
+
setShowCropModal(false);
|
|
59
|
+
processMutation.mutate({
|
|
60
|
+
base64Image: croppedBase64,
|
|
61
|
+
targetWidth: config.width,
|
|
62
|
+
targetHeight: config.height,
|
|
63
|
+
cropArea: {
|
|
64
|
+
x: cropArea.x,
|
|
65
|
+
y: cropArea.y,
|
|
66
|
+
width: cropArea.width,
|
|
67
|
+
height: cropArea.height,
|
|
68
|
+
unit: cropArea.unit
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
} catch {
|
|
72
|
+
onError?.("Erro ao aplicar crop");
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
[config, processMutation, onError]
|
|
76
|
+
);
|
|
77
|
+
const clear = useCallback(() => {
|
|
78
|
+
setOriginalImage(null);
|
|
79
|
+
setCroppedPreview(null);
|
|
80
|
+
}, []);
|
|
81
|
+
return {
|
|
82
|
+
originalImage,
|
|
83
|
+
croppedPreview,
|
|
84
|
+
showCropModal,
|
|
85
|
+
config,
|
|
86
|
+
isProcessing: processMutation.isPending,
|
|
87
|
+
handleFileSelect,
|
|
88
|
+
handleCropConfirm,
|
|
89
|
+
setShowCropModal,
|
|
90
|
+
clear
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export {
|
|
94
|
+
useImageUpload
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=use-image-upload.hook.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/images/hooks/use-image-upload.hook.ts"],"sourcesContent":["'use client'\n\nimport { useState, useCallback } from 'react'\nimport { useMutation } from '@tanstack/react-query'\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 { processImageAction } from '../actions/process-image.action'\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\n const processMutation = useMutation({\n mutationFn: processImageAction,\n onSuccess: (result) => {\n if (result.success && result.base64Image) {\n setCroppedPreview(result.base64Image)\n onSuccess?.(result.base64Image)\n } else {\n onError?.(result.error || 'Erro desconhecido')\n }\n },\n onError: (error) => {\n onError?.(error instanceof Error ? error.message : 'Erro ao processar')\n },\n })\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 try {\n const croppedBase64 = await cropImageToCanvas(\n imageElement,\n cropArea,\n config.width,\n config.height\n )\n\n setCroppedPreview(croppedBase64)\n setShowCropModal(false)\n\n processMutation.mutate({\n base64Image: croppedBase64,\n targetWidth: config.width,\n targetHeight: config.height,\n cropArea: {\n x: cropArea.x,\n y: cropArea.y,\n width: cropArea.width,\n height: cropArea.height,\n unit: cropArea.unit,\n },\n })\n } catch {\n onError?.('Erro ao aplicar crop')\n }\n },\n [config, processMutation, 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: processMutation.isPending,\n handleFileSelect,\n handleCropConfirm,\n setShowCropModal,\n clear,\n }\n}\n"],"mappings":";AAEA,SAAS,UAAU,mBAAmB;AACtC,SAAS,mBAAmB;AAE5B,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAClC,SAAS,qBAAqB;AAC9B,SAAS,0BAA0B;AAS5B,SAAS,eAAe,SAAgC;AAC7D,QAAM,EAAE,QAAQ,WAAW,QAAQ,IAAI;AAEvC,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAwB,IAAI;AACtE,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAwB,IAAI;AACxE,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAS,KAAK;AAExD,QAAM,kBAAkB,YAAY;AAAA,IAClC,YAAY;AAAA,IACZ,WAAW,CAAC,WAAW;AACrB,UAAI,OAAO,WAAW,OAAO,aAAa;AACxC,0BAAkB,OAAO,WAAW;AACpC,oBAAY,OAAO,WAAW;AAAA,MAChC,OAAO;AACL,kBAAU,OAAO,SAAS,mBAAmB;AAAA,MAC/C;AAAA,IACF;AAAA,IACA,SAAS,CAAC,UAAU;AAClB,gBAAU,iBAAiB,QAAQ,MAAM,UAAU,mBAAmB;AAAA,IACxE;AAAA,EACF,CAAC;AAED,QAAM,mBAAmB;AAAA,IACvB,OAAO,SAAe;AACpB,YAAM,aAAa,cAAc,IAAI;AACrC,UAAI,CAAC,WAAW,OAAO;AACrB,kBAAU,WAAW,SAAS,qBAAkB;AAChD;AAAA,MACF;AAEA,UAAI;AACF,cAAM,aAAa,MAAM,cAAc,IAAI;AAC3C,cAAM,SAAS,IAAI,WAAW;AAC9B,eAAO,SAAS,CAAC,MAAM;AACrB,2BAAiB,EAAE,QAAQ,MAAgB;AAC3C,2BAAiB,IAAI;AAAA,QACvB;AACA,eAAO,cAAc,UAAU;AAAA,MACjC,QAAQ;AACN,kBAAU,0BAA0B;AAAA,MACtC;AAAA,IACF;AAAA,IACA,CAAC,OAAO;AAAA,EACV;AAEA,QAAM,oBAAoB;AAAA,IACxB,OAAO,cAAgC,aAAmB;AACxD,UAAI;AACF,cAAM,gBAAgB,MAAM;AAAA,UAC1B;AAAA,UACA;AAAA,UACA,OAAO;AAAA,UACP,OAAO;AAAA,QACT;AAEA,0BAAkB,aAAa;AAC/B,yBAAiB,KAAK;AAEtB,wBAAgB,OAAO;AAAA,UACrB,aAAa;AAAA,UACb,aAAa,OAAO;AAAA,UACpB,cAAc,OAAO;AAAA,UACrB,UAAU;AAAA,YACR,GAAG,SAAS;AAAA,YACZ,GAAG,SAAS;AAAA,YACZ,OAAO,SAAS;AAAA,YAChB,QAAQ,SAAS;AAAA,YACjB,MAAM,SAAS;AAAA,UACjB;AAAA,QACF,CAAC;AAAA,MACH,QAAQ;AACN,kBAAU,sBAAsB;AAAA,MAClC;AAAA,IACF;AAAA,IACA,CAAC,QAAQ,iBAAiB,OAAO;AAAA,EACnC;AAEA,QAAM,QAAQ,YAAY,MAAM;AAC9B,qBAAiB,IAAI;AACrB,sBAAkB,IAAI;AAAA,EACxB,GAAG,CAAC,CAAC;AAEL,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc,gBAAgB;AAAA,IAC9B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PhotonImage,
|
|
3
|
+
resize,
|
|
4
|
+
crop,
|
|
5
|
+
SamplingFilter
|
|
6
|
+
} from "@cf-wasm/photon/edge-light";
|
|
7
|
+
function processImage(input) {
|
|
8
|
+
const {
|
|
9
|
+
imageBytes,
|
|
10
|
+
targetWidth,
|
|
11
|
+
targetHeight,
|
|
12
|
+
outputFormat = "jpeg",
|
|
13
|
+
quality = 85
|
|
14
|
+
} = input;
|
|
15
|
+
const image = PhotonImage.new_from_byteslice(imageBytes);
|
|
16
|
+
try {
|
|
17
|
+
const sourceWidth = image.get_width();
|
|
18
|
+
const sourceHeight = image.get_height();
|
|
19
|
+
const targetAspect = targetWidth / targetHeight;
|
|
20
|
+
const sourceAspect = sourceWidth / sourceHeight;
|
|
21
|
+
let cropX1 = 0;
|
|
22
|
+
let cropY1 = 0;
|
|
23
|
+
let cropX2 = sourceWidth;
|
|
24
|
+
let cropY2 = sourceHeight;
|
|
25
|
+
if (sourceAspect > targetAspect) {
|
|
26
|
+
const cropWidth = Math.round(sourceHeight * targetAspect);
|
|
27
|
+
cropX1 = Math.round((sourceWidth - cropWidth) / 2);
|
|
28
|
+
cropX2 = cropX1 + cropWidth;
|
|
29
|
+
} else if (sourceAspect < targetAspect) {
|
|
30
|
+
const cropHeight = Math.round(sourceWidth / targetAspect);
|
|
31
|
+
cropY1 = Math.round((sourceHeight - cropHeight) / 2);
|
|
32
|
+
cropY2 = cropY1 + cropHeight;
|
|
33
|
+
}
|
|
34
|
+
const cropped = crop(image, cropX1, cropY1, cropX2, cropY2);
|
|
35
|
+
const resized = resize(
|
|
36
|
+
cropped,
|
|
37
|
+
targetWidth,
|
|
38
|
+
targetHeight,
|
|
39
|
+
SamplingFilter.Triangle
|
|
40
|
+
);
|
|
41
|
+
let outputBytes;
|
|
42
|
+
switch (outputFormat) {
|
|
43
|
+
case "jpeg":
|
|
44
|
+
outputBytes = resized.get_bytes_jpeg(quality);
|
|
45
|
+
break;
|
|
46
|
+
case "png":
|
|
47
|
+
outputBytes = resized.get_bytes();
|
|
48
|
+
break;
|
|
49
|
+
case "webp":
|
|
50
|
+
outputBytes = resized.get_bytes_webp();
|
|
51
|
+
break;
|
|
52
|
+
default:
|
|
53
|
+
outputBytes = resized.get_bytes_jpeg(quality);
|
|
54
|
+
}
|
|
55
|
+
cropped.free();
|
|
56
|
+
resized.free();
|
|
57
|
+
return {
|
|
58
|
+
bytes: outputBytes,
|
|
59
|
+
width: targetWidth,
|
|
60
|
+
height: targetHeight,
|
|
61
|
+
format: `image/${outputFormat}`
|
|
62
|
+
};
|
|
63
|
+
} finally {
|
|
64
|
+
image.free();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export {
|
|
68
|
+
processImage
|
|
69
|
+
};
|
|
70
|
+
//# sourceMappingURL=image-processing.service.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/images/services/image-processing.service.ts"],"sourcesContent":["import {\n PhotonImage,\n resize,\n crop,\n SamplingFilter,\n} from '@cf-wasm/photon/edge-light'\nimport type { ProcessImageInput, ProcessImageOutput } from '../types/image.type'\n\nexport function processImage(input: ProcessImageInput): ProcessImageOutput {\n const {\n imageBytes,\n targetWidth,\n targetHeight,\n outputFormat = 'jpeg',\n quality = 85,\n } = input\n\n const image = PhotonImage.new_from_byteslice(imageBytes)\n\n try {\n const sourceWidth = image.get_width()\n const sourceHeight = image.get_height()\n const targetAspect = targetWidth / targetHeight\n const sourceAspect = sourceWidth / sourceHeight\n\n let cropX1 = 0\n let cropY1 = 0\n let cropX2 = sourceWidth\n let cropY2 = sourceHeight\n\n if (sourceAspect > targetAspect) {\n const cropWidth = Math.round(sourceHeight * targetAspect)\n cropX1 = Math.round((sourceWidth - cropWidth) / 2)\n cropX2 = cropX1 + cropWidth\n } else if (sourceAspect < targetAspect) {\n const cropHeight = Math.round(sourceWidth / targetAspect)\n cropY1 = Math.round((sourceHeight - cropHeight) / 2)\n cropY2 = cropY1 + cropHeight\n }\n\n const cropped = crop(image, cropX1, cropY1, cropX2, cropY2)\n const resized = resize(\n cropped,\n targetWidth,\n targetHeight,\n SamplingFilter.Triangle\n )\n\n let outputBytes: Uint8Array\n switch (outputFormat) {\n case 'jpeg':\n outputBytes = resized.get_bytes_jpeg(quality)\n break\n case 'png':\n outputBytes = resized.get_bytes()\n break\n case 'webp':\n outputBytes = resized.get_bytes_webp()\n break\n default:\n outputBytes = resized.get_bytes_jpeg(quality)\n }\n\n cropped.free()\n resized.free()\n\n return {\n bytes: outputBytes,\n width: targetWidth,\n height: targetHeight,\n format: `image/${outputFormat}`,\n }\n } finally {\n image.free()\n }\n}\n"],"mappings":"AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGA,SAAS,aAAa,OAA8C;AACzE,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe;AAAA,IACf,UAAU;AAAA,EACZ,IAAI;AAEJ,QAAM,QAAQ,YAAY,mBAAmB,UAAU;AAEvD,MAAI;AACF,UAAM,cAAc,MAAM,UAAU;AACpC,UAAM,eAAe,MAAM,WAAW;AACtC,UAAM,eAAe,cAAc;AACnC,UAAM,eAAe,cAAc;AAEnC,QAAI,SAAS;AACb,QAAI,SAAS;AACb,QAAI,SAAS;AACb,QAAI,SAAS;AAEb,QAAI,eAAe,cAAc;AAC/B,YAAM,YAAY,KAAK,MAAM,eAAe,YAAY;AACxD,eAAS,KAAK,OAAO,cAAc,aAAa,CAAC;AACjD,eAAS,SAAS;AAAA,IACpB,WAAW,eAAe,cAAc;AACtC,YAAM,aAAa,KAAK,MAAM,cAAc,YAAY;AACxD,eAAS,KAAK,OAAO,eAAe,cAAc,CAAC;AACnD,eAAS,SAAS;AAAA,IACpB;AAEA,UAAM,UAAU,KAAK,OAAO,QAAQ,QAAQ,QAAQ,MAAM;AAC1D,UAAM,UAAU;AAAA,MACd;AAAA,MACA;AAAA,MACA;AAAA,MACA,eAAe;AAAA,IACjB;AAEA,QAAI;AACJ,YAAQ,cAAc;AAAA,MACpB,KAAK;AACH,sBAAc,QAAQ,eAAe,OAAO;AAC5C;AAAA,MACF,KAAK;AACH,sBAAc,QAAQ,UAAU;AAChC;AAAA,MACF,KAAK;AACH,sBAAc,QAAQ,eAAe;AACrC;AAAA,MACF;AACE,sBAAc,QAAQ,eAAe,OAAO;AAAA,IAChD;AAEA,YAAQ,KAAK;AACb,YAAQ,KAAK;AAEb,WAAO;AAAA,MACL,OAAO;AAAA,MACP,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,QAAQ,SAAS,YAAY;AAAA,IAC/B;AAAA,EACF,UAAE;AACA,UAAM,KAAK;AAAA,EACb;AACF;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=image.type.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import imageCompression from "browser-image-compression";
|
|
2
|
+
import { TARGET_FILE_SIZE_MB } from "../constants/image.constants";
|
|
3
|
+
async function compressImage(file, options = {}) {
|
|
4
|
+
const { maxSizeMB = TARGET_FILE_SIZE_MB, maxWidthOrHeight = 1920 } = options;
|
|
5
|
+
if (file.size <= maxSizeMB * 1024 * 1024) {
|
|
6
|
+
return file;
|
|
7
|
+
}
|
|
8
|
+
return imageCompression(file, {
|
|
9
|
+
maxSizeMB,
|
|
10
|
+
maxWidthOrHeight,
|
|
11
|
+
useWebWorker: true,
|
|
12
|
+
fileType: file.type
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
compressImage
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=compress-image.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/images/utils/compress-image.ts"],"sourcesContent":["import imageCompression from 'browser-image-compression'\nimport type { CompressImageOptions } from '../types/image.type'\nimport { TARGET_FILE_SIZE_MB } from '../constants/image.constants'\n\nexport async function compressImage(\n file: File,\n options: CompressImageOptions = {}\n): Promise<File> {\n const { maxSizeMB = TARGET_FILE_SIZE_MB, maxWidthOrHeight = 1920 } = options\n\n if (file.size <= maxSizeMB * 1024 * 1024) {\n return file\n }\n\n return imageCompression(file, {\n maxSizeMB,\n maxWidthOrHeight,\n useWebWorker: true,\n fileType: file.type as 'image/jpeg' | 'image/png' | 'image/webp',\n })\n}\n"],"mappings":"AAAA,OAAO,sBAAsB;AAE7B,SAAS,2BAA2B;AAEpC,eAAsB,cACpB,MACA,UAAgC,CAAC,GAClB;AACf,QAAM,EAAE,YAAY,qBAAqB,mBAAmB,KAAK,IAAI;AAErE,MAAI,KAAK,QAAQ,YAAY,OAAO,MAAM;AACxC,WAAO;AAAA,EACT;AAEA,SAAO,iBAAiB,MAAM;AAAA,IAC5B;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd,UAAU,KAAK;AAAA,EACjB,CAAC;AACH;","names":[]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
async function cropImageToCanvas(image, crop, targetWidth, targetHeight) {
|
|
2
|
+
const canvas = document.createElement("canvas");
|
|
3
|
+
const ctx = canvas.getContext("2d");
|
|
4
|
+
if (!ctx) throw new Error("Canvas n\xE3o suportado");
|
|
5
|
+
const pixelCrop = {
|
|
6
|
+
x: crop.unit === "%" ? crop.x / 100 * image.naturalWidth : crop.x,
|
|
7
|
+
y: crop.unit === "%" ? crop.y / 100 * image.naturalHeight : crop.y,
|
|
8
|
+
width: crop.unit === "%" ? crop.width / 100 * image.naturalWidth : crop.width,
|
|
9
|
+
height: crop.unit === "%" ? crop.height / 100 * image.naturalHeight : crop.height
|
|
10
|
+
};
|
|
11
|
+
canvas.width = targetWidth;
|
|
12
|
+
canvas.height = targetHeight;
|
|
13
|
+
ctx.drawImage(
|
|
14
|
+
image,
|
|
15
|
+
pixelCrop.x,
|
|
16
|
+
pixelCrop.y,
|
|
17
|
+
pixelCrop.width,
|
|
18
|
+
pixelCrop.height,
|
|
19
|
+
0,
|
|
20
|
+
0,
|
|
21
|
+
targetWidth,
|
|
22
|
+
targetHeight
|
|
23
|
+
);
|
|
24
|
+
return canvas.toDataURL("image/jpeg", 0.9);
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
cropImageToCanvas
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=crop-image.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/images/utils/crop-image.ts"],"sourcesContent":["import type { Crop } from 'react-image-crop'\n\nexport async function cropImageToCanvas(\n image: HTMLImageElement,\n crop: Crop,\n targetWidth: number,\n targetHeight: number\n): Promise<string> {\n const canvas = document.createElement('canvas')\n const ctx = canvas.getContext('2d')\n\n if (!ctx) throw new Error('Canvas não suportado')\n\n const pixelCrop = {\n x: crop.unit === '%' ? (crop.x / 100) * image.naturalWidth : crop.x,\n y: crop.unit === '%' ? (crop.y / 100) * image.naturalHeight : crop.y,\n width:\n crop.unit === '%' ? (crop.width / 100) * image.naturalWidth : crop.width,\n height:\n crop.unit === '%'\n ? (crop.height / 100) * image.naturalHeight\n : crop.height,\n }\n\n canvas.width = targetWidth\n canvas.height = targetHeight\n\n ctx.drawImage(\n image,\n pixelCrop.x,\n pixelCrop.y,\n pixelCrop.width,\n pixelCrop.height,\n 0,\n 0,\n targetWidth,\n targetHeight\n )\n\n return canvas.toDataURL('image/jpeg', 0.9)\n}\n"],"mappings":"AAEA,eAAsB,kBACpB,OACA,MACA,aACA,cACiB;AACjB,QAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,QAAM,MAAM,OAAO,WAAW,IAAI;AAElC,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,yBAAsB;AAEhD,QAAM,YAAY;AAAA,IAChB,GAAG,KAAK,SAAS,MAAO,KAAK,IAAI,MAAO,MAAM,eAAe,KAAK;AAAA,IAClE,GAAG,KAAK,SAAS,MAAO,KAAK,IAAI,MAAO,MAAM,gBAAgB,KAAK;AAAA,IACnE,OACE,KAAK,SAAS,MAAO,KAAK,QAAQ,MAAO,MAAM,eAAe,KAAK;AAAA,IACrE,QACE,KAAK,SAAS,MACT,KAAK,SAAS,MAAO,MAAM,gBAC5B,KAAK;AAAA,EACb;AAEA,SAAO,QAAQ;AACf,SAAO,SAAS;AAEhB,MAAI;AAAA,IACF;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,IACV,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO,OAAO,UAAU,cAAc,GAAG;AAC3C;","names":[]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ACCEPTED_IMAGE_FORMATS,
|
|
3
|
+
MAX_FILE_SIZE_MB
|
|
4
|
+
} from "../constants/image.constants";
|
|
5
|
+
function validateImageFormat(file) {
|
|
6
|
+
if (!ACCEPTED_IMAGE_FORMATS.includes(file.type)) {
|
|
7
|
+
return {
|
|
8
|
+
valid: false,
|
|
9
|
+
error: "Formato n\xE3o suportado. Use PNG, JPEG ou WebP."
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
return { valid: true };
|
|
13
|
+
}
|
|
14
|
+
function validateImageSize(file, maxSizeMB = MAX_FILE_SIZE_MB) {
|
|
15
|
+
if (file.size > maxSizeMB * 1024 * 1024) {
|
|
16
|
+
return {
|
|
17
|
+
valid: false,
|
|
18
|
+
error: `Arquivo muito grande. M\xE1ximo ${maxSizeMB}MB.`
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return { valid: true };
|
|
22
|
+
}
|
|
23
|
+
function validateImage(file) {
|
|
24
|
+
const formatValidation = validateImageFormat(file);
|
|
25
|
+
if (!formatValidation.valid) return formatValidation;
|
|
26
|
+
const sizeValidation = validateImageSize(file);
|
|
27
|
+
if (!sizeValidation.valid) return sizeValidation;
|
|
28
|
+
return { valid: true };
|
|
29
|
+
}
|
|
30
|
+
async function getImageDimensions(file) {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const img = new Image();
|
|
33
|
+
img.onload = () => {
|
|
34
|
+
resolve({ width: img.naturalWidth, height: img.naturalHeight });
|
|
35
|
+
URL.revokeObjectURL(img.src);
|
|
36
|
+
};
|
|
37
|
+
img.onerror = () => {
|
|
38
|
+
reject(new Error("Falha ao carregar imagem"));
|
|
39
|
+
URL.revokeObjectURL(img.src);
|
|
40
|
+
};
|
|
41
|
+
img.src = URL.createObjectURL(file);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
export {
|
|
45
|
+
getImageDimensions,
|
|
46
|
+
validateImage,
|
|
47
|
+
validateImageFormat,
|
|
48
|
+
validateImageSize
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=validate-image.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/images/utils/validate-image.ts"],"sourcesContent":["import {\n ACCEPTED_IMAGE_FORMATS,\n MAX_FILE_SIZE_MB,\n} from '../constants/image.constants'\n\nexport interface ImageValidationResult {\n valid: boolean\n error?: string\n}\n\nexport function validateImageFormat(file: File): ImageValidationResult {\n if (!ACCEPTED_IMAGE_FORMATS.includes(file.type as (typeof ACCEPTED_IMAGE_FORMATS)[number])) {\n return {\n valid: false,\n error: 'Formato não suportado. Use PNG, JPEG ou WebP.',\n }\n }\n return { valid: true }\n}\n\nexport function validateImageSize(\n file: File,\n maxSizeMB: number = MAX_FILE_SIZE_MB\n): ImageValidationResult {\n if (file.size > maxSizeMB * 1024 * 1024) {\n return {\n valid: false,\n error: `Arquivo muito grande. Máximo ${maxSizeMB}MB.`,\n }\n }\n return { valid: true }\n}\n\nexport function validateImage(file: File): ImageValidationResult {\n const formatValidation = validateImageFormat(file)\n if (!formatValidation.valid) return formatValidation\n\n const sizeValidation = validateImageSize(file)\n if (!sizeValidation.valid) return sizeValidation\n\n return { valid: true }\n}\n\nexport async function getImageDimensions(\n file: File\n): Promise<{ width: number; height: number }> {\n return new Promise((resolve, reject) => {\n const img = new Image()\n img.onload = () => {\n resolve({ width: img.naturalWidth, height: img.naturalHeight })\n URL.revokeObjectURL(img.src)\n }\n img.onerror = () => {\n reject(new Error('Falha ao carregar imagem'))\n URL.revokeObjectURL(img.src)\n }\n img.src = URL.createObjectURL(file)\n })\n}\n"],"mappings":"AAAA;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAOA,SAAS,oBAAoB,MAAmC;AACrE,MAAI,CAAC,uBAAuB,SAAS,KAAK,IAA+C,GAAG;AAC1F,WAAO;AAAA,MACL,OAAO;AAAA,MACP,OAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO,EAAE,OAAO,KAAK;AACvB;AAEO,SAAS,kBACd,MACA,YAAoB,kBACG;AACvB,MAAI,KAAK,OAAO,YAAY,OAAO,MAAM;AACvC,WAAO;AAAA,MACL,OAAO;AAAA,MACP,OAAO,mCAAgC,SAAS;AAAA,IAClD;AAAA,EACF;AACA,SAAO,EAAE,OAAO,KAAK;AACvB;AAEO,SAAS,cAAc,MAAmC;AAC/D,QAAM,mBAAmB,oBAAoB,IAAI;AACjD,MAAI,CAAC,iBAAiB,MAAO,QAAO;AAEpC,QAAM,iBAAiB,kBAAkB,IAAI;AAC7C,MAAI,CAAC,eAAe,MAAO,QAAO;AAElC,SAAO,EAAE,OAAO,KAAK;AACvB;AAEA,eAAsB,mBACpB,MAC4C;AAC5C,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,MAAM,IAAI,MAAM;AACtB,QAAI,SAAS,MAAM;AACjB,cAAQ,EAAE,OAAO,IAAI,cAAc,QAAQ,IAAI,cAAc,CAAC;AAC9D,UAAI,gBAAgB,IAAI,GAAG;AAAA,IAC7B;AACA,QAAI,UAAU,MAAM;AAClB,aAAO,IAAI,MAAM,0BAA0B,CAAC;AAC5C,UAAI,gBAAgB,IAAI,GAAG;AAAA,IAC7B;AACA,QAAI,MAAM,IAAI,gBAAgB,IAAI;AAAA,EACpC,CAAC;AACH;","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "server-only";
|
|
2
|
-
import { api } from "@greatapps/common/server";
|
|
2
|
+
import { api, getUserContext } from "@greatapps/common/server";
|
|
3
3
|
import { ApiError, buildQueryParams, PlanSchema } from "@greatapps/common";
|
|
4
4
|
import greatCache from "@greatapps/cache";
|
|
5
5
|
const PLANS_CACHE_TTL = 604800;
|
|
@@ -22,7 +22,11 @@ class PlansService {
|
|
|
22
22
|
* GET /{id_wl}/plans?active=true&search=client&sort=id:ASC
|
|
23
23
|
*/
|
|
24
24
|
async listPlans(params) {
|
|
25
|
-
const
|
|
25
|
+
const { id_account } = await getUserContext();
|
|
26
|
+
const cacheKey = this.buildCacheKey("plans", {
|
|
27
|
+
...params,
|
|
28
|
+
id_account
|
|
29
|
+
});
|
|
26
30
|
const cachedData = await this.cache.select(cacheKey);
|
|
27
31
|
if (cachedData.status == 1 && "data" in cachedData && cachedData.data) {
|
|
28
32
|
const data2 = JSON.parse(cachedData.data);
|
|
@@ -32,7 +36,7 @@ class PlansService {
|
|
|
32
36
|
sort: params?.sort ?? "id:ASC",
|
|
33
37
|
active: params?.active,
|
|
34
38
|
search: params?.search,
|
|
35
|
-
id_account
|
|
39
|
+
id_account
|
|
36
40
|
});
|
|
37
41
|
const url = `/plans${query ? `?${query}` : ""}`;
|
|
38
42
|
const response = await api.apps.get(url);
|
|
@@ -52,15 +56,19 @@ class PlansService {
|
|
|
52
56
|
success: true
|
|
53
57
|
};
|
|
54
58
|
}
|
|
55
|
-
async findById(idPlan
|
|
56
|
-
const
|
|
59
|
+
async findById(idPlan) {
|
|
60
|
+
const { id_account } = await getUserContext();
|
|
61
|
+
const cacheKey = this.buildCacheKey(`plan-${idPlan}`, {
|
|
62
|
+
id_account: String(id_account)
|
|
63
|
+
});
|
|
57
64
|
const cachedData = await this.cache.select(cacheKey);
|
|
58
65
|
if (cachedData.status == 1 && "data" in cachedData && cachedData.data) {
|
|
59
66
|
const data = JSON.parse(cachedData.data);
|
|
60
67
|
return { data, success: true };
|
|
61
68
|
}
|
|
69
|
+
const query = buildQueryParams({ type: "client", id_account });
|
|
62
70
|
const response = await api.apps.get(
|
|
63
|
-
`/plans/${idPlan}
|
|
71
|
+
`/plans/${idPlan}?${query}`
|
|
64
72
|
);
|
|
65
73
|
if (response.status === 0) {
|
|
66
74
|
throw new ApiError(
|
|
@@ -70,11 +78,7 @@ class PlansService {
|
|
|
70
78
|
);
|
|
71
79
|
}
|
|
72
80
|
if (!response.data?.length) {
|
|
73
|
-
throw new ApiError(
|
|
74
|
-
"Plano n\xE3o encontrado",
|
|
75
|
-
"PLAN_NOT_FOUND",
|
|
76
|
-
404
|
|
77
|
-
);
|
|
81
|
+
throw new ApiError("Plano n\xE3o encontrado", "PLAN_NOT_FOUND", 404);
|
|
78
82
|
}
|
|
79
83
|
const plan = PlanSchema.parse(response.data[0]);
|
|
80
84
|
await this.cache.insert(cacheKey, JSON.stringify(plan), PLANS_CACHE_TTL);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/modules/plans/services/plans.service.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/plans/services/plans.service.ts"],"sourcesContent":["import \"server-only\";\n\nimport { api, apiClient, getUserContext } from \"@greatapps/common/server\";\nimport { ApiError, buildQueryParams, PlanSchema } from \"@greatapps/common\";\nimport type {\n ApiPaginatedActionResult,\n PaginatedSuccessResult,\n Plan,\n SuccessResult,\n} from \"@greatapps/common\";\nimport greatCache from \"@greatapps/cache\";\n\nexport type ListPlansParams = {\n sort?: string;\n active?: boolean;\n search?: string;\n};\n\nconst PLANS_CACHE_TTL = 604800;\n\nclass PlansService {\n private cache = new greatCache({\n service: \"plans\",\n version: \"1.0.2\",\n domain: \"whitelabel-cache.greatapps.com.br\",\n ambient: process.env.NODE_ENV || \"development\",\n });\n\n private buildCacheKey(key: string, params?: Record<string, unknown>): string {\n const sort = params?.sort ?? \"id:ASC\";\n const active = params?.active ?? \"\";\n const search = params?.search ?? \"\";\n return `${key}-${sort}-${active}-${search}-v2`;\n }\n\n /**\n * Lista planos do whitelabel.\n * Exemplo:\n * GET /{id_wl}/plans?active=true&search=client&sort=id:ASC\n */\n async listPlans(\n params?: ListPlansParams,\n ): Promise<PaginatedSuccessResult<Plan>> {\n const { id_account } = await getUserContext();\n const cacheKey = this.buildCacheKey(\"plans\", {\n ...params,\n id_account,\n });\n\n const cachedData = await this.cache.select(cacheKey);\n\n if (cachedData.status == 1 && \"data\" in cachedData && cachedData.data) {\n const data = JSON.parse(cachedData.data) as Plan[];\n return { data, total: data.length, success: true };\n }\n\n const query = buildQueryParams({\n sort: params?.sort ?? \"id:ASC\",\n active: params?.active,\n search: params?.search,\n id_account: id_account,\n });\n const url = `/plans${query ? `?${query}` : \"\"}`;\n\n const response = await api.apps.get<ApiPaginatedActionResult<Plan[]>>(url);\n\n if (response.status === 0) {\n throw new ApiError(\n (response as { message?: string }).message || \"Erro ao listar planos\",\n \"LIST_PLANS_FAILED\",\n 400,\n );\n }\n\n const rawData = (response as { data?: unknown }).data;\n const data = Array.isArray(rawData)\n ? rawData.map((item) => PlanSchema.parse(item))\n : [];\n\n await this.cache.insert(cacheKey, JSON.stringify(data), PLANS_CACHE_TTL);\n\n return {\n data,\n total: response.total,\n success: true,\n } satisfies PaginatedSuccessResult<Plan>;\n }\n\n async findById(idPlan: number | string): Promise<SuccessResult<Plan>> {\n const { id_account } = await getUserContext();\n const cacheKey = this.buildCacheKey(`plan-${idPlan}`, {\n id_account: String(id_account),\n });\n\n const cachedData = await this.cache.select(cacheKey);\n\n if (cachedData.status == 1 && \"data\" in cachedData && cachedData.data) {\n const data = JSON.parse(cachedData.data) as Plan;\n return { data, success: true };\n }\n\n const query = buildQueryParams({ type: \"client\", id_account });\n\n const response = await api.apps.get<ApiPaginatedActionResult<Plan>>(\n `/plans/${idPlan}?${query}`,\n );\n\n if (response.status === 0) {\n throw new ApiError(\n response.message || \"Erro ao buscar plano\",\n \"FIND_PLAN_FAILED\",\n 400,\n );\n }\n\n if (!response.data?.length) {\n throw new ApiError(\"Plano não encontrado\", \"PLAN_NOT_FOUND\", 404);\n }\n\n const plan = PlanSchema.parse(response.data[0]);\n\n await this.cache.insert(cacheKey, JSON.stringify(plan), PLANS_CACHE_TTL);\n\n return {\n success: true,\n data: plan,\n };\n }\n}\n\nexport const plansService = new PlansService();\n"],"mappings":"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","names":["data"]}
|
package/dist/server.mjs
CHANGED
|
@@ -53,6 +53,8 @@ import { getClientInfoFromRequest } from "./infra/utils/client-info";
|
|
|
53
53
|
import { getUserContext } from "./modules/auth/utils/get-user-context";
|
|
54
54
|
import { buildWlOverride, buildWlOverrideFromJwt } from "./modules/auth/utils/build-wl-override";
|
|
55
55
|
import { safeServerAction } from "./utils/safeServerAction";
|
|
56
|
+
import { processImageAction } from "./modules/images/actions/process-image.action";
|
|
57
|
+
import { processImage } from "./modules/images/services/image-processing.service";
|
|
56
58
|
export {
|
|
57
59
|
ApiClient,
|
|
58
60
|
accountService,
|
|
@@ -94,6 +96,8 @@ export {
|
|
|
94
96
|
listProjectUsersAction,
|
|
95
97
|
listSubscriptionsAction,
|
|
96
98
|
plansService,
|
|
99
|
+
processImage,
|
|
100
|
+
processImageAction,
|
|
97
101
|
projectUsersService,
|
|
98
102
|
removeProjectUserAction,
|
|
99
103
|
requestEmailChangeAction,
|
package/dist/server.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["import 'server-only';\r\n\r\n// API Client\r\nexport { ApiClient, api, apiClient } from './infra/api/client';\r\n\r\n// Services\r\nexport { authService } from './modules/auth/services/auth.service';\r\nexport { whitelabelService } from './modules/whitelabel/services/whitelabel.service';\r\nexport { revalidateWhitelabelAction } from './modules/whitelabel/actions/revalidate-whitelabel.action';\r\nexport { assertManagementPermission } from './modules/auth/utils/assert-management-permission';\r\nexport { assertManagementSubscription } from './modules/auth/utils/assert-management-subscription';\r\nexport { assertSubscriptionAddon } from './modules/auth/utils/assert-subscription-addon';\r\nexport { assertPaidSubscription } from './modules/auth/utils/assert-paid-subscription';\r\nexport { subscriptionsService } from './modules/subscriptions/services/subscriptions.service';\r\nexport { plansService } from './modules/plans/services/plans.service';\r\nexport { cardsService } from './modules/cards/services/cards.service';\r\nexport { iaCreditsService } from './modules/ia-credits/services/ia-credits.service';\r\n\r\n// Actions\r\nexport { findWhitelabel } from './modules/whitelabel/actions/find-whitelabel.action';\r\nexport { validateSessionAction } from './modules/auth/actions/validate-session.action';\r\nexport { findUserById } from './modules/users/action/find-user-by-id.action';\r\nexport { findCurrentAccount } from './modules/accounts/actions/find-current-account.action';\r\nexport { listSubscriptionsAction } from './modules/subscriptions/actions/list-subscriptions.action';\r\nexport { findPlanByIdAction } from './modules/plans/actions/find-plan-by-id.action';\r\nexport { listPlansAction } from './modules/plans/actions/list-plans.action';\r\nexport { calculateSubscriptionAction } from './modules/subscriptions/actions/calculate-subscription.action';\r\nexport { updateSubscriptionPlanAction } from './modules/subscriptions/actions/update-subscription-plan.action';\r\nexport { listCardsAction } from './modules/cards/actions/list-cards.action';\r\nexport { createCardAction } from './modules/cards/actions/create-card.action';\r\nexport { resolvePlanExtrasPrices } from './modules/subscriptions/utils/resolve-plan-extras-prices';\r\nexport { hasSubscriptionExpired } from './modules/subscriptions/utils/has-subscription-expired';\r\nexport { listIaCreditsAction } from './modules/ia-credits/actions/list-ia-credits.action';\r\n\r\n// Project Users Services & Actions (server-only)\r\nexport { projectUsersService } from './modules/projects/services/project-users.service';\r\nexport { listProjectUsersAction } from './modules/projects/actions/list-project-users.action';\r\nexport { listAvailableUsersAction } from './modules/projects/actions/list-available-users.action';\r\nexport { addProjectUserAction } from './modules/projects/actions/add-project-user.action';\r\nexport { removeProjectUserAction } from './modules/projects/actions/remove-project-user.action';\r\n\r\n// Account Services & Actions (server-only)\r\nexport { accountService } from './modules/accounts/services/account.service';\r\nexport { twoFactorService } from './modules/accounts/services/two-factor.service';\r\nexport {\r\n listAccountUsersAction,\r\n updateUserAction,\r\n updateAccountAction,\r\n updateAccountUserByIdAction,\r\n deleteAccountUserAction,\r\n deleteAccountAction,\r\n createAccountUserAction,\r\n changePasswordAction,\r\n requestEmailChangeAction,\r\n confirmEmailChangeAction,\r\n requestPhoneChangeAction,\r\n confirmPhoneChangeAction,\r\n generateTwoFactorAction,\r\n confirmTwoFactorAction,\r\n disableTwoFactorAction,\r\n} from './modules/accounts/actions/account-management.action';\r\n\r\n// Server Utils\r\nexport { getClientInfoFromRequest } from './infra/utils/client-info';\r\nexport { getUserContext } from './modules/auth/utils/get-user-context';\r\nexport { buildWlOverride, buildWlOverrideFromJwt } from './modules/auth/utils/build-wl-override';\r\nexport { safeServerAction } from './utils/safeServerAction';\r\n"],"mappings":"AAAA,OAAO;AAGP,SAAS,WAAW,KAAK,iBAAiB;AAG1C,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAClC,SAAS,kCAAkC;AAC3C,SAAS,kCAAkC;AAC3C,SAAS,oCAAoC;AAC7C,SAAS,+BAA+B;AACxC,SAAS,8BAA8B;AACvC,SAAS,4BAA4B;AACrC,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AAGjC,SAAS,sBAAsB;AAC/B,SAAS,6BAA6B;AACtC,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,+BAA+B;AACxC,SAAS,0BAA0B;AACnC,SAAS,uBAAuB;AAChC,SAAS,mCAAmC;AAC5C,SAAS,oCAAoC;AAC7C,SAAS,uBAAuB;AAChC,SAAS,wBAAwB;AACjC,SAAS,+BAA+B;AACxC,SAAS,8BAA8B;AACvC,SAAS,2BAA2B;AAGpC,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;AACvC,SAAS,gCAAgC;AACzC,SAAS,4BAA4B;AACrC,SAAS,+BAA+B;AAGxC,SAAS,sBAAsB;AAC/B,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,gCAAgC;AACzC,SAAS,sBAAsB;AAC/B,SAAS,iBAAiB,8BAA8B;AACxD,SAAS,wBAAwB;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["import 'server-only';\r\n\r\n// API Client\r\nexport { ApiClient, api, apiClient } from './infra/api/client';\r\n\r\n// Services\r\nexport { authService } from './modules/auth/services/auth.service';\r\nexport { whitelabelService } from './modules/whitelabel/services/whitelabel.service';\r\nexport { revalidateWhitelabelAction } from './modules/whitelabel/actions/revalidate-whitelabel.action';\r\nexport { assertManagementPermission } from './modules/auth/utils/assert-management-permission';\r\nexport { assertManagementSubscription } from './modules/auth/utils/assert-management-subscription';\r\nexport { assertSubscriptionAddon } from './modules/auth/utils/assert-subscription-addon';\r\nexport { assertPaidSubscription } from './modules/auth/utils/assert-paid-subscription';\r\nexport { subscriptionsService } from './modules/subscriptions/services/subscriptions.service';\r\nexport { plansService } from './modules/plans/services/plans.service';\r\nexport { cardsService } from './modules/cards/services/cards.service';\r\nexport { iaCreditsService } from './modules/ia-credits/services/ia-credits.service';\r\n\r\n// Actions\r\nexport { findWhitelabel } from './modules/whitelabel/actions/find-whitelabel.action';\r\nexport { validateSessionAction } from './modules/auth/actions/validate-session.action';\r\nexport { findUserById } from './modules/users/action/find-user-by-id.action';\r\nexport { findCurrentAccount } from './modules/accounts/actions/find-current-account.action';\r\nexport { listSubscriptionsAction } from './modules/subscriptions/actions/list-subscriptions.action';\r\nexport { findPlanByIdAction } from './modules/plans/actions/find-plan-by-id.action';\r\nexport { listPlansAction } from './modules/plans/actions/list-plans.action';\r\nexport { calculateSubscriptionAction } from './modules/subscriptions/actions/calculate-subscription.action';\r\nexport { updateSubscriptionPlanAction } from './modules/subscriptions/actions/update-subscription-plan.action';\r\nexport { listCardsAction } from './modules/cards/actions/list-cards.action';\r\nexport { createCardAction } from './modules/cards/actions/create-card.action';\r\nexport { resolvePlanExtrasPrices } from './modules/subscriptions/utils/resolve-plan-extras-prices';\r\nexport { hasSubscriptionExpired } from './modules/subscriptions/utils/has-subscription-expired';\r\nexport { listIaCreditsAction } from './modules/ia-credits/actions/list-ia-credits.action';\r\n\r\n// Project Users Services & Actions (server-only)\r\nexport { projectUsersService } from './modules/projects/services/project-users.service';\r\nexport { listProjectUsersAction } from './modules/projects/actions/list-project-users.action';\r\nexport { listAvailableUsersAction } from './modules/projects/actions/list-available-users.action';\r\nexport { addProjectUserAction } from './modules/projects/actions/add-project-user.action';\r\nexport { removeProjectUserAction } from './modules/projects/actions/remove-project-user.action';\r\n\r\n// Account Services & Actions (server-only)\r\nexport { accountService } from './modules/accounts/services/account.service';\r\nexport { twoFactorService } from './modules/accounts/services/two-factor.service';\r\nexport {\r\n listAccountUsersAction,\r\n updateUserAction,\r\n updateAccountAction,\r\n updateAccountUserByIdAction,\r\n deleteAccountUserAction,\r\n deleteAccountAction,\r\n createAccountUserAction,\r\n changePasswordAction,\r\n requestEmailChangeAction,\r\n confirmEmailChangeAction,\r\n requestPhoneChangeAction,\r\n confirmPhoneChangeAction,\r\n generateTwoFactorAction,\r\n confirmTwoFactorAction,\r\n disableTwoFactorAction,\r\n} from './modules/accounts/actions/account-management.action';\r\n\r\n// Server Utils\r\nexport { getClientInfoFromRequest } from './infra/utils/client-info';\r\nexport { getUserContext } from './modules/auth/utils/get-user-context';\r\nexport { buildWlOverride, buildWlOverrideFromJwt } from './modules/auth/utils/build-wl-override';\r\nexport { safeServerAction } from './utils/safeServerAction';\r\n\r\n// Image Processing (server-side)\r\nexport { processImageAction } from './modules/images/actions/process-image.action';\r\nexport { processImage } from './modules/images/services/image-processing.service';\r\nexport type {\r\n ProcessImageInput,\r\n ProcessImageOutput,\r\n ProcessImageActionInput,\r\n ProcessImageActionOutput,\r\n} from './modules/images/types/image.type';\r\n"],"mappings":"AAAA,OAAO;AAGP,SAAS,WAAW,KAAK,iBAAiB;AAG1C,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAClC,SAAS,kCAAkC;AAC3C,SAAS,kCAAkC;AAC3C,SAAS,oCAAoC;AAC7C,SAAS,+BAA+B;AACxC,SAAS,8BAA8B;AACvC,SAAS,4BAA4B;AACrC,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AAGjC,SAAS,sBAAsB;AAC/B,SAAS,6BAA6B;AACtC,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,+BAA+B;AACxC,SAAS,0BAA0B;AACnC,SAAS,uBAAuB;AAChC,SAAS,mCAAmC;AAC5C,SAAS,oCAAoC;AAC7C,SAAS,uBAAuB;AAChC,SAAS,wBAAwB;AACjC,SAAS,+BAA+B;AACxC,SAAS,8BAA8B;AACvC,SAAS,2BAA2B;AAGpC,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;AACvC,SAAS,gCAAgC;AACzC,SAAS,4BAA4B;AACrC,SAAS,+BAA+B;AAGxC,SAAS,sBAAsB;AAC/B,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,gCAAgC;AACzC,SAAS,sBAAsB;AAC/B,SAAS,iBAAiB,8BAA8B;AACxD,SAAS,wBAAwB;AAGjC,SAAS,0BAA0B;AACnC,SAAS,oBAAoB;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@greatapps/common",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.449",
|
|
4
4
|
"description": "Shared library for GreatApps frontend applications",
|
|
5
5
|
"main": "./dist/index.mjs",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"author": "",
|
|
32
32
|
"license": "ISC",
|
|
33
33
|
"dependencies": {
|
|
34
|
+
"@cf-wasm/photon": "^0.3.4",
|
|
34
35
|
"@greatapps/cache": "^2.0.1",
|
|
35
36
|
"@radix-ui/react-accordion": "^1.2.12",
|
|
36
37
|
"@radix-ui/react-checkbox": "^1.3.3",
|
|
@@ -47,6 +48,7 @@
|
|
|
47
48
|
"@tanstack/query-broadcast-client-experimental": "^5.95.2",
|
|
48
49
|
"@tanstack/react-query-persist-client": "^5.95.2",
|
|
49
50
|
"axios": "^1.13.6",
|
|
51
|
+
"browser-image-compression": "^2.0.2",
|
|
50
52
|
"class-variance-authority": "^0.7.1",
|
|
51
53
|
"clsx": "^2.1.1",
|
|
52
54
|
"cmdk": "^1.1.1",
|
|
@@ -55,6 +57,7 @@
|
|
|
55
57
|
"input-otp": "^1.4.2",
|
|
56
58
|
"libphonenumber-js": "^1.12.40",
|
|
57
59
|
"react-day-picker": "^9.14.0",
|
|
60
|
+
"react-image-crop": "^11.0.10",
|
|
58
61
|
"server-only": "^0.0.1",
|
|
59
62
|
"sonner": ">=2.0.0",
|
|
60
63
|
"tailwind-merge": "^3.5.0",
|
|
@@ -9,7 +9,7 @@ import { useCreateCard } from '../../../modules/cards/hooks/create-card.hook';
|
|
|
9
9
|
import { useUpdateAccount } from '../../../modules/accounts/hooks/useAccountManagement';
|
|
10
10
|
import { CardFormFields, BillingFormFields, cardFormSchema } from './CardFormFields';
|
|
11
11
|
import type { CardFormData, StripeElementsStatus } from './CardFormFields';
|
|
12
|
-
import { IconX } from '@tabler/icons-react';
|
|
12
|
+
import { IconLoader2, IconX } from '@tabler/icons-react';
|
|
13
13
|
import { useForm } from 'react-hook-form';
|
|
14
14
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
15
15
|
import { CardNumberElement, useElements, useStripe } from '@stripe/react-stripe-js';
|
|
@@ -248,6 +248,7 @@ export default function AddCardModal() {
|
|
|
248
248
|
Voltar
|
|
249
249
|
</Button>
|
|
250
250
|
<Button type="submit" className="h-10!" disabled={isSubmitting || !isStep2Valid}>
|
|
251
|
+
{isSubmitting && <IconLoader2 className="size-4 animate-spin" />}
|
|
251
252
|
{isSubmitting ? 'Adicionando cartão...' : 'Adicionar cartão'}
|
|
252
253
|
</Button>
|
|
253
254
|
</>
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useRef, useState } from 'react'
|
|
4
|
+
import ReactCrop, {
|
|
5
|
+
type Crop,
|
|
6
|
+
centerCrop,
|
|
7
|
+
makeAspectCrop,
|
|
8
|
+
} from 'react-image-crop'
|
|
9
|
+
import 'react-image-crop/dist/ReactCrop.css'
|
|
10
|
+
import {
|
|
11
|
+
Dialog,
|
|
12
|
+
DialogContent,
|
|
13
|
+
DialogFooter,
|
|
14
|
+
DialogHeader,
|
|
15
|
+
DialogTitle,
|
|
16
|
+
} from '../../ui/overlay/Dialog'
|
|
17
|
+
import { Button } from '../../ui/buttons/Button'
|
|
18
|
+
|
|
19
|
+
interface ImageCropModalProps {
|
|
20
|
+
open: boolean
|
|
21
|
+
onOpenChange: (open: boolean) => void
|
|
22
|
+
imageSrc: string
|
|
23
|
+
aspectRatio: number
|
|
24
|
+
onConfirm: (image: HTMLImageElement, crop: Crop) => void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function ImageCropModal({
|
|
28
|
+
open,
|
|
29
|
+
onOpenChange,
|
|
30
|
+
imageSrc,
|
|
31
|
+
aspectRatio,
|
|
32
|
+
onConfirm,
|
|
33
|
+
}: ImageCropModalProps) {
|
|
34
|
+
const imgRef = useRef<HTMLImageElement>(null)
|
|
35
|
+
const [crop, setCrop] = useState<Crop>()
|
|
36
|
+
|
|
37
|
+
const onImageLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {
|
|
38
|
+
const { naturalWidth, naturalHeight } = e.currentTarget
|
|
39
|
+
|
|
40
|
+
const initialCrop = centerCrop(
|
|
41
|
+
makeAspectCrop(
|
|
42
|
+
{ unit: '%', width: 90 },
|
|
43
|
+
aspectRatio,
|
|
44
|
+
naturalWidth,
|
|
45
|
+
naturalHeight
|
|
46
|
+
),
|
|
47
|
+
naturalWidth,
|
|
48
|
+
naturalHeight
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
setCrop(initialCrop)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const handleConfirm = () => {
|
|
55
|
+
if (imgRef.current && crop) {
|
|
56
|
+
onConfirm(imgRef.current, crop)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
62
|
+
<DialogContent className="max-w-2xl">
|
|
63
|
+
<DialogHeader>
|
|
64
|
+
<DialogTitle>Ajustar imagem</DialogTitle>
|
|
65
|
+
</DialogHeader>
|
|
66
|
+
|
|
67
|
+
<div className="flex justify-center py-4">
|
|
68
|
+
<ReactCrop
|
|
69
|
+
crop={crop}
|
|
70
|
+
onChange={(c) => setCrop(c)}
|
|
71
|
+
aspect={aspectRatio}
|
|
72
|
+
className="max-h-[60vh]"
|
|
73
|
+
>
|
|
74
|
+
<img
|
|
75
|
+
ref={imgRef}
|
|
76
|
+
src={imageSrc}
|
|
77
|
+
alt="Crop"
|
|
78
|
+
onLoad={onImageLoad}
|
|
79
|
+
className="max-h-[60vh] object-contain"
|
|
80
|
+
/>
|
|
81
|
+
</ReactCrop>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<p className="paragraph-small-regular text-gray-500 text-center">
|
|
85
|
+
Arraste para ajustar a área de corte
|
|
86
|
+
</p>
|
|
87
|
+
|
|
88
|
+
<DialogFooter>
|
|
89
|
+
<Button variant="secondary" onClick={() => onOpenChange(false)}>
|
|
90
|
+
Cancelar
|
|
91
|
+
</Button>
|
|
92
|
+
<Button onClick={handleConfirm}>Confirmar</Button>
|
|
93
|
+
</DialogFooter>
|
|
94
|
+
</DialogContent>
|
|
95
|
+
</Dialog>
|
|
96
|
+
)
|
|
97
|
+
}
|