Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
248 changes: 127 additions & 121 deletions app/routes/home.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { Route } from "./+types/home";
import Navbar from "../../components/Navbar";
import {ArrowRight, ArrowUpRight, Clock, Layers} from "lucide-react";
import Button from "../../components/ui/Button";
import { ArrowRight, ArrowUpRight, Clock, Layers } from "lucide-react";
import Upload from "../../components/Upload";
import {useNavigate} from "react-router";
import {useEffect, useRef, useState} from "react";
import {createProject, getProjects} from "../../lib/puter.action";
import { useNavigate } from "react-router";
import { useEffect, useRef, useState } from "react";
import { createProject, getProjects } from "../../lib/puter.action";
import { Helmet } from "react-helmet";

export function meta({}: Route.MetaArgs) {
return [
Expand All @@ -15,115 +15,118 @@ export function meta({}: Route.MetaArgs) {
}

export default function Home() {
const navigate = useNavigate();
const [projects, setProjects] = useState<DesignItem[]>([]);
const isCreatingProjectRef = useRef(false);

const handleUploadComplete = async (base64Image: string) => {
try {

if(isCreatingProjectRef.current) return false;
isCreatingProjectRef.current = true;
const newId = Date.now().toString();
const name = `Residence ${newId}`;

const newItem = {
id: newId, name, sourceImage: base64Image,
renderedImage: undefined,
timestamp: Date.now()
}

const saved = await createProject({ item: newItem, visibility: 'private' });

if(!saved) {
console.error("Failed to create project");
return false;
}

setProjects((prev) => [saved, ...prev]);

navigate(`/visualizer/${newId}`, {
state: {
initialImage: saved.sourceImage,
initialRendered: saved.renderedImage || null,
name
}
});

return true;
} finally {
isCreatingProjectRef.current = false;
}
}
const handleDelete = async (id: string) => {
try {
// TODO: ถ้ามี backend
// await deleteProject(id);

setProjects((prev) => prev.filter((p) => p.id !== id));
} catch (err) {
console.error("Delete failed", err);
const navigate = useNavigate();
const [projects, setProjects] = useState<DesignItem[]>([]);
const isCreatingProjectRef = useRef(false);

const handleUploadComplete = async (base64Image: string) => {
try {
if (isCreatingProjectRef.current) return false;
isCreatingProjectRef.current = true;
const newId = Date.now().toString();
const name = `Residence ${newId}`;

const newItem = {
id: newId,
name,
sourceImage: base64Image,
renderedImage: undefined,
timestamp: Date.now(),
};

const saved = await createProject({
item: newItem,
visibility: "private",
});

if (!saved) {
console.error("Failed to create project");
return false;
}
};

useEffect(() => {
const fetchProjects = async () => {
const items = await getProjects();

setProjects(items)
}
setProjects((prev) => [saved, ...prev]);

fetchProjects();
}, []);

return (
<div className="home">
<Navbar />
navigate(`/visualizer/${newId}`, {
state: {
initialImage: saved.sourceImage,
initialRendered: saved.renderedImage || null,
name,
},
});

<section className="hero">
<div className="announce">
<div className="dot">
<div className="pulse"></div>
</div>
return true;
} finally {
isCreatingProjectRef.current = false;
}
};

<p>Introducing Roomify 2.0</p>
</div>
useEffect(() => {
const fetchProjects = async () => {
const items = await getProjects();

<h1>Build beautiful spaces at the speed of thought with Roomify</h1>
setProjects(items);
};

<p className="subtitle">
Roomify is an AI-first design environment that helps you visualize, render, and ship architectural projects faster than ever.
</p>
fetchProjects();
}, []);

<div className="actions">
<a href="#upload" className="cta">
Start Building <ArrowRight className="icon" />
</a>
useEffect(() => {
document.title = "3DRoom";

}, []);

<Button variant="outline" size="lg" className="demo">
Watch Demo
</Button>
return (
<>
<Helmet>
{/* <title>3DRoom</title> */}
<link rel="icon" href="/home.png" />
</Helmet>

<div className="home">

<Navbar />

<section className="hero">
<div className="announce">
<div className="dot">
<div className="pulse"></div>
</div>

<p>Introducing 3DRoom 2.0</p>
</div>

<h1>Build 2D floor plan to 3D floor plan</h1>

<p className="subtitle">
3DRoom is an AI-first design environment that helps you visualize,
render, and ship architectural projects faster than ever.
</p>

<div className="actions">
<a href="#upload" className="cta">
Start Building <ArrowRight className="icon" />
</a>
</div>

<div id="upload" className="upload-shell">
<div className="grid-overlay" />

<div className="upload-card">
<div className="upload-head">
<div className="upload-icon">
<Layers className="icon" />
</div>

<div id="upload" className="upload-shell">
<div className="grid-overlay" />
<h3>Upload your floor plan</h3>
<p>Supports JPG, PNG, formats up to 10MB</p>
</div>

<div className="upload-card">
<div className="upload-head">
<div className="upload-icon">
<Layers className="icon" />
</div>

<h3>Upload your floor plan</h3>
<p>Supports JPG, PNG, formats up to 10MB</p>
</div>

<Upload onComplete={handleUploadComplete} />
</div>
</div>
</section>
<Upload onComplete={handleUploadComplete} />
</div>
</div>
</section>

<section className="projects">
{/* <section className="projects">
<div className="section-inner">
<div className="section-head">
<div className="copy">
Expand All @@ -134,34 +137,36 @@ export default function Home() {

<div className="projects-grid">
{projects.map(({id, name, renderedImage, sourceImage, timestamp}) => (
<div key={id} className="project-card group" onClick={() => navigate(`/visualizer/${id}`)}>
<div
key={id}
className="project-card group"
onClick={() => navigate(`/visualizer/${id}`)}
>
<div className="preview">
<img src={renderedImage || sourceImage} alt="Project"
/>
<img src={renderedImage || sourceImage} alt="Project"/>
<button
className="absolute top-2 right-2 bg-red-500 text-white px-2 py-1 text-xs rounded opacity-0 group-hover:opacity-100 transition"
onClick={(e) => {
e.stopPropagation(); // ❗ กันไม่ให้ onClick ของ card ทำงาน
handleDelete(id);
}}
>
Delete
</button>


<div className="badge">
<span>Community</span>
</div>
{/* 🔴 ปุ่มลบ */}
<button
className="absolute top-2 right-2 bg-red-500 text-white px-2 py-1 text-xs rounded opacity-0 group-hover:opacity-100 transition"
onClick={(e) => {
e.stopPropagation(); // ❗ สำคัญ
handleDelete(id);
}}
>
Delete
</button>


</div>

<div className="card-body">
<div>
{/* <h3>{name}</h3> */}

<div className="meta">
<Clock size={12} />
<span>{new Date(timestamp).toLocaleDateString()}</span>

</div>
</div>
<div className="arrow">
Expand All @@ -172,7 +177,8 @@ export default function Home() {
))}
</div>
</div>
</section>
</div>
)
}
</section> */}
</div>
</>
);
}
Loading