This commit is contained in:
S M Fahim Hossen 2025-09-25 10:53:13 +06:00
parent 9174cb5375
commit dd9691acdc
24 changed files with 511 additions and 270 deletions

View file

@ -1,62 +0,0 @@
"use client";
import { useState } from "react";
import { sendPasswordResetEmail } from "firebase/auth";
import { auth } from "@/lib/firebase";
import Navbar from "@/components/Navbar";
import Link from "next/link";
export default function ForgotPasswordPage() {
const [email, setEmail] = useState("");
const [message, setMessage] = useState("");
const [error, setError] = useState("");
const handleReset = async (e: React.FormEvent) => {
e.preventDefault();
setMessage("");
setError("");
try {
await sendPasswordResetEmail(auth, email);
setMessage("Password reset link sent to your email!");
} catch (err: any) {
setError(err.message);
}
};
return (
<>
<Navbar />
<div className="flex min-h-screen items-center justify-center">
<form
className="p-6 bg-white rounded shadow-md w-full max-w-md"
onSubmit={handleReset}
>
<h2 className="text-2xl font-bold mb-4 text-center">
Forgot Password
</h2>
<input
type="email"
placeholder="Enter your email"
className="border p-2 w-full mb-2 rounded"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button
className="w-full bg-blue-600 text-white p-2 rounded mt-2"
type="submit"
>
Send Reset Link
</button>
<Link
className="py-1 mt-3 bg-amber-400 text-white flex justify-center items-center"
href={"/login"}
>
back to login
</Link>
{message && <p className="text-green-600 mt-2">{message}</p>}
{error && <p className="text-red-600 mt-2">{error}</p>}
</form>
</div>
</>
);
}

View file

@ -1,80 +0,0 @@
"use client";
import { useState } from "react";
import { signInWithEmailAndPassword, signInWithPopup } from "firebase/auth";
import { auth, googleProvider } from "@/lib/firebase";
import { useRouter } from "next/navigation";
import Navbar from "@/components/Navbar";
export default function LoginPage() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const router = useRouter();
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
try {
await signInWithEmailAndPassword(auth, email, password);
router.push("/dashboard");
} catch (error: any) {
alert(error.message);
}
};
const handleGoogleLogin = async () => {
try {
await signInWithPopup(auth, googleProvider);
router.push("/dashboard");
} catch (error: any) {
alert(error.message);
}
};
return (
<>
<Navbar />
<div className="flex min-h-screen items-center justify-center">
<form
className="p-6 bg-white rounded shadow-md w-full max-w-md"
onSubmit={handleLogin}
>
<h2 className="text-2xl font-bold mb-4 text-center">Login</h2>
<input
type="email"
placeholder="Email"
className="border p-2 w-full mb-2 rounded"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
placeholder="Password"
className="border p-2 w-full mb-2 rounded"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button
className="w-full bg-green-600 text-white p-2 rounded mt-2"
type="submit"
>
Login
</button>
<p className="mt-4 text-sm text-right">
<a
href="/forgot-password"
className="text-blue-600 hover:underline"
>
Forgot Password?
</a>
</p>
<button
type="button"
onClick={handleGoogleLogin}
className="w-full bg-red-500 text-white p-2 rounded mt-2"
>
Login with Google
</button>
</form>
</div>
</>
);
}

View file

@ -1,72 +0,0 @@
"use client";
import { useState } from "react";
import { createUserWithEmailAndPassword, signInWithPopup } from "firebase/auth";
import { auth, googleProvider } from "@/lib/firebase";
import { useRouter } from "next/navigation";
import Navbar from "@/components/Navbar";
export default function SignupPage() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const router = useRouter();
const handleSignup = async (e: React.FormEvent) => {
e.preventDefault();
try {
await createUserWithEmailAndPassword(auth, email, password);
router.push("/dashboard");
} catch (error: any) {
alert(error.message);
}
};
const handleGoogleLogin = async () => {
try {
await signInWithPopup(auth, googleProvider);
router.push("/dashboard");
} catch (error: any) {
alert(error.message);
}
};
return (
<>
<Navbar />
<div className="flex min-h-screen items-center justify-center">
<form
className="p-6 bg-white rounded shadow-md w-full max-w-md"
onSubmit={handleSignup}
>
<h2 className="text-2xl font-bold mb-4 text-center">Signup</h2>
<input
type="email"
placeholder="Email"
className="border p-2 w-full mb-2 rounded"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
placeholder="Password"
className="border p-2 w-full mb-2 rounded"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button
className="w-full bg-blue-600 text-white p-2 rounded mt-2"
type="submit"
>
Signup
</button>
<button
type="button"
onClick={handleGoogleLogin}
className="w-full bg-red-500 text-white p-2 rounded mt-2"
>
Signup with Google
</button>
</form>
</div>
</>
);
}

