Web2k / client /src /App.jsx
Shinhati2023's picture
Update client/src/App.jsx
bf381bb verified
import React, { useState, useEffect, useRef } from 'react';
import { createClient } from '@supabase/supabase-js';
// --- CONFIG ---
const SUPABASE_URL = "https://whpciwshxecjvalksicw.supabase.co";
const SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6IndocGNpd3NoeGVjanZhbGtzaWN3Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzAxNDQyNjYsImV4cCI6MjA4NTcyMDI2Nn0.TwWAlQCUstvGykPhaDhPAVpTyg2MV7eltG7JFvjZ-zU"; // <--- PASTE KEY HERE
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
const AI_MODELS = [
{ id: 'openai/dall-e-3', name: 'TEK 3 (Best)' },
{ id: 'google/imagen-4.0-preview', name: 'TEK 4 HD (Real)' },
{ id: 'gemini-2.5-flash-image', name: 'TEK NANO HD (Fast)' },
];
const ASPECT_RATIOS = [
{ id: '1:1', name: 'Square' },
{ id: '16:9', name: 'Wide' },
{ id: '9:16', name: 'Portrait' },
];
function App() {
const [session, setSession] = useState(null);
const [prompt, setPrompt] = useState("");
const [selectedModel, setSelectedModel] = useState(AI_MODELS[0].id);
const [selectedRatio, setSelectedRatio] = useState(ASPECT_RATIOS[0].id);
const [generating, setGenerating] = useState(false);
const [imageSrc, setImageSrc] = useState(null);
const [inputImage, setInputImage] = useState(null);
const [previewUrl, setPreviewUrl] = useState(null);
const fileInputRef = useRef(null);
const [deferredPrompt, setDeferredPrompt] = useState(null);
const [showInstallBanner, setShowInstallBanner] = useState(false);
const [isIOS, setIsIOS] = useState(false);
useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => setSession(session));
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => setSession(session));
const isIosDevice = /ipad|iphone|ipod/.test(navigator.userAgent.toLowerCase()) && !window.MSStream;
const isInStandaloneMode = window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true;
if (isIosDevice && !isInStandaloneMode) {
setIsIOS(true);
setTimeout(() => setShowInstallBanner(true), 3000);
}
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
setDeferredPrompt(e);
if (!isInStandaloneMode) setShowInstallBanner(true);
});
return () => subscription.unsubscribe();
}, []);
const handleInstallClick = async () => {
if (deferredPrompt) {
deferredPrompt.prompt();
setDeferredPrompt(null);
setShowInstallBanner(false);
}
};
const handleLogin = async () => {
const { error } = await supabase.auth.signInAnonymously();
if (error) alert("Login failed: " + error.message);
};
const handleFileChange = (e) => {
const file = e.target.files[0];
if (file) {
setInputImage(file);
setPreviewUrl(URL.createObjectURL(file));
}
};
const handleGenerate = async () => {
if (!prompt) return;
setGenerating(true);
setImageSrc(null);
try {
if (!window.puter.auth.isSignedIn()) await window.puter.auth.signIn();
const ratioText = selectedRatio === '1:1' ? '' : ` --ar ${selectedRatio}`;
const finalPrompt = prompt + ratioText;
let imgElement;
if (inputImage && !selectedModel.includes('dall-e')) {
imgElement = await window.puter.ai.txt2img(finalPrompt, { model: selectedModel, image: inputImage });
} else {
if (inputImage) alert("Note: TEK 3 ignores input images for now.");
imgElement = await window.puter.ai.txt2img(finalPrompt, { model: selectedModel });
}
setImageSrc(imgElement.src);
} catch (e) {
alert("Error: " + (e.message || "Failed"));
} finally {
setGenerating(false);
}
};
// --- NEW START SCREEN (TEK DREAMS) ---
if (!session) {
return (
<div style={{
height: '100vh', display: 'flex', flexDirection: 'column',
alignItems: 'center', justifyContent: 'center'
}}>
{/* The Outer Glass Box */}
<div className="dream-stack-outer">
{/* The Inner Glass Box */}
<div className="dream-stack-middle">
<div className="dream-title">TEK DREAMS</div>
<button onClick={handleLogin} className="dream-btn">
Enter
</button>
</div>
</div>
</div>
);
}
// --- MAIN APP ---
return (
<div style={{
minHeight: '100vh', display: 'flex', flexDirection: 'column',
paddingBottom: '150px', boxSizing: 'border-box'
}}>
{/* HEADER */}
<div style={{
padding: '15px 20px', display: 'flex', justifyContent: 'space-between', alignItems: 'center',
position: 'sticky', top: 0, zIndex: 10, gap: '20px'
}}>
<div className="logo-expand-container">
<span className="logo-main">TEK</span>
<span className="logo-hidden">BUILD</span>
</div>
<div style={{display: 'flex', gap: '8px', flexShrink: 0}}>
<select className="glass-pill" value={selectedRatio} onChange={(e) => setSelectedRatio(e.target.value)}>
{ASPECT_RATIOS.map(r => <option key={r.id} value={r.id}>{r.name}</option>)}
</select>
<select className="glass-pill" value={selectedModel} onChange={(e) => setSelectedModel(e.target.value)} style={{maxWidth:'130px'}}>
{AI_MODELS.map(m => <option key={m.id} value={m.id}>{m.name}</option>)}
</select>
</div>
</div>
{/* VIEWPORT */}
<div style={{
flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center',
padding: '20px', flexDirection: 'column', minHeight: 0
}}>
{generating ? (
<div className="glass-panel" style={{padding: '20px', borderRadius:'50%'}}>
<div style={{fontSize:'40px', animation:'spin 2s infinite'}}></div>
</div>
) : imageSrc ? (
<div className="glass-panel" style={{padding: '10px', maxWidth: '100%', maxHeight: '100%', display:'flex'}}>
<img src={imageSrc} alt="Generated" style={{maxWidth: '100%', maxHeight: '65vh', borderRadius: '16px', objectFit:'contain'}} />
</div>
) : (
<div style={{opacity: 0.1, transform:'scale(0.8)'}}></div>
)}
</div>
{/* PWA BANNER */}
{showInstallBanner && (
<div className="pwa-banner">
{isIOS ? (
<div style={{display:'flex', alignItems:'center', gap:'10px'}}>
<span style={{fontSize:'24px'}}>📲</span>
<div style={{textAlign:'left', fontSize:'14px'}}>Tap Share then "Add to Home Screen"</div>
<button onClick={() => setShowInstallBanner(false)} style={{background:'none', border:'none', color:'#aaa', fontSize:'20px'}}>×</button>
</div>
) : (
<div style={{display:'flex', alignItems:'center', justifyContent:'space-between', width:'100%'}}>
<span style={{fontSize:'14px', fontWeight:'500'}}>Install App?</span>
<div style={{display:'flex', gap:'10px'}}>
<button onClick={() => setShowInstallBanner(false)} className="glass-pill" style={{padding:'6px 12px', background:'transparent'}}>Later</button>
<button onClick={handleInstallClick} className="run-btn" style={{borderRadius:'20px', padding:'6px 15px', height:'auto', width:'auto', fontSize:'14px'}}>Install</button>
</div>
</div>
)}
</div>
)}
{/* INPUT DOCK */}
<div style={{
position: 'fixed', bottom: 0, left: 0, right: 0,
padding: '10px 20px 30px 20px', zIndex: 20,
paddingBottom: 'calc(30px + env(safe-area-inset-bottom))'
}}>
<div style={{
textAlign: 'center', fontSize: '12px', color: 'rgba(255,255,255,0.6)',
marginBottom: '10px', letterSpacing: '1px', textTransform: 'uppercase'
}}>
{generating ? "Dreaming..." : (imageSrc ? "Done" : "Ready to dream")}
</div>
{previewUrl && (
<div style={{position: 'absolute', bottom: '110px', left: '20px'}}>
<div className="holo-box" style={{padding:'5px', marginBottom:0, display:'inline-block'}}>
<img src={previewUrl} style={{height: '60px', borderRadius: '4px'}} />
<button onClick={() => {setPreviewUrl(null); setInputImage(null);}} style={{position: 'absolute', top: -8, right: -8, borderRadius:'50%', width:'20px', height:'20px', border:'none', background:'red', color:'white', fontSize:'12px', display:'flex', alignItems:'center', justifyContent:'center'}}>×</button>
</div>
</div>
)}
<div className="glass-panel" style={{
padding: '10px', display: 'flex', alignItems: 'flex-end', gap: '10px',
borderRadius: '32px', background: 'rgba(0,0,0,0.4)'
}}>
<div onClick={() => fileInputRef.current.click()} style={{
height: '48px', width: '48px', borderRadius: '50%', background: 'rgba(255,255,255,0.1)',
display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', flexShrink: 0
}}>
<span style={{fontSize: '20px'}}>📷</span>
</div>
<input type="file" ref={fileInputRef} onChange={handleFileChange} style={{display: 'none'}} accept="image/*" />
<textarea
value={prompt} onChange={e => setPrompt(e.target.value)}
placeholder="Describe your image..."
style={{
flex: 1, padding: '14px', fontSize: '16px', height: '50px',
borderRadius: '24px', resize: 'none', border:'none', background:'transparent'
}}
/>
<button className="run-btn" onClick={handleGenerate} disabled={generating} style={{height: '48px', width: '48px', flexShrink: 0}}>
{generating ? '...' : '➜'}
</button>
</div>
</div>
</div>
);
}
export default App;