feat: setup S2S integration, tailwind fixes, admin routing, nginx fallback

This commit is contained in:
2026-09-22 16:40:42 +05:30
parent 45d13fa7be
commit 05358be6c1
17 changed files with 522 additions and 149 deletions
+45 -49
View File
@@ -61,9 +61,14 @@ const App = () => {
let intervalId;
if (view === 'camera' && videoRef.current && modelsLoaded) {
const detectskin = async () => {
const detections = await skinapi.detectSingleFace(videoRef.current, new skinapi.TinyFaceDetectorOptions());
if (detections) setskinBox(detections.box);
else setskinBox(null);
if (!videoRef.current || videoRef.current.readyState < 2) return;
try {
const detections = await skinapi.detectSingleFace(videoRef.current, new skinapi.TinyFaceDetectorOptions());
if (detections) setskinBox(detections.box);
else setskinBox(null);
} catch (e) {
console.error("Face detection error:", e);
}
};
intervalId = setInterval(detectskin, 300);
}
@@ -332,38 +337,37 @@ const App = () => {
throw new Error(`Backend Analysis Request failed: ${e.message}`);
}
// --- Process ZIP & save analysis ---
// --- Process New S2S JSON Response ---
try {
const maskZipUrl = result.result?.results?.[0]?.data?.[0]?.url || result.result?.url;
if (!maskZipUrl) throw new Error("No mask ZIP URL found in result");
setZipUrl(maskZipUrl);
const zipRes = await fetch(maskZipUrl);
if (!zipRes.ok) throw new Error(`Failed to download ZIP file (status: ${zipRes.status})`);
const zipBlob = await zipRes.blob();
const zip = await JSZip.loadAsync(zipBlob);
const scoreFile = zip.file("skinanalysisResult/score_info.json") || zip.file("score_info.json");
const scores = scoreFile ? JSON.parse(await scoreFile.async("text")) : result.result?.results?.[0]?.scores || {};
if (Object.keys(scores).length === 0) throw new Error("Could not extract scores from analysis result.");
const newMaskMap = {};
for (const key of Object.keys(scores)) {
const maskName = scores[key].output_mask_name;
if (!maskName) continue;
const f = zip.file(`skinanalysisResult/${maskName}`) || zip.file(`masks/${maskName}`) || zip.file(maskName);
if (!f) continue;
const b = await f.async('blob');
newMaskMap[maskName] = await blobToBase64(b);
}
const scores = result.result?.scores;
if (!scores) throw new Error("No scores found in result from backend.");
const originalBase64 = await blobToBase64(await fetch(previewUrl).then(r => r.blob()));
const newMaskMap = {};
for (const key of Object.keys(scores)) {
const maskUrl = scores[key].output_mask_name;
if (!maskUrl) continue;
try {
// Fetch the image from S3 to cache it locally as base64
const imgRes = await fetch(maskUrl);
if (imgRes.ok) {
const b = await imgRes.blob();
newMaskMap[maskUrl] = await blobToBase64(b);
} else {
newMaskMap[maskUrl] = maskUrl; // Fallback to URL
}
} catch(err) {
newMaskMap[maskUrl] = maskUrl;
}
}
await saveToDB('analysisData', {
scoreData: scores,
maskImages: newMaskMap,
originalImage: originalBase64,
zipDownloadUrl: maskZipUrl
zipDownloadUrl: null
});
setAnalysisResults(scores);
@@ -395,9 +399,9 @@ const App = () => {
<div className="container mx-auto px-4 py-10 md:py-16 relative z-10 min-h-screen flex flex-col items-center justify-start gap-4 md:gap-8">
{/* Header */}
<header className="fixed top-0 left-0 right-0 z-[60] py-4 px-6 flex items-center justify-between bg-[limelite-red] shadow-lg">
<header className="fixed top-0 left-0 right-0 z-[60] py-4 px-6 flex items-center justify-between bg-transparent">
<div className="flex items-center gap-4">
<img src={process.env.PUBLIC_URL + '/assets/images/logo.jpg'} alt="Limelite Logo" className="h-12 md:h-16 w-auto " />
<img src={process.env.PUBLIC_URL + '/assets/images/logo.jpg'} alt="Limelite Logo" className="h-10 md:h-14 w-auto" />
</div>
<div className="flex items-center gap-4">
@@ -442,7 +446,7 @@ const App = () => {
{/* Landing View */}
{view === 'landing' && (
<div className="flex flex-col items-center justify-center w-full px-4 gap-8 fade-in mt-20 md:mt-32">
<div className="flex flex-col items-center justify-center w-full px-4 gap-8 fade-in mt-16 md:mt-32">
<div className="text-center space-y-4 mb-2">
<h1 className="text-4xl md:text-5xl font-serif italic text-white leading-tight tracking-[0.02em]">
Step into your style
@@ -453,11 +457,11 @@ const App = () => {
</div>
{/* Action Buttons Container */}
<div className="w-full flex flex-row items-center justify-center gap-4 max-w-md mx-auto">
<div className="w-full flex flex-col sm:flex-row items-center justify-center gap-4 max-w-sm sm:max-w-md mx-auto">
{/* Take Photo Card */}
<button
onClick={() => setView('camera')}
className="flex-1 bg-white text-[#1A1A1A] rounded-xl px-8 py-4 font-bold text-sm tracking-widest uppercase shadow-lg hover:bg-gray-100 transition-all text-center border-2 border-transparent"
className="w-full sm:flex-1 bg-white text-[#1A1A1A] rounded-xl px-6 py-4 font-bold text-sm tracking-widest uppercase shadow-lg hover:bg-gray-100 transition-all text-center border-2 border-transparent"
>
Take Photo
</button>
@@ -465,7 +469,7 @@ const App = () => {
{/* Upload Photo Card */}
<button
onClick={() => document.getElementById('uploadInput').click()}
className="flex-1 bg-[#1A1A1A] text-white border border-gray-700 rounded-xl px-8 py-4 font-bold text-sm tracking-widest uppercase shadow-lg hover:bg-black transition-all text-center"
className="w-full sm:flex-1 bg-[#1A1A1A] text-white border border-gray-700 rounded-xl px-6 py-4 font-bold text-sm tracking-widest uppercase shadow-lg hover:bg-black transition-all text-center"
>
Upload Photo
</button>
@@ -478,15 +482,7 @@ const App = () => {
/>
</div>
{/* Admin Link */}
<div className="mt-8">
<button
onClick={() => navigate('/login')}
className="text-gray-400 hover:text-[limelite-red] transition-colors text-xs uppercase tracking-widest font-bold"
>
Admin Login
</button>
</div>
</div>
)}
{
@@ -504,7 +500,7 @@ const App = () => {
<div className="absolute bottom-12 left-0 right-0 flex justify-center items-center z-20">
<button
onClick={handleCaptureskin}
className="transform active:scale-95 transition-all cursor-pointer bg-[limelite-red] w-36 h-18 py-4 px-10 rounded-full shadow-[0_0_20px_rgba(0,151,195,0.3)] flex items-center justify-center border-none"
className="transform active:scale-95 transition-all cursor-pointer bg-limelite-red w-36 h-18 py-4 px-10 rounded-full shadow-[0_0_20px_rgba(0,151,195,0.3)] flex items-center justify-center border-none"
>
<IoMdAperture className="w-12 h-12 text-white" />
</button>
@@ -528,8 +524,8 @@ const App = () => {
{/* Scanning Animation only when loading */}
{isLoading && (
<div className="absolute inset-0 z-20 pointer-events-none overflow-hidden">
<div className="w-full h-[2px] bg-[limelite-red] shadow-[0_0_20px_rgba(0,151,195,0.8)] absolute top-0 animate-[scan_3s_linear_infinite]"></div>
<div className="w-full h-32 bg-gradient-to-t from-[limelite-red]/20 to-transparent absolute top-0 -translate-y-full animate-[scanTrace_3s_linear_infinite]"></div>
<div className="w-full h-[2px] bg-limelite-red shadow-[0_0_20px_rgba(0,151,195,0.8)] absolute top-0 animate-[scan_3s_linear_infinite]"></div>
<div className="w-full h-32 bg-gradient-to-t from-limelite-red/20 to-transparent absolute top-0 -translate-y-full animate-[scanTrace_3s_linear_infinite]"></div>
</div>
)}
@@ -553,7 +549,7 @@ const App = () => {
<button
onClick={handleSubmitClick}
disabled={isLoading}
className="h-14 w-20 bg-[limelite-red] text-white rounded-none flex items-center justify-center shadow-lg hover:bg-[limelite-maroon] transition-all active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed"
className="h-14 w-20 bg-limelite-red text-white rounded-none flex items-center justify-center shadow-lg hover:bg-limelite-maroon transition-all active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed"
aria-label="Confirm"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@@ -573,7 +569,7 @@ const App = () => {
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/80 backdrop-blur-sm p-6 animate-[fadeIn_0.2s_ease-out]">
<div className="bg-white rounded-none shadow-2xl w-full max-w-sm p-8 text-center">
<div className="w-16 h-16 bg-cyan-100 rounded-full flex items-center justify-center mx-auto mb-6">
<svg className="w-8 h-8 text-[limelite-red]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<svg className="w-8 h-8 text-limelite-red" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
@@ -586,7 +582,7 @@ const App = () => {
</p>
<button
onClick={handleRetakeClick}
className="w-full bg-[limelite-red] text-white font-bold py-4 px-6 rounded-none hover:bg-[limelite-maroon] transition-all active:scale-95 uppercase tracking-widest text-sm"
className="w-full bg-limelite-red text-white font-bold py-4 px-6 rounded-none hover:bg-limelite-maroon transition-all active:scale-95 uppercase tracking-widest text-sm"
>
Retake Photo
</button>