View file

@ -0,0 +1,29 @@
"use client";
import { getUserSites } from "@/lib/sites";
import { templates } from "@/lib/template";
import { notFound } from "next/navigation";
interface CartPageProps {
params: { site: string };
}
export default function CartPage({ params }: CartPageProps) {
if (typeof window === "undefined") return null; // SSR safe
const userSites = getUserSites();
const site = userSites.find((s) => s.slug === params.site);
if (!site) return notFound();
const Template = templates[site.template];
const Header = Template.Header;
const Footer = Template.Footer;
const Cart = Template.pages.Cart;
return (
<>
<Header />
<Cart />
<Footer />
</>
);
}

View file

@ -0,0 +1,29 @@
"use client";
import { templates } from "@/lib/template";
import { getUserSites } from "@/lib/sites";
import { notFound } from "next/navigation";
interface CheckoutPageProps {
params: { site: string };
}
export default function CheckoutPage({ params }: CheckoutPageProps) {
if (typeof window === "undefined") return null; // SSR safe
const userSites = getUserSites();
const site = userSites.find((s) => s.slug === params.site);
if (!site) return notFound();
const Template = templates[site.template];
const Header = Template.Header;
const Footer = Template.Footer;
const Checkout = Template.pages.Checkout;
return (
<>
<Header />
<Checkout />
<Footer />
</>
);
}

View file

@ -0,0 +1,35 @@
// app/builder/[site]/page.tsx
"use client";
import { useEffect, useState } from "react";
import { templates } from "@/lib/template";
import { getUserSites, Site } from "@/lib/sites";
interface BuilderPageProps {
params: { site: string };
}
export default function BuilderPage({ params }: BuilderPageProps) {
const [site, setSite] = useState<Site | null>(null);
useEffect(() => {
const userSites = getUserSites();
const found = userSites.find((s) => s.slug === params.site) || null;
setSite(found);
}, [params.site]);
if (!site) return <div className="p-8 text-center">Loading...</div>;
const Template = templates[site.template];
const Header = Template.Header;
const Footer = Template.Footer;
const HomePage = Template.pages.Home;
return (
<>
<Header />
<HomePage siteData={site.data} />
<Footer />
</>
);
}

View file

@ -0,0 +1,29 @@
"use client";
import { templates } from "@/lib/template";
import { getUserSites } from "@/lib/sites";
import { notFound } from "next/navigation";
interface ProductPageProps {
params: { site: string; id: string };
}
export default function ProductPage({ params }: ProductPageProps) {
if (typeof window === "undefined") return null;
const userSites = getUserSites();
const site = userSites.find((s) => s.slug === params.site);
if (!site) return notFound();
const Template = templates[site.template];
const Header = Template.Header;
const Footer = Template.Footer;
const Product = Template.pages.Product;
return (
<>
<Header />
<Product params={{ id: params.id }} />
<Footer />
</>
);
}

View file

@ -0,0 +1,28 @@
"use client";
import { getUserSites } from "@/lib/sites";
import { templates } from "@/lib/template";
import { notFound } from "next/navigation";
interface ShopPageProps {
params: { site: string };
}
export default function ShopPage({ params }: ShopPageProps) {
if (typeof window === "undefined") return null; // SSR safe
const userSites = getUserSites();
const site = userSites.find((s) => s.slug === params.site);
if (!site) return notFound();
const Template = templates[site.template];
const Header = Template.Header;
const Footer = Template.Footer;
const Shop = Template.pages.Shop;
return (
<>
<Header />
<Shop />
<Footer />
</>
);
}

View file

