@primestyleai/tryon 5.10.100 → 5.10.101
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.
|
@@ -7,8 +7,11 @@ import type { TranslateFn } from "../../i18n";
|
|
|
7
7
|
* Desktop: split — photo upload on the left, Do/Don't/Pro-Tip on the right.
|
|
8
8
|
* Mobile: stacked — title + preview + checklist + START TRY-ON.
|
|
9
9
|
*/
|
|
10
|
-
export declare function PhotoGuideView({ measurementType, onBack, onSubmit, t, }: {
|
|
10
|
+
export declare function PhotoGuideView({ measurementType, apiUrl, apiKey, onBack, onSubmit, t, }: {
|
|
11
11
|
measurementType?: "body" | "face" | "head" | "foot";
|
|
12
|
+
/** Backend URL for the age-check call (passed through to PhotoUploadZone). */
|
|
13
|
+
apiUrl?: string;
|
|
14
|
+
apiKey?: string;
|
|
12
15
|
onBack: () => void;
|
|
13
16
|
onSubmit: (file: File) => void;
|
|
14
17
|
t: TranslateFn;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { TranslateFn } from "../../i18n";
|
|
2
|
+
/**
|
|
3
|
+
* Single source of truth for photo upload UX across the SDK. Handles:
|
|
4
|
+
* • drag / drop (with proper enter+over+leave+drop handlers — drop fails
|
|
5
|
+
* silently in Chromium without preventDefault on every event)
|
|
6
|
+
* • click-to-open file picker
|
|
7
|
+
* • optional Gemini age-check (when apiUrl + apiKey are supplied)
|
|
8
|
+
* • size + type validation
|
|
9
|
+
* • internal preview, processing spinner, rejection card
|
|
10
|
+
*
|
|
11
|
+
* Used by PhotoGuideView, SizeResultView's photo-guide overlay, and any
|
|
12
|
+
* other place a customer is asked for a photo. Replaces the duplicated
|
|
13
|
+
* handlePhotoSelect blocks in BodyProfileView / CreateProfileWizard /
|
|
14
|
+
* AccessorySizeView (incrementally — those still have their own state).
|
|
15
|
+
*/
|
|
16
|
+
export type PhotoUploadZoneVariant = "block" | "inline";
|
|
17
|
+
export declare function PhotoUploadZone({ apiUrl, apiKey, previewUrl, requireAgeConfirm, ageConfirmed, disabled, hintLine, emptyTitle, variant, onPhotoAccepted, onClearPhoto, t, }: {
|
|
18
|
+
/** When provided with apiKey, runs Gemini age-check before accepting. */
|
|
19
|
+
apiUrl?: string;
|
|
20
|
+
apiKey?: string;
|
|
21
|
+
/** External preview URL (e.g. parent already has a previewUrl from an
|
|
22
|
+
* earlier flow). Pass null to render the empty drop state. */
|
|
23
|
+
previewUrl?: string | null;
|
|
24
|
+
/** When true, the zone refuses uploads until ageConfirmed is true. */
|
|
25
|
+
requireAgeConfirm?: boolean;
|
|
26
|
+
ageConfirmed?: boolean;
|
|
27
|
+
/** Disables click + drop (e.g. while parent is processing). */
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
/** Optional copy under the empty-state title. */
|
|
30
|
+
hintLine?: string;
|
|
31
|
+
/** Custom empty-state title. Defaults to "Upload your photo". */
|
|
32
|
+
emptyTitle?: string;
|
|
33
|
+
/** "block" (default) — full upload card with title/hint/icon.
|
|
34
|
+
* "inline" — just the drop area sized to its parent (preview-only). */
|
|
35
|
+
variant?: PhotoUploadZoneVariant;
|
|
36
|
+
/** Called with the validated File once it passes age-check + size/type
|
|
37
|
+
* validation. Parent is responsible for compressing or sending it. */
|
|
38
|
+
onPhotoAccepted: (file: File) => void;
|
|
39
|
+
/** Called when the user removes the current photo (via the X on preview). */
|
|
40
|
+
onClearPhoto?: () => void;
|
|
41
|
+
t: TranslateFn;
|
|
42
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -17929,6 +17929,110 @@ const STYLES$1 = `
|
|
|
17929
17929
|
|
|
17930
17930
|
/* Upload hover overlay */
|
|
17931
17931
|
.ps-tryon-upload-hover:hover .ps-tryon-upload-hover-overlay { opacity: 1 !important; }
|
|
17932
|
+
|
|
17933
|
+
/* ─────────── Unified PhotoUploadZone ─────────── */
|
|
17934
|
+
.ps-photo-zone {
|
|
17935
|
+
flex: 1; min-height: 220px;
|
|
17936
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
17937
|
+
border: 2px dashed var(--ps-border-color);
|
|
17938
|
+
border-radius: 0.5vw;
|
|
17939
|
+
background: var(--ps-bg-secondary);
|
|
17940
|
+
cursor: pointer; position: relative; overflow: hidden;
|
|
17941
|
+
transition: border-color 0.18s, background 0.18s, transform 0.18s;
|
|
17942
|
+
}
|
|
17943
|
+
.ps-photo-zone:hover { border-color: var(--ps-accent); background: rgba(33,84,239,0.02); }
|
|
17944
|
+
.ps-photo-zone.ps-photo-zone-drag {
|
|
17945
|
+
border-color: var(--ps-accent); border-style: solid;
|
|
17946
|
+
background: rgba(33,84,239,0.06);
|
|
17947
|
+
transform: scale(1.005);
|
|
17948
|
+
}
|
|
17949
|
+
.ps-photo-zone.ps-photo-zone-has { border: none; cursor: default; padding: 0; }
|
|
17950
|
+
.ps-photo-zone.ps-photo-zone-inline { min-height: 100%; height: 100%; }
|
|
17951
|
+
|
|
17952
|
+
.ps-photo-zone-empty {
|
|
17953
|
+
display: flex; flex-direction: column; align-items: center; gap: 0.4vw;
|
|
17954
|
+
padding: 1vw; text-align: center; pointer-events: none;
|
|
17955
|
+
}
|
|
17956
|
+
.ps-photo-zone-title { font-size: 0.85vw; font-weight: 600; color: var(--ps-text-primary); }
|
|
17957
|
+
.ps-photo-zone-hint { font-size: 0.6vw; color: var(--ps-text-muted); line-height: 1.4; max-width: 24vw; }
|
|
17958
|
+
|
|
17959
|
+
.ps-photo-zone-img {
|
|
17960
|
+
width: 100%; height: 100%; object-fit: contain;
|
|
17961
|
+
display: block; cursor: pointer;
|
|
17962
|
+
}
|
|
17963
|
+
.ps-photo-zone-hover-overlay {
|
|
17964
|
+
position: absolute; inset: 0;
|
|
17965
|
+
display: flex; align-items: center; justify-content: center;
|
|
17966
|
+
background: rgba(0,0,0,0.4); opacity: 0; transition: opacity 0.2s;
|
|
17967
|
+
color: #fff; font-size: 0.8vw; font-weight: 600;
|
|
17968
|
+
border-radius: 0.5vw; cursor: pointer;
|
|
17969
|
+
}
|
|
17970
|
+
.ps-photo-zone:hover .ps-photo-zone-hover-overlay { opacity: 1; }
|
|
17971
|
+
.ps-photo-zone-remove {
|
|
17972
|
+
position: absolute; top: 0.5vw; right: 0.5vw;
|
|
17973
|
+
background: rgba(0,0,0,0.55); color: #fff; border: none; border-radius: 50%;
|
|
17974
|
+
width: 24px; height: 24px; min-width: 24px;
|
|
17975
|
+
display: flex; align-items: center; justify-content: center;
|
|
17976
|
+
cursor: pointer; transition: background 0.15s;
|
|
17977
|
+
z-index: 2;
|
|
17978
|
+
}
|
|
17979
|
+
.ps-photo-zone-remove:hover { background: rgba(239,68,68,0.85); }
|
|
17980
|
+
|
|
17981
|
+
.ps-photo-zone-processing {
|
|
17982
|
+
display: flex; flex-direction: column; align-items: center; gap: 0.5vw;
|
|
17983
|
+
padding: 1vw;
|
|
17984
|
+
}
|
|
17985
|
+
.ps-photo-zone-spinner {
|
|
17986
|
+
width: 28px; height: 28px;
|
|
17987
|
+
border: 3px solid rgba(33,84,239,0.18);
|
|
17988
|
+
border-top-color: var(--ps-accent);
|
|
17989
|
+
border-radius: 50%;
|
|
17990
|
+
animation: ps-spin 0.7s linear infinite;
|
|
17991
|
+
}
|
|
17992
|
+
.ps-photo-zone-status { font-size: 0.7vw; color: var(--ps-text-secondary); }
|
|
17993
|
+
|
|
17994
|
+
.ps-photo-zone-rejection {
|
|
17995
|
+
display: flex; flex-direction: column; align-items: center; gap: 0.5vw;
|
|
17996
|
+
padding: 1vw 1.2vw; max-width: 22vw; text-align: center;
|
|
17997
|
+
cursor: default;
|
|
17998
|
+
}
|
|
17999
|
+
.ps-photo-zone-rejection-icon {
|
|
18000
|
+
width: 36px; height: 36px; border-radius: 50%;
|
|
18001
|
+
background: rgba(239,68,68,0.12); color: #dc2626;
|
|
18002
|
+
display: flex; align-items: center; justify-content: center;
|
|
18003
|
+
font-size: 18px; font-weight: 700;
|
|
18004
|
+
}
|
|
18005
|
+
.ps-photo-zone-rejection-title { font-size: 0.85vw; font-weight: 700; color: var(--ps-text-primary); }
|
|
18006
|
+
.ps-photo-zone-rejection-msg { font-size: 0.65vw; color: var(--ps-text-secondary); line-height: 1.5; }
|
|
18007
|
+
.ps-photo-zone-rejection-cta {
|
|
18008
|
+
margin-top: 0.3vw; padding: 0.55vw 1vw;
|
|
18009
|
+
background: var(--ps-accent); color: #fff; border: none;
|
|
18010
|
+
border-radius: 0.4vw; font-family: inherit;
|
|
18011
|
+
font-size: 0.7vw; font-weight: 600; cursor: pointer;
|
|
18012
|
+
transition: opacity 0.15s;
|
|
18013
|
+
}
|
|
18014
|
+
.ps-photo-zone-rejection-cta:hover { opacity: 0.9; }
|
|
18015
|
+
|
|
18016
|
+
.ps-photo-zone-error {
|
|
18017
|
+
position: absolute; bottom: 0.6vw; left: 0.6vw; right: 0.6vw;
|
|
18018
|
+
background: rgba(239,68,68,0.08); color: #dc2626;
|
|
18019
|
+
border: 1px solid rgba(239,68,68,0.2); border-radius: 0.4vw;
|
|
18020
|
+
padding: 0.4vw 0.6vw; font-size: 0.6vw; line-height: 1.4;
|
|
18021
|
+
text-align: center; pointer-events: none;
|
|
18022
|
+
}
|
|
18023
|
+
|
|
18024
|
+
@media (max-width: 700px) {
|
|
18025
|
+
.ps-photo-zone { min-height: 200px; border-radius: 12px; }
|
|
18026
|
+
.ps-photo-zone-title { font-size: 14px; }
|
|
18027
|
+
.ps-photo-zone-hint { font-size: 11px; max-width: 90%; }
|
|
18028
|
+
.ps-photo-zone-hover-overlay { font-size: 13px; }
|
|
18029
|
+
.ps-photo-zone-status { font-size: 12px; }
|
|
18030
|
+
.ps-photo-zone-rejection { max-width: 90%; gap: 8px; padding: 14px; }
|
|
18031
|
+
.ps-photo-zone-rejection-title { font-size: 14px; }
|
|
18032
|
+
.ps-photo-zone-rejection-msg { font-size: 12px; }
|
|
18033
|
+
.ps-photo-zone-rejection-cta { font-size: 13px; padding: 10px 16px; border-radius: 8px; }
|
|
18034
|
+
.ps-photo-zone-error { font-size: 11px; padding: 8px 10px; border-radius: 8px; }
|
|
18035
|
+
}
|
|
17932
18036
|
`;
|
|
17933
18037
|
function CameraIcon$1({ size = 18 }) {
|
|
17934
18038
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
@@ -21928,8 +22032,191 @@ function UploadView({
|
|
|
21928
22032
|
}
|
|
21929
22033
|
) });
|
|
21930
22034
|
}
|
|
22035
|
+
function PhotoUploadZone({
|
|
22036
|
+
apiUrl,
|
|
22037
|
+
apiKey,
|
|
22038
|
+
previewUrl,
|
|
22039
|
+
requireAgeConfirm = false,
|
|
22040
|
+
ageConfirmed,
|
|
22041
|
+
disabled = false,
|
|
22042
|
+
hintLine,
|
|
22043
|
+
emptyTitle,
|
|
22044
|
+
variant = "block",
|
|
22045
|
+
onPhotoAccepted,
|
|
22046
|
+
onClearPhoto,
|
|
22047
|
+
t: t2
|
|
22048
|
+
}) {
|
|
22049
|
+
const inputRef = reactExports.useRef(null);
|
|
22050
|
+
const dragDepthRef = reactExports.useRef(0);
|
|
22051
|
+
const [dragOver, setDragOver] = reactExports.useState(false);
|
|
22052
|
+
const [processing, setProcessing] = reactExports.useState(false);
|
|
22053
|
+
const [statusText, setStatusText] = reactExports.useState("");
|
|
22054
|
+
const [rejection, setRejection] = reactExports.useState(null);
|
|
22055
|
+
const [localError, setLocalError] = reactExports.useState(null);
|
|
22056
|
+
reactExports.useEffect(() => {
|
|
22057
|
+
if (!previewUrl) {
|
|
22058
|
+
setRejection(null);
|
|
22059
|
+
setLocalError(null);
|
|
22060
|
+
}
|
|
22061
|
+
}, [previewUrl]);
|
|
22062
|
+
const acceptFile = async (file) => {
|
|
22063
|
+
if (disabled || processing) return;
|
|
22064
|
+
setLocalError(null);
|
|
22065
|
+
setRejection(null);
|
|
22066
|
+
if (requireAgeConfirm && ageConfirmed !== true) {
|
|
22067
|
+
setLocalError(t2("Please confirm that the person in the photo is 18 or older before uploading."));
|
|
22068
|
+
return;
|
|
22069
|
+
}
|
|
22070
|
+
if (!file.type.startsWith("image/")) {
|
|
22071
|
+
setLocalError(t2("Please upload an image file"));
|
|
22072
|
+
return;
|
|
22073
|
+
}
|
|
22074
|
+
if (file.size > 10 * 1024 * 1024) {
|
|
22075
|
+
setLocalError(t2("Image must be under 10MB"));
|
|
22076
|
+
return;
|
|
22077
|
+
}
|
|
22078
|
+
if (apiUrl && apiKey) {
|
|
22079
|
+
setProcessing(true);
|
|
22080
|
+
setStatusText(t2("Analyzing photo…"));
|
|
22081
|
+
try {
|
|
22082
|
+
const ageResult = await checkAgeBeforeUpload(file, apiUrl, apiKey);
|
|
22083
|
+
if (!ageResult.isAdult) {
|
|
22084
|
+
const reason = ageResult.reasoning?.trim() || t2("This photo appears to be of a minor. Please upload a photo of someone 18 or older.");
|
|
22085
|
+
setRejection(reason);
|
|
22086
|
+
setProcessing(false);
|
|
22087
|
+
setStatusText("");
|
|
22088
|
+
return;
|
|
22089
|
+
}
|
|
22090
|
+
try {
|
|
22091
|
+
await compressImage(file, { maxDimension: 1024, quality: 0.85 });
|
|
22092
|
+
} catch {
|
|
22093
|
+
}
|
|
22094
|
+
} catch {
|
|
22095
|
+
} finally {
|
|
22096
|
+
setProcessing(false);
|
|
22097
|
+
setStatusText("");
|
|
22098
|
+
}
|
|
22099
|
+
}
|
|
22100
|
+
onPhotoAccepted(file);
|
|
22101
|
+
};
|
|
22102
|
+
const onClick = () => {
|
|
22103
|
+
if (disabled || processing) return;
|
|
22104
|
+
inputRef.current?.click();
|
|
22105
|
+
};
|
|
22106
|
+
const onDragEnter = (e) => {
|
|
22107
|
+
e.preventDefault();
|
|
22108
|
+
e.stopPropagation();
|
|
22109
|
+
if (disabled || processing) return;
|
|
22110
|
+
dragDepthRef.current += 1;
|
|
22111
|
+
setDragOver(true);
|
|
22112
|
+
};
|
|
22113
|
+
const onDragOver = (e) => {
|
|
22114
|
+
e.preventDefault();
|
|
22115
|
+
e.stopPropagation();
|
|
22116
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
|
|
22117
|
+
};
|
|
22118
|
+
const onDragLeave = (e) => {
|
|
22119
|
+
e.preventDefault();
|
|
22120
|
+
e.stopPropagation();
|
|
22121
|
+
dragDepthRef.current = Math.max(0, dragDepthRef.current - 1);
|
|
22122
|
+
if (dragDepthRef.current === 0) setDragOver(false);
|
|
22123
|
+
};
|
|
22124
|
+
const onDrop = (e) => {
|
|
22125
|
+
e.preventDefault();
|
|
22126
|
+
e.stopPropagation();
|
|
22127
|
+
dragDepthRef.current = 0;
|
|
22128
|
+
setDragOver(false);
|
|
22129
|
+
if (disabled || processing) return;
|
|
22130
|
+
const file = e.dataTransfer?.files?.[0];
|
|
22131
|
+
if (file) void acceptFile(file);
|
|
22132
|
+
};
|
|
22133
|
+
const onChange = (e) => {
|
|
22134
|
+
const file = e.target.files?.[0];
|
|
22135
|
+
if (file) void acceptFile(file);
|
|
22136
|
+
if (inputRef.current) inputRef.current.value = "";
|
|
22137
|
+
};
|
|
22138
|
+
const removePhoto = () => {
|
|
22139
|
+
setRejection(null);
|
|
22140
|
+
setLocalError(null);
|
|
22141
|
+
onClearPhoto?.();
|
|
22142
|
+
};
|
|
22143
|
+
const isInline = variant === "inline";
|
|
22144
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
22145
|
+
"div",
|
|
22146
|
+
{
|
|
22147
|
+
className: `ps-photo-zone${dragOver ? " ps-photo-zone-drag" : ""}${isInline ? " ps-photo-zone-inline" : ""}${previewUrl ? " ps-photo-zone-has" : ""}`,
|
|
22148
|
+
onClick: previewUrl ? void 0 : onClick,
|
|
22149
|
+
onDragEnter,
|
|
22150
|
+
onDragOver,
|
|
22151
|
+
onDragLeave,
|
|
22152
|
+
onDrop,
|
|
22153
|
+
children: [
|
|
22154
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
22155
|
+
"input",
|
|
22156
|
+
{
|
|
22157
|
+
ref: inputRef,
|
|
22158
|
+
type: "file",
|
|
22159
|
+
accept: "image/jpeg,image/png,image/webp",
|
|
22160
|
+
style: { display: "none" },
|
|
22161
|
+
onChange
|
|
22162
|
+
}
|
|
22163
|
+
),
|
|
22164
|
+
previewUrl && !rejection && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
22165
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: previewUrl, alt: t2("Your photo"), className: "ps-photo-zone-img" }),
|
|
22166
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-photo-zone-hover-overlay", onClick, title: t2("Click to change photo"), children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2("Click to change photo") }) }),
|
|
22167
|
+
onClearPhoto && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
22168
|
+
"button",
|
|
22169
|
+
{
|
|
22170
|
+
type: "button",
|
|
22171
|
+
className: "ps-photo-zone-remove",
|
|
22172
|
+
onClick: (e) => {
|
|
22173
|
+
e.stopPropagation();
|
|
22174
|
+
removePhoto();
|
|
22175
|
+
},
|
|
22176
|
+
"aria-label": t2("Remove photo"),
|
|
22177
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", width: "14", height: "14", children: [
|
|
22178
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
22179
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
22180
|
+
] })
|
|
22181
|
+
}
|
|
22182
|
+
)
|
|
22183
|
+
] }),
|
|
22184
|
+
!previewUrl && !rejection && !processing && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-photo-zone-empty", children: [
|
|
22185
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(UploadIcon, { size: 32 }),
|
|
22186
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-photo-zone-title", children: emptyTitle || t2("Upload your photo") }),
|
|
22187
|
+
hintLine && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-photo-zone-hint", children: hintLine }),
|
|
22188
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-photo-zone-hint", children: t2("JPEG, PNG up to 10MB · click or drop") })
|
|
22189
|
+
] }),
|
|
22190
|
+
processing && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-photo-zone-processing", children: [
|
|
22191
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-photo-zone-spinner" }),
|
|
22192
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-photo-zone-status", children: statusText })
|
|
22193
|
+
] }),
|
|
22194
|
+
rejection && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-photo-zone-rejection", onClick: (e) => e.stopPropagation(), children: [
|
|
22195
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-photo-zone-rejection-icon", "aria-hidden": "true", children: "!" }),
|
|
22196
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-photo-zone-rejection-title", children: t2("Different photo needed") }),
|
|
22197
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-photo-zone-rejection-msg", children: rejection }),
|
|
22198
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
22199
|
+
"button",
|
|
22200
|
+
{
|
|
22201
|
+
type: "button",
|
|
22202
|
+
className: "ps-photo-zone-rejection-cta",
|
|
22203
|
+
onClick: () => {
|
|
22204
|
+
setRejection(null);
|
|
22205
|
+
inputRef.current?.click();
|
|
22206
|
+
},
|
|
22207
|
+
children: t2("Choose another photo")
|
|
22208
|
+
}
|
|
22209
|
+
)
|
|
22210
|
+
] }),
|
|
22211
|
+
localError && !rejection && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-photo-zone-error", onClick: (e) => e.stopPropagation(), children: localError })
|
|
22212
|
+
]
|
|
22213
|
+
}
|
|
22214
|
+
);
|
|
22215
|
+
}
|
|
21931
22216
|
function PhotoGuideView({
|
|
21932
22217
|
measurementType = "body",
|
|
22218
|
+
apiUrl,
|
|
22219
|
+
apiKey,
|
|
21933
22220
|
onBack,
|
|
21934
22221
|
onSubmit,
|
|
21935
22222
|
t: t2
|
|
@@ -21937,8 +22224,6 @@ function PhotoGuideView({
|
|
|
21937
22224
|
const isMobile = useIsMobile();
|
|
21938
22225
|
const [guideFile, setGuideFile] = reactExports.useState(null);
|
|
21939
22226
|
const [guidePreviewUrl, setGuidePreviewUrl] = reactExports.useState(null);
|
|
21940
|
-
const [, setGuideDragOver] = reactExports.useState(false);
|
|
21941
|
-
const guideInputRef = reactExports.useRef(null);
|
|
21942
22227
|
reactExports.useEffect(() => {
|
|
21943
22228
|
if (!guideFile) {
|
|
21944
22229
|
setGuidePreviewUrl(null);
|
|
@@ -21958,49 +22243,17 @@ function PhotoGuideView({
|
|
|
21958
22243
|
/* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "ps-pm-title", children: t2("Review your photo") }),
|
|
21959
22244
|
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "ps-pm-subtitle", children: measurementType === "face" ? t2("A clear, front-facing face photo — no glasses on — gives us the most accurate try-on.") : measurementType === "head" ? t2("Face the camera with your head and shoulders in frame, leaving space above your head.") : t2("Ensure your full body is visible for the most accurate virtual try-on.") })
|
|
21960
22245
|
] }),
|
|
21961
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
21962
|
-
|
|
22246
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-pm-preview", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
22247
|
+
PhotoUploadZone,
|
|
21963
22248
|
{
|
|
21964
|
-
|
|
21965
|
-
|
|
21966
|
-
|
|
21967
|
-
|
|
21968
|
-
|
|
21969
|
-
|
|
21970
|
-
|
|
21971
|
-
|
|
21972
|
-
}
|
|
21973
|
-
),
|
|
21974
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-pm-preview", children: guideFile && guidePreviewUrl ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
21975
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: guidePreviewUrl, alt: t2("Your photo"), className: "ps-pm-preview-img" }),
|
|
21976
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
21977
|
-
"button",
|
|
21978
|
-
{
|
|
21979
|
-
type: "button",
|
|
21980
|
-
className: "ps-pm-preview-remove",
|
|
21981
|
-
onClick: () => setGuideFile(null),
|
|
21982
|
-
"aria-label": t2("Remove photo"),
|
|
21983
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", width: "14", height: "14", children: [
|
|
21984
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
21985
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
21986
|
-
] })
|
|
21987
|
-
}
|
|
21988
|
-
)
|
|
21989
|
-
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
21990
|
-
"button",
|
|
21991
|
-
{
|
|
21992
|
-
type: "button",
|
|
21993
|
-
className: "ps-pm-preview-empty",
|
|
21994
|
-
onClick: () => guideInputRef.current?.click(),
|
|
21995
|
-
children: [
|
|
21996
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", width: "32", height: "32", children: [
|
|
21997
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
21998
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("polyline", { points: "17 8 12 3 7 8" }),
|
|
21999
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
|
|
22000
|
-
] }),
|
|
22001
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-pm-preview-empty-title", children: t2("Tap to upload") }),
|
|
22002
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ps-pm-preview-empty-hint", children: t2("JPEG, PNG up to 10MB") })
|
|
22003
|
-
]
|
|
22249
|
+
apiUrl,
|
|
22250
|
+
apiKey,
|
|
22251
|
+
previewUrl: guidePreviewUrl,
|
|
22252
|
+
variant: "inline",
|
|
22253
|
+
onPhotoAccepted: (f2) => setGuideFile(f2),
|
|
22254
|
+
onClearPhoto: () => setGuideFile(null),
|
|
22255
|
+
emptyTitle: t2("Tap to upload"),
|
|
22256
|
+
t: t2
|
|
22004
22257
|
}
|
|
22005
22258
|
) }),
|
|
22006
22259
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-pm-checklist", children: [
|
|
@@ -22039,7 +22292,7 @@ function PhotoGuideView({
|
|
|
22039
22292
|
{
|
|
22040
22293
|
type: "button",
|
|
22041
22294
|
className: "ps-pm-secondary-btn",
|
|
22042
|
-
onClick: () =>
|
|
22295
|
+
onClick: () => setGuideFile(null),
|
|
22043
22296
|
children: t2("RETAKE PHOTO")
|
|
22044
22297
|
}
|
|
22045
22298
|
) : /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
@@ -22056,60 +22309,20 @@ function PhotoGuideView({
|
|
|
22056
22309
|
}
|
|
22057
22310
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", flexDirection: "column", padding: "1.5vw", width: "100%", height: "100%", background: "var(--ps-bg-primary)", borderRadius: "0.8vw", overflow: "hidden" }, children: [
|
|
22058
22311
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", gap: "1.2vw", flex: 1, alignItems: "stretch", minHeight: 0, overflow: "hidden" }, children: [
|
|
22059
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
22060
|
-
|
|
22312
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { flex: 1, display: "flex", minHeight: 0 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
22313
|
+
PhotoUploadZone,
|
|
22061
22314
|
{
|
|
22062
|
-
|
|
22063
|
-
|
|
22064
|
-
|
|
22065
|
-
|
|
22066
|
-
|
|
22067
|
-
|
|
22068
|
-
|
|
22069
|
-
|
|
22070
|
-
|
|
22071
|
-
const f2 = e.dataTransfer.files[0];
|
|
22072
|
-
if (f2) setGuideFile(f2);
|
|
22073
|
-
},
|
|
22074
|
-
style: {
|
|
22075
|
-
flex: 1,
|
|
22076
|
-
display: "flex",
|
|
22077
|
-
flexDirection: "column",
|
|
22078
|
-
alignItems: "center",
|
|
22079
|
-
justifyContent: "center",
|
|
22080
|
-
border: guideFile ? "none" : "2px dashed var(--ps-border-color)",
|
|
22081
|
-
borderRadius: "0.5vw",
|
|
22082
|
-
cursor: "pointer",
|
|
22083
|
-
position: "relative",
|
|
22084
|
-
background: "var(--ps-bg-secondary)",
|
|
22085
|
-
overflow: "hidden"
|
|
22086
|
-
},
|
|
22087
|
-
className: "ps-tryon-upload-hover",
|
|
22088
|
-
children: [
|
|
22089
|
-
guideFile && guidePreviewUrl ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
22090
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: guidePreviewUrl, alt: "preview", style: { width: "100%", height: "100%", objectFit: "contain" } }),
|
|
22091
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-upload-hover-overlay", style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", background: "rgba(0,0,0,0.4)", opacity: 0, transition: "opacity 0.2s", borderRadius: "0.5vw" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#fff", fontSize: "0.8vw", fontWeight: 600 }, children: t2("Click to change photo") }) })
|
|
22092
|
-
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
22093
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(UploadIcon, { size: 32 }),
|
|
22094
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: "0.85vw", fontWeight: 600, color: "var(--ps-text-primary)", marginTop: "0.5vw" }, children: t2("Upload your photo") }),
|
|
22095
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: "0.6vw", color: "var(--ps-text-muted)", marginTop: "0.2vw" }, children: measurementType === "face" ? t2("Click or drag a close-up face photo") : measurementType === "head" ? t2("Click or drag a head-and-shoulders photo") : t2("Click or drag a full-body photo") })
|
|
22096
|
-
] }),
|
|
22097
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
22098
|
-
"input",
|
|
22099
|
-
{
|
|
22100
|
-
ref: guideInputRef,
|
|
22101
|
-
type: "file",
|
|
22102
|
-
accept: "image/jpeg,image/png,image/webp",
|
|
22103
|
-
style: { display: "none" },
|
|
22104
|
-
onChange: (e) => {
|
|
22105
|
-
const f2 = e.target.files?.[0];
|
|
22106
|
-
if (f2) setGuideFile(f2);
|
|
22107
|
-
}
|
|
22108
|
-
}
|
|
22109
|
-
)
|
|
22110
|
-
]
|
|
22315
|
+
apiUrl,
|
|
22316
|
+
apiKey,
|
|
22317
|
+
previewUrl: guidePreviewUrl,
|
|
22318
|
+
variant: "inline",
|
|
22319
|
+
emptyTitle: t2("Upload your photo"),
|
|
22320
|
+
hintLine: measurementType === "face" ? t2("Click or drag a close-up face photo") : measurementType === "head" ? t2("Click or drag a head-and-shoulders photo") : t2("Click or drag a full-body photo"),
|
|
22321
|
+
onPhotoAccepted: (f2) => setGuideFile(f2),
|
|
22322
|
+
onClearPhoto: () => setGuideFile(null),
|
|
22323
|
+
t: t2
|
|
22111
22324
|
}
|
|
22112
|
-
),
|
|
22325
|
+
) }),
|
|
22113
22326
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { flex: 1, display: "flex", flexDirection: "column", justifyContent: "center", gap: "0.6vw" }, children: [
|
|
22114
22327
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { fontSize: "0.85vw", fontWeight: 700, color: "var(--ps-text-primary)", marginBottom: "0.3vw" }, children: t2("How to take the best photo") }),
|
|
22115
22328
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { background: "#ddfbe7", borderRadius: "0.5vw", padding: "0.6vw 0.8vw" }, children: [
|
|
@@ -23183,6 +23396,30 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
|
|
|
23183
23396
|
if (!photoBase64 && ageConfirmed === true) setUploadHoverCpw(true);
|
|
23184
23397
|
},
|
|
23185
23398
|
onMouseLeave: () => setUploadHoverCpw(false),
|
|
23399
|
+
onDragEnter: (e) => {
|
|
23400
|
+
e.preventDefault();
|
|
23401
|
+
e.stopPropagation();
|
|
23402
|
+
},
|
|
23403
|
+
onDragOver: (e) => {
|
|
23404
|
+
e.preventDefault();
|
|
23405
|
+
e.stopPropagation();
|
|
23406
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
|
|
23407
|
+
},
|
|
23408
|
+
onDragLeave: (e) => {
|
|
23409
|
+
e.preventDefault();
|
|
23410
|
+
e.stopPropagation();
|
|
23411
|
+
},
|
|
23412
|
+
onDrop: (e) => {
|
|
23413
|
+
e.preventDefault();
|
|
23414
|
+
e.stopPropagation();
|
|
23415
|
+
if (photoBase64 || ageConfirmed !== true) return;
|
|
23416
|
+
const file = e.dataTransfer?.files?.[0];
|
|
23417
|
+
if (!file || !photoInputRef.current) return;
|
|
23418
|
+
const dt = new DataTransfer();
|
|
23419
|
+
dt.items.add(file);
|
|
23420
|
+
photoInputRef.current.files = dt.files;
|
|
23421
|
+
photoInputRef.current.dispatchEvent(new Event("change", { bubbles: true }));
|
|
23422
|
+
},
|
|
23186
23423
|
style: {
|
|
23187
23424
|
flex: 1,
|
|
23188
23425
|
display: "flex",
|
|
@@ -25666,6 +25903,30 @@ function BodyProfileView({
|
|
|
25666
25903
|
if (!photoPreview && ageConfirmed === true) setUploadHover(true);
|
|
25667
25904
|
},
|
|
25668
25905
|
onMouseLeave: () => setUploadHover(false),
|
|
25906
|
+
onDragEnter: (e) => {
|
|
25907
|
+
e.preventDefault();
|
|
25908
|
+
e.stopPropagation();
|
|
25909
|
+
},
|
|
25910
|
+
onDragOver: (e) => {
|
|
25911
|
+
e.preventDefault();
|
|
25912
|
+
e.stopPropagation();
|
|
25913
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
|
|
25914
|
+
},
|
|
25915
|
+
onDragLeave: (e) => {
|
|
25916
|
+
e.preventDefault();
|
|
25917
|
+
e.stopPropagation();
|
|
25918
|
+
},
|
|
25919
|
+
onDrop: (e) => {
|
|
25920
|
+
e.preventDefault();
|
|
25921
|
+
e.stopPropagation();
|
|
25922
|
+
if (photoPreview || ageConfirmed !== true) return;
|
|
25923
|
+
const file = e.dataTransfer?.files?.[0];
|
|
25924
|
+
if (!file || !fileInputRef.current) return;
|
|
25925
|
+
const dt = new DataTransfer();
|
|
25926
|
+
dt.items.add(file);
|
|
25927
|
+
fileInputRef.current.files = dt.files;
|
|
25928
|
+
fileInputRef.current.dispatchEvent(new Event("change", { bubbles: true }));
|
|
25929
|
+
},
|
|
25669
25930
|
style: {
|
|
25670
25931
|
flex: 1,
|
|
25671
25932
|
display: "flex",
|
|
@@ -25959,7 +26220,7 @@ function BodyProfileView({
|
|
|
25959
26220
|
] }),
|
|
25960
26221
|
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { style: { margin: "0.2vw 0 0", fontSize: "0.65vw", color: "var(--ps-text-muted)" }, children: t2("These calibrate the AI — all required.") })
|
|
25961
26222
|
] }),
|
|
25962
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bp-system-toggle", style: { alignSelf: "center" }, children: [
|
|
26223
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-bp-system-toggle", style: { alignSelf: "center", marginTop: "1.2vw" }, children: [
|
|
25963
26224
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: `ps-bp-system-btn${!isImperialMode ? " ps-bp-system-active" : ""}`, onClick: photoSwitchToMetric, type: "button", children: t2("Metric") }),
|
|
25964
26225
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: `ps-bp-system-btn${isImperialMode ? " ps-bp-system-active" : ""}`, onClick: photoSwitchToImperial, type: "button", children: t2("Imperial") })
|
|
25965
26226
|
] }),
|
|
@@ -29480,6 +29741,8 @@ function PrimeStyleTryonInner({
|
|
|
29480
29741
|
PhotoGuideView,
|
|
29481
29742
|
{
|
|
29482
29743
|
measurementType: detectMeasurementType(productTitle),
|
|
29744
|
+
apiUrl: getApiUrl(apiUrl),
|
|
29745
|
+
apiKey: getApiKey(),
|
|
29483
29746
|
onBack: () => setView("no-chart"),
|
|
29484
29747
|
onSubmit: (file) => {
|
|
29485
29748
|
handleFileSelect(file);
|