@ -1,39 +1,34 @@
"use client";
import { useAuthStore } from "@/store/authStore";
import { useRouter } from "next/navigation";
import Navbar from "@/components/Navbar";
import Link from "next/link";
import { getUserSites } from "@/lib/sites";
export default function DashboardPage() {
const { user, token, logout, loading } = useAuthStore();
const router = useRouter();
export default function Dashboard() {
const userId = parseInt(localStorage.getItem("userId") || "0");
if (typeof window === "undefined") return null; // SSR safe
const userSites = getUserSites();
if (loading) return <p className="text-center mt-10">Loading...</p>;
if (!user) {
router.push("/login");
return null;
}
const sites = userSites.filter((s) => s.userId === userId);
return (
<>
<Navbar />
<div className="flex flex-col items-center justify-center min-h-screen">
<h1 className="text-3xl font-bold">Welcome, {user.email}</h1>
{token ? (
<div className="mt-4 p-4 bg-gray-100 rounded w-[600px] break-words">
<p className="text-sm font-mono">{token}</p>
<main className="container mx-auto p-8">
<h1 className="text-3xl font-bold mb-6">Your Sites</h1>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{sites.map((site) => (
<div
key={site.id}
className="border p-4 rounded shadow hover:shadow-lg"
>
<h2 className="text-xl font-semibold mb-2">{site.name}</h2>
<p className="text-gray-600 mb-4">Template: {site.template}</p>
<Link
href={`/builder/${site.slug}`}
className="text-blue-600 hover:underline"
>
Open Builder
</Link>
</div>
) : (
<p className="mt-4 text-gray-500">Fetching token...</p>
)}
<button
onClick={logout}
className="mt-4 bg-red-500 text-white p-2 rounded"
>
Logout
</button>
))}
</div>
</>
</main>
);
}

View file

@ -1,26 +1 @@
@import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}

51
app/signup/page.tsx Normal file
View file

@ -0,0 +1,51 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { createUser } from "@/lib/users";
export default function Signup() {
const router = useRouter();
const [form, setForm] = useState({ name: "", email: "", businessName: "" });
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const user = createUser(form);
localStorage.setItem("userId", user.id.toString());
router.push("/template-select");
};
return (
<main className="container mx-auto p-8">
<h1 className="text-2xl font-bold mb-6">Sign Up</h1>
<form onSubmit={handleSubmit} className="space-y-4 max-w-md">
<input
type="text"
placeholder="Name"
className="w-full border p-2 rounded"
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
required
/>
<input
type="email"
placeholder="Email"
className="w-full border p-2 rounded"
value={form.email}
onChange={(e) => setForm({ ...form, email: e.target.value })}
required
/>
<input
type="text"
placeholder="Business Name"
className="w-full border p-2 rounded"
value={form.businessName}
onChange={(e) => setForm({ ...form, businessName: e.target.value })}
required
/>
<button className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
Next
</button>
</form>
</main>
);
}

View file

@ -0,0 +1,49 @@
"use client";
import { useRouter } from "next/navigation";
import { createSite, Site } from "@/lib/sites";
import { useState } from "react";
export default function TemplateSelect() {
const router = useRouter();
const userId = parseInt(localStorage.getItem("userId") || "0");
const templates = ["template1"];
const [selectedTemplate, setSelectedTemplate] = useState<string | null>(null);
const handleSelect = () => {
if (!selectedTemplate) return;
const site = createSite({
userId,
businessName: "My Business",
template: selectedTemplate,
});
router.push(`/dashboard`);
};
return (
<main className="container mx-auto p-8">
<h1 className="text-2xl font-bold mb-6">Select a Template</h1>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{templates.map((t) => (
<div
key={t}
onClick={() => setSelectedTemplate(t)}
className={`border p-4 rounded shadow cursor-pointer hover:shadow-lg transition ${
selectedTemplate === t ? "border-blue-600" : ""
}`}
>
<h2 className="font-semibold mb-2">{t}</h2>
<div className="h-40 bg-gray-100 flex items-center justify-center">
Preview
</div>
</div>
))}
</div>
<button
onClick={handleSelect}
className="mt-6 bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
>
Create Site
</button>
</main>
);
}

View file

@ -0,0 +1,9 @@
export default function Footer() {
return (
<footer className="bg-blue-600 text-white p-4 mt-10">
<div className="container mx-auto text-center">
&copy; 2025 Template 1
</div>
</footer>
);
}

View file

@ -0,0 +1,27 @@
"use client";
import React from "react";
import Link from "next/link";
const Header: React.FC = () => {
return (
<header className="bg-blue-600 text-white p-4 shadow-md">
<div className="container mx-auto flex justify-between items-center">
<h1 className="text-xl font-bold">Template 1</h1>
<nav className="space-x-4">
<Link href="#" className="hover:underline">
Home
</Link>
<Link href="#" className="hover:underline">
Shop
</Link>
<Link href="#" className="hover:underline">
Cart
</Link>
</nav>
</div>
</header>
);
};
export default Header;

View file

@ -0,0 +1,8 @@
export default function Cart() {
return (
<main className="container mx-auto p-8">
<h2 className="text-2xl font-bold mb-4">Your Cart</h2>
<p className="text-gray-700">No items in cart yet.</p>
</main>
);
}

View file

@ -0,0 +1,10 @@
export default function Checkout() {
return (
<main className="container mx-auto p-8">
<h2 className="text-2xl font-bold mb-4">Checkout</h2>
<p className="text-gray-700">
Checkout functionality will be implemented here.
</p>
</main>
);
}

View file

@ -0,0 +1,14 @@
interface HomeProps {
siteData: { heroTitle: string; heroSubtitle: string };
}
export default function Home({ siteData }: HomeProps) {
return (
<main className="container mx-auto p-8">
<section className="text-center bg-gray-100 p-8 rounded-md shadow-md">
<h2 className="text-3xl font-bold mb-2">{siteData.heroTitle}</h2>
<p className="text-gray-700">{siteData.heroSubtitle}</p>
</section>
</main>
);
}

View file

@ -0,0 +1,6 @@
// components/templates/template1/pages/index.ts
export { default as Home } from "./home";
export { default as Shop } from "./shop";
export { default as Product } from "./product";
export { default as Cart } from "./cart";
export { default as Checkout } from "./checkout";

View file

@ -0,0 +1,32 @@
interface Product {
id: number;
name: string;
price: number;
}
const products: Product[] = [
{ id: 1, name: "Product 1", price: 50 },
{ id: 2, name: "Product 2", price: 30 },
{ id: 3, name: "Product 3", price: 20 },
];
interface ProductProps {
params: { id: string };
}
export default function Product({ params }: ProductProps) {
const product = products.find((p) => p.id === parseInt(params.id));
if (!product) return <div className="p-8">Product not found</div>;
return (
<main className="container mx-auto p-8">
<div className="border rounded p-6 shadow">
<h2 className="text-2xl font-bold mb-2">{product.name}</h2>
<p className="text-gray-700 mb-4">Price: ${product.price}</p>
<button className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
Add to Cart
</button>
</div>
</main>
);
}

View file

@ -0,0 +1,38 @@
import Link from "next/link";
interface Product {
id: number;
name: string;
price: number;
}
const products: Product[] = [
{ id: 1, name: "Product 1", price: 50 },
{ id: 2, name: "Product 2", price: 30 },
{ id: 3, name: "Product 3", price: 20 },
];
export default function Shop() {
return (
<main className="container mx-auto p-8">
<h2 className="text-2xl font-bold mb-4">Shop</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{products.map((p) => (
<div
key={p.id}
className="border rounded p-4 shadow hover:shadow-lg transition"
>
<h3 className="font-semibold">{p.name}</h3>
<p className="text-gray-700">${p.price}</p>
<Link
href={`/builder/site-1/product/${p.id}`}
className="text-blue-600 hover:underline mt-2 inline-block"
>
View Product
</Link>
</div>
))}
</div>
</main>
);
}

53
lib/sites.ts Normal file
View file

@ -0,0 +1,53 @@
export interface SiteData {
heroTitle: string;
heroSubtitle: string;
}
export interface Site {
id: number;
userId: number;
slug: string;
name: string;
template: string;
data: SiteData;
}
// Get sites from localStorage
export function getUserSites(): Site[] {
if (typeof window === "undefined") return []; // SSR safe
const data = localStorage.getItem("userSites");
return data ? JSON.parse(data) : [];
}
// Save sites to localStorage
export function saveUserSites(sites: Site[]) {
localStorage.setItem("userSites", JSON.stringify(sites));
}
export function createSite({
userId,
businessName,
template,
}: {
userId: number;
businessName: string;
template: string;
}): Site {
const sites = getUserSites();
const id = sites.length + 1;
const slug = `site-${id}`;
const newSite: Site = {
id,
userId,
slug,
name: businessName,
template,
data: {
heroTitle: `Welcome to ${businessName}`,
heroSubtitle: "Best products ever",
},
};
sites.push(newSite);
saveUserSites(sites);
return newSite;
}

17
lib/template.ts Normal file
View file

@ -0,0 +1,17 @@
import T1Header from "@/components/templates/template1/layout/header";
import T1Footer from "@/components/templates/template1/layout/footer";
import * as T1Pages from "@/components//templates/template1/pages";
export interface Template {
Header: React.ComponentType;
Footer: React.ComponentType;
pages: typeof T1Pages;
}
export const templates: Record<string, Template> = {
template1: {
Header: T1Header,
Footer: T1Footer,
pages: T1Pages,
},
};

15
lib/users.ts Normal file
View file

@ -0,0 +1,15 @@
export interface User {
id: number;
name: string;
email: string;
businessName: string;
}
export const users: User[] = [];
export function createUser(data: Omit<User, "id">): User {
const id = users.length + 1;
const newUser: User = { id, ...data };
users.push(newUser);
return newUser;
}

View file

@ -22,6 +22,12 @@
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
"components/**/*"
],
"exclude": ["node_modules"]
}