Merge version_3 into main #3
136
src/app/contact/page.tsx
Normal file
136
src/app/contact/page.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
"use client";
|
||||
|
||||
import { MessageCircle } from "lucide-react";
|
||||
import Input from "@/components/form/Input";
|
||||
import { useState } from "react";
|
||||
import { Loader } from "lucide-react";
|
||||
|
||||
export default function ContactPage() {
|
||||
const [name, setName] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [message, setMessage] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
// Simulate API call
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
setSubmitted(true);
|
||||
setName("");
|
||||
setEmail("");
|
||||
setMessage("");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<div className="max-w-2xl mx-auto px-4 py-12">
|
||||
<div className="text-center mb-12">
|
||||
<MessageCircle className="w-12 h-12 text-primary-cta mx-auto mb-4" />
|
||||
<h1 className="text-4xl font-bold text-foreground mb-2">Get in Touch</h1>
|
||||
<p className="text-foreground/75">Have a question? We'd love to hear from you.</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-card rounded-lg p-8">
|
||||
{submitted ? (
|
||||
<div className="text-center py-8">
|
||||
<div className="mb-4 text-green-500">
|
||||
<MessageCircle className="w-12 h-12 mx-auto" />
|
||||
</div>
|
||||
<h2 className="text-xl font-bold text-foreground mb-2">Thank you!</h2>
|
||||
<p className="text-foreground/75 mb-6">We've received your message and will get back to you soon.</p>
|
||||
<button
|
||||
onClick={() => setSubmitted(false)}
|
||||
className="text-primary-cta hover:underline font-medium"
|
||||
>
|
||||
Send another message
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Name
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Your name"
|
||||
value={name}
|
||||
onChange={setName}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Email
|
||||
</label>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Your email"
|
||||
value={email}
|
||||
onChange={setEmail}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Message
|
||||
</label>
|
||||
<textarea
|
||||
placeholder="Your message"
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
required
|
||||
rows={6}
|
||||
className="w-full px-4 py-2 bg-secondary-cta text-foreground placeholder-foreground/50 rounded-lg border border-foreground/10 focus:outline-none focus:border-primary-cta transition-colors resize-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full bg-primary-cta text-white py-3 px-4 rounded-lg font-medium hover:opacity-90 transition-opacity disabled:opacity-50 flex items-center justify-center gap-2"
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<Loader className="w-4 h-4 animate-spin" />
|
||||
Sending...
|
||||
</>
|
||||
) : (
|
||||
"Send Message"
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* FAQ section */}
|
||||
<div className="mt-12">
|
||||
<h2 className="text-2xl font-bold text-foreground mb-6">Frequently Asked Questions</h2>
|
||||
<div className="space-y-4">
|
||||
<div className="bg-card rounded-lg p-6">
|
||||
<h3 className="font-semibold text-foreground mb-2">What is your response time?</h3>
|
||||
<p className="text-foreground/75">We aim to respond to all inquiries within 24 hours during business days.</p>
|
||||
</div>
|
||||
<div className="bg-card rounded-lg p-6">
|
||||
<h3 className="font-semibold text-foreground mb-2">How can I report content?</h3>
|
||||
<p className="text-foreground/75">Click the report button on any content and let us know the issue. Our team reviews all reports promptly.</p>
|
||||
</div>
|
||||
<div className="bg-card rounded-lg p-6">
|
||||
<h3 className="font-semibold text-foreground mb-2">Do you have technical support?</h3>
|
||||
<p className="text-foreground/75">Yes! Email support@mediahub.com for technical issues and we'll help you out.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,213 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
|
||||
import NavbarStyleFullscreen from "@/components/navbar/NavbarStyleFullscreen/NavbarStyleFullscreen";
|
||||
import ProductCardTwo from "@/components/sections/product/ProductCardTwo";
|
||||
import SocialProofOne from "@/components/sections/socialProof/SocialProofOne";
|
||||
import FooterSimple from "@/components/sections/footer/FooterSimple";
|
||||
import Link from "next/link";
|
||||
import { ImageIcon } from "lucide-react";
|
||||
|
||||
export default function GalleryPage() {
|
||||
return (
|
||||
<ThemeProvider
|
||||
defaultButtonVariant="hover-magnetic"
|
||||
defaultTextAnimation="reveal-blur"
|
||||
borderRadius="rounded"
|
||||
contentWidth="compact"
|
||||
sizing="medium"
|
||||
background="aurora"
|
||||
cardStyle="gradient-mesh"
|
||||
primaryButtonStyle="flat"
|
||||
secondaryButtonStyle="solid"
|
||||
headingFontWeight="light"
|
||||
>
|
||||
<div id="nav" data-section="nav">
|
||||
<NavbarStyleFullscreen
|
||||
navItems={[
|
||||
{ name: "Home", id: "/" },
|
||||
{ name: "Explore", id: "/gallery" },
|
||||
{ name: "Upload", id: "upload" },
|
||||
{ name: "Support", id: "contact" },
|
||||
{ name: "Join Now", id: "#" },
|
||||
]}
|
||||
brandName="MediaHub"
|
||||
bottomLeftText="Creative Community"
|
||||
bottomRightText="support@mediahub.com"
|
||||
/>
|
||||
</div>
|
||||
<div className="min-h-screen bg-background">
|
||||
<div className="max-w-6xl mx-auto px-4 py-12">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl font-bold text-foreground mb-2">Creative Gallery</h1>
|
||||
<p className="text-foreground/75">Discover amazing photos and videos from our creative community</p>
|
||||
</div>
|
||||
|
||||
<div id="gallery" data-section="gallery">
|
||||
<ProductCardTwo
|
||||
title="Featured Content Gallery"
|
||||
description="Discover amazing photos and videos from our creative community"
|
||||
tag="Gallery"
|
||||
tagIcon={ImageIcon}
|
||||
products={[
|
||||
{
|
||||
id: "1",
|
||||
brand: "Sarah Chen",
|
||||
name: "Sunset Mountain Photography",
|
||||
price: "4.9★",
|
||||
rating: 5,
|
||||
reviewCount: "2.3k",
|
||||
imageSrc: "http://img.b2bpic.net/free-photo/beautiful-setting-sun-forest-mountain-landscape_181624-23715.jpg?_wi=2",
|
||||
imageAlt: "sunset mountain landscape photography",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
brand: "Alex Rodriguez",
|
||||
name: "Urban Street Art",
|
||||
price: "4.8★",
|
||||
rating: 5,
|
||||
reviewCount: "1.9k",
|
||||
imageSrc: "http://img.b2bpic.net/free-photo/woman-blue-coat-street_158595-2575.jpg?_wi=2",
|
||||
imageAlt: "urban street art photography city",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
brand: "Maya Patel",
|
||||
name: "Nature's Beautiful Details",
|
||||
price: "4.9★",
|
||||
rating: 5,
|
||||
reviewCount: "3.1k",
|
||||
imageSrc: "http://img.b2bpic.net/free-photo/macro-shot-anthurium-flower_23-2147836286.jpg?_wi=2",
|
||||
imageAlt: "macro photography nature details close-up",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
brand: "James Wilson",
|
||||
name: "Travel Vlog Collection",
|
||||
price: "4.7★",
|
||||
rating: 5,
|
||||
reviewCount: "2.7k",
|
||||
imageSrc: "http://img.b2bpic.net/free-photo/camera-man-filming-couple-inside-transparent-bubble-tent-glamping-professional-camera_1268-24534.jpg?_wi=2",
|
||||
imageAlt: "travel vlog video production cinematography",
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
brand: "Emma Thompson",
|
||||
name: "Creative Animations",
|
||||
price: "4.8★",
|
||||
rating: 5,
|
||||
reviewCount: "1.5k",
|
||||
imageSrc: "http://img.b2bpic.net/free-photo/video-creator-woman-editing-music-video-dual-monitors-agency-studio_482257-119068.jpg?_wi=2",
|
||||
imageAlt: "animation creative video motion graphics",
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
brand: "Lucas Kim",
|
||||
name: "Design & Art Showcase",
|
||||
price: "4.9★",
|
||||
rating: 5,
|
||||
reviewCount: "2.2k",
|
||||
imageSrc: "http://img.b2bpic.net/free-vector/design-process-landing-page-concept_52683-25329.jpg?_wi=2",
|
||||
imageAlt: "graphic design art illustration showcase",
|
||||
},
|
||||
{
|
||||
id: "7",
|
||||
brand: "Sophie Turner",
|
||||
name: "Fashion & Lifestyle",
|
||||
price: "4.8★",
|
||||
rating: 5,
|
||||
reviewCount: "1.8k",
|
||||
imageSrc: "http://img.b2bpic.net/free-psd/fashion-magazine-template-design_23-2150583108.jpg?_wi=2",
|
||||
imageAlt: "fashion magazine template design",
|
||||
},
|
||||
{
|
||||
id: "8",
|
||||
brand: "David Chen",
|
||||
name: "Architecture & Design",
|
||||
price: "4.9★",
|
||||
rating: 5,
|
||||
reviewCount: "2.4k",
|
||||
imageSrc: "http://img.b2bpic.net/free-photo/three-frames-shelf_23-2147755106.jpg?_wi=2",
|
||||
imageAlt: "architecture and design showcase",
|
||||
},
|
||||
{
|
||||
id: "9",
|
||||
brand: "Jessica Park",
|
||||
name: "Food & Culinary",
|
||||
price: "4.7★",
|
||||
rating: 5,
|
||||
reviewCount: "1.6k",
|
||||
imageSrc: "http://img.b2bpic.net/free-photo/professional-camera-recording-cooking-show-content-with-chefs-restaurant-kitchen-filming-people-making-food-recipe-dish-with-fresh-ingredients-gastronomy-cuisine-online-class_482257-46088.jpg?_wi=2",
|
||||
imageAlt: "professional camera recording cooking show",
|
||||
},
|
||||
]}
|
||||
gridVariant="bento-grid-inverted"
|
||||
animationType="slide-up"
|
||||
textboxLayout="default"
|
||||
useInvertedBackground={false}
|
||||
/>
|
||||
{/* Gallery Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{[1, 2, 3, 4, 5, 6].map((item) => (
|
||||
<div
|
||||
key={item}
|
||||
className="bg-card rounded-lg overflow-hidden hover:shadow-lg transition-shadow cursor-pointer group"
|
||||
>
|
||||
<div className="aspect-video bg-background/50 flex items-center justify-center group-hover:bg-background transition-colors">
|
||||
<ImageIcon className="w-12 h-12 text-foreground/30" />
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<h3 className="font-semibold text-foreground mb-1">Featured Content</h3>
|
||||
<p className="text-sm text-foreground/75">By Creative Community</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="social-proof" data-section="social-proof">
|
||||
<SocialProofOne
|
||||
title="Trusted by Creative Communities"
|
||||
description="Join creators from around the world who share their work on MediaHub"
|
||||
tag="Social Proof"
|
||||
textboxLayout="default"
|
||||
useInvertedBackground={false}
|
||||
names={[
|
||||
"Photography Studios",
|
||||
"Video Production Houses",
|
||||
"Design Agencies",
|
||||
"Artists Collective",
|
||||
"Content Creators",
|
||||
"Freelance Portfolio",
|
||||
"Creative Networks",
|
||||
"Digital Artists",
|
||||
]}
|
||||
speed={40}
|
||||
showCard={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="footer" data-section="footer">
|
||||
<FooterSimple
|
||||
columns={[
|
||||
{
|
||||
title: "Platform",
|
||||
items: [
|
||||
{ label: "Upload", href: "/upload" },
|
||||
{ label: "Explore", href: "/gallery" },
|
||||
{ label: "Collections", href: "#collections" },
|
||||
{ label: "Trending", href: "#trending" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Community",
|
||||
items: [
|
||||
{ label: "Creators", href: "#creators" },
|
||||
{ label: "Challenges", href: "#challenges" },
|
||||
{ label: "Support", href: "/support" },
|
||||
{ label: "Blog", href: "#blog" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Resources",
|
||||
items: [
|
||||
{ label: "Help Center", href: "#help" },
|
||||
{ label: "API Docs", href: "#docs" },
|
||||
{ label: "Contact", href: "mailto:support@mediahub.com" },
|
||||
{ label: "Status", href: "#status" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Legal",
|
||||
items: [
|
||||
{ label: "Privacy Policy", href: "#privacy" },
|
||||
{ label: "Terms of Service", href: "#terms" },
|
||||
{ label: "Cookie Policy", href: "#cookies" },
|
||||
{ label: "Guidelines", href: "#guidelines" },
|
||||
],
|
||||
},
|
||||
]}
|
||||
bottomLeftText="© 2025 MediaHub. All rights reserved."
|
||||
bottomRightText="Made with creativity and passion"
|
||||
/>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
99
src/app/login/page.tsx
Normal file
99
src/app/login/page.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Input from "@/components/form/Input";
|
||||
import Link from "next/link";
|
||||
import { Mail, Lock, Loader } from "lucide-react";
|
||||
|
||||
export default function LoginPage() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { login } = useAuth();
|
||||
const router = useRouter();
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
await login(email, password);
|
||||
router.push("/");
|
||||
} catch (err: any) {
|
||||
setError(err.message || "Login failed. Please try again.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-background px-4">
|
||||
<div className="w-full max-w-md bg-card rounded-lg p-8 shadow-lg">
|
||||
<h1 className="text-3xl font-bold text-foreground mb-2">Welcome Back</h1>
|
||||
<p className="text-foreground/75 mb-6">Sign in to your MediaHub account</p>
|
||||
|
||||
{error && (
|
||||
<div className="mb-4 p-3 bg-red-500/10 border border-red-500 rounded-lg text-red-500 text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Email Address
|
||||
</label>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Enter your email"
|
||||
value={email}
|
||||
onChange={setEmail}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Password
|
||||
</label>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Enter your password"
|
||||
value={password}
|
||||
onChange={setPassword}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full bg-primary-cta text-white py-2 px-4 rounded-lg font-medium hover:opacity-90 transition-opacity disabled:opacity-50 flex items-center justify-center gap-2"
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<Loader className="w-4 h-4 animate-spin" />
|
||||
Signing in...
|
||||
</>
|
||||
) : (
|
||||
"Sign In"
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div className="mt-6 text-center">
|
||||
<p className="text-foreground/75 text-sm">
|
||||
Don't have an account?{" "}
|
||||
<Link href="/signup" className="text-primary-cta hover:underline font-medium">
|
||||
Create one
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -13,8 +13,36 @@ import ContactFaq from "@/components/sections/contact/ContactFaq";
|
||||
import FooterSimple from "@/components/sections/footer/FooterSimple";
|
||||
import Link from "next/link";
|
||||
import { Sparkles, Zap, Upload, Images, FileCheck, Grid, Smartphone, Eye, Users, MessageCircle, Heart, ImageIcon, Check, Phone } from "lucide-react";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function HomePage() {
|
||||
const { user, logout } = useAuth();
|
||||
const router = useRouter();
|
||||
const [showLogoutMenu, setShowLogoutMenu] = useState(false);
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout();
|
||||
setShowLogoutMenu(false);
|
||||
router.push("/");
|
||||
};
|
||||
|
||||
const navItems = user
|
||||
? [
|
||||
{ name: "Home", id: "/" },
|
||||
{ name: "Explore", id: "/gallery" },
|
||||
{ name: "Upload", id: "/upload" },
|
||||
{ name: "Support", id: "/contact" },
|
||||
]
|
||||
: [
|
||||
{ name: "Home", id: "/" },
|
||||
{ name: "Explore", id: "/gallery" },
|
||||
{ name: "Upload", id: "/upload" },
|
||||
{ name: "Support", id: "/contact" },
|
||||
{ name: "Join Now", id: "/login" },
|
||||
];
|
||||
|
||||
return (
|
||||
<ThemeProvider
|
||||
defaultButtonVariant="hover-magnetic"
|
||||
@@ -30,17 +58,31 @@ export default function HomePage() {
|
||||
>
|
||||
<div id="nav" data-section="nav">
|
||||
<NavbarStyleFullscreen
|
||||
navItems={[
|
||||
{ name: "Home", id: "/" },
|
||||
{ name: "Explore", id: "/gallery" },
|
||||
{ name: "Upload", id: "upload" },
|
||||
{ name: "Support", id: "contact" },
|
||||
{ name: "Join Now", id: "#" },
|
||||
]}
|
||||
navItems={navItems}
|
||||
brandName="MediaHub"
|
||||
bottomLeftText="Creative Community"
|
||||
bottomRightText="support@mediahub.com"
|
||||
bottomRightText={user ? `${user.email}` : "support@mediahub.com"}
|
||||
/>
|
||||
{user && (
|
||||
<div className="fixed top-4 right-4 z-50">
|
||||
<button
|
||||
onClick={() => setShowLogoutMenu(!showLogoutMenu)}
|
||||
className="px-4 py-2 rounded-lg bg-primary-cta text-white text-sm font-medium hover:opacity-90 transition-opacity"
|
||||
>
|
||||
{user.email}
|
||||
</button>
|
||||
{showLogoutMenu && (
|
||||
<div className="absolute top-12 right-0 bg-card rounded-lg shadow-lg p-2 w-32">
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="w-full text-left px-4 py-2 text-sm hover:bg-background rounded transition-colors"
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div id="hero" data-section="hero">
|
||||
@@ -268,7 +310,7 @@ export default function HomePage() {
|
||||
title: "Community", items: [
|
||||
{ label: "Creators", href: "#creators" },
|
||||
{ label: "Challenges", href: "#challenges" },
|
||||
{ label: "Support", href: "/support" },
|
||||
{ label: "Support", href: "/contact" },
|
||||
{ label: "Blog", href: "#blog" },
|
||||
],
|
||||
},
|
||||
|
||||
124
src/app/signup/page.tsx
Normal file
124
src/app/signup/page.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Input from "@/components/form/Input";
|
||||
import Link from "next/link";
|
||||
import { Loader } from "lucide-react";
|
||||
|
||||
export default function SignupPage() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [confirmPassword, setConfirmPassword] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { signup } = useAuth();
|
||||
const router = useRouter();
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
setError("Passwords do not match");
|
||||
return;
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
setError("Password must be at least 6 characters");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
await signup(email, password);
|
||||
router.push("/");
|
||||
} catch (err: any) {
|
||||
setError(err.message || "Signup failed. Please try again.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-background px-4">
|
||||
<div className="w-full max-w-md bg-card rounded-lg p-8 shadow-lg">
|
||||
<h1 className="text-3xl font-bold text-foreground mb-2">Join MediaHub</h1>
|
||||
<p className="text-foreground/75 mb-6">Create your account and start sharing</p>
|
||||
|
||||
{error && (
|
||||
<div className="mb-4 p-3 bg-red-500/10 border border-red-500 rounded-lg text-red-500 text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Email Address
|
||||
</label>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Enter your email"
|
||||
value={email}
|
||||
onChange={setEmail}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Password
|
||||
</label>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Create a password"
|
||||
value={password}
|
||||
onChange={setPassword}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Confirm Password
|
||||
</label>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Confirm your password"
|
||||
value={confirmPassword}
|
||||
onChange={setConfirmPassword}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full bg-primary-cta text-white py-2 px-4 rounded-lg font-medium hover:opacity-90 transition-opacity disabled:opacity-50 flex items-center justify-center gap-2"
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<Loader className="w-4 h-4 animate-spin" />
|
||||
Creating account...
|
||||
</>
|
||||
) : (
|
||||
"Create Account"
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div className="mt-6 text-center">
|
||||
<p className="text-foreground/75 text-sm">
|
||||
Already have an account?{" "}
|
||||
<Link href="/login" className="text-primary-cta hover:underline font-medium">
|
||||
Sign in
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,183 +1,75 @@
|
||||
"use client";
|
||||
|
||||
import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
|
||||
import NavbarStyleFullscreen from "@/components/navbar/NavbarStyleFullscreen/NavbarStyleFullscreen";
|
||||
import HeroBillboard from "@/components/sections/hero/HeroBillboard";
|
||||
import FeatureCardTen from "@/components/sections/feature/FeatureCardTen";
|
||||
import InlineImageSplitTextAbout from "@/components/sections/about/InlineImageSplitTextAbout";
|
||||
import FooterSimple from "@/components/sections/footer/FooterSimple";
|
||||
import { Upload, Images, FileCheck, Zap } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import { Upload, AlertCircle } from "lucide-react";
|
||||
|
||||
export default function UploadPage() {
|
||||
const { user, loading } = useAuth();
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && !user) {
|
||||
router.push("/login?redirect=/upload");
|
||||
}
|
||||
}, [user, loading, router]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-background">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin mb-4">
|
||||
<Upload className="w-8 h-8 text-primary-cta" />
|
||||
</div>
|
||||
<p className="text-foreground">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-background">
|
||||
<div className="max-w-md bg-card rounded-lg p-8 text-center">
|
||||
<AlertCircle className="w-12 h-12 text-red-500 mx-auto mb-4" />
|
||||
<h2 className="text-xl font-bold text-foreground mb-2">Sign in required</h2>
|
||||
<p className="text-foreground/75">You must be logged in to upload content.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemeProvider
|
||||
defaultButtonVariant="hover-magnetic"
|
||||
defaultTextAnimation="reveal-blur"
|
||||
borderRadius="rounded"
|
||||
contentWidth="compact"
|
||||
sizing="medium"
|
||||
background="aurora"
|
||||
cardStyle="gradient-mesh"
|
||||
primaryButtonStyle="flat"
|
||||
secondaryButtonStyle="solid"
|
||||
headingFontWeight="light"
|
||||
>
|
||||
{/* Navbar */}
|
||||
<div id="nav" data-section="nav">
|
||||
<NavbarStyleFullscreen
|
||||
brandName="MediaHub"
|
||||
navItems={[
|
||||
{ name: "Home", id: "/" },
|
||||
{ name: "Explore", id: "/gallery" },
|
||||
{ name: "Upload", id: "/upload" },
|
||||
{ name: "Support", id: "/support" },
|
||||
{ name: "Join Now", id: "#" },
|
||||
]}
|
||||
bottomLeftText="Creative Community"
|
||||
bottomRightText="support@mediahub.com"
|
||||
/>
|
||||
</div>
|
||||
<div className="min-h-screen bg-background">
|
||||
<div className="max-w-4xl mx-auto px-4 py-12">
|
||||
<div className="bg-card rounded-lg p-8">
|
||||
<h1 className="text-3xl font-bold text-foreground mb-2">Upload Content</h1>
|
||||
<p className="text-foreground/75 mb-8">
|
||||
Welcome, {user.email}! Share your creative work with the community.
|
||||
</p>
|
||||
|
||||
{/* Hero Section */}
|
||||
<div id="hero" data-section="hero">
|
||||
<HeroBillboard
|
||||
title="Upload Your Creative Vision with the World"
|
||||
description="Share your stunning photos and videos instantly. Our intuitive upload system makes it effortless to share, organize, and showcase your best work to a thriving global community."
|
||||
background={{ variant: "sparkles-gradient" }}
|
||||
tag="Upload Made Easy"
|
||||
tagIcon={Zap}
|
||||
buttons={[
|
||||
{ text: "Start Uploading Now", href: "/upload" },
|
||||
{ text: "Learn More", href: "/gallery" },
|
||||
]}
|
||||
imageSrc="http://img.b2bpic.net/free-vector/template-dashboard-user-panel_23-2148371519.jpg?_wi=2"
|
||||
imageAlt="Upload interface dashboard"
|
||||
/>
|
||||
</div>
|
||||
{/* Drag and drop area */}
|
||||
<div className="border-2 border-dashed border-foreground/20 rounded-lg p-12 text-center mb-8 hover:border-foreground/40 transition-colors cursor-pointer">
|
||||
<Upload className="w-12 h-12 text-foreground/50 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-semibold text-foreground mb-2">Drag and drop your files</h3>
|
||||
<p className="text-foreground/75 mb-4">or click to browse from your computer</p>
|
||||
<p className="text-sm text-foreground/50">Supported formats: JPG, PNG, MP4, MOV, WebM</p>
|
||||
</div>
|
||||
|
||||
{/* Features Section */}
|
||||
<div id="features" data-section="features">
|
||||
<FeatureCardTen
|
||||
title="Seamless Upload & Organization"
|
||||
description="Everything you need to upload, organize, and manage your creative content with ease"
|
||||
tag="Upload Features"
|
||||
tagIcon={Zap}
|
||||
features={[
|
||||
{
|
||||
id: "1",
|
||||
title: "Drag & Drop Upload System",
|
||||
description: "Upload multiple photos and videos at once with our intuitive drag-and-drop interface. Batch processing saves time and keeps your workflow smooth.",
|
||||
media: {
|
||||
imageSrc: "http://img.b2bpic.net/free-photo/elegant-uber-driver-giving-taxi-ride_23-2149241774.jpg?_wi=2",
|
||||
},
|
||||
items: [
|
||||
{ icon: Upload, text: "Drag & drop upload" },
|
||||
{ icon: Images, text: "Batch processing" },
|
||||
{ icon: FileCheck, text: "Auto-organization" },
|
||||
],
|
||||
reverse: false,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
title: "Smart File Management",
|
||||
description: "Automatically organize your uploads into collections. Tag, categorize, and manage your entire portfolio from one powerful dashboard.",
|
||||
media: {
|
||||
imageSrc: "http://img.b2bpic.net/free-photo/three-frames-shelf_23-2147755106.jpg?_wi=3",
|
||||
},
|
||||
items: [
|
||||
{ icon: Images, text: "Auto-categorization" },
|
||||
{ icon: FileCheck, text: "Smart tagging" },
|
||||
{ icon: Upload, text: "Bulk operations" },
|
||||
],
|
||||
reverse: true,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
title: "Quality & Format Support",
|
||||
description: "Support for all major formats - JPG, PNG, MP4, MOV, WebM and more. Automatic optimization ensures your content looks perfect everywhere.",
|
||||
media: {
|
||||
imageSrc: "http://img.b2bpic.net/free-photo/company-partners-working-office_23-2148352762.jpg?_wi=2",
|
||||
},
|
||||
items: [
|
||||
{ icon: FileCheck, text: "Format support" },
|
||||
{ icon: Images, text: "Auto-optimization" },
|
||||
{ icon: Upload, text: "Quality preservation" },
|
||||
],
|
||||
reverse: false,
|
||||
},
|
||||
]}
|
||||
textboxLayout="default"
|
||||
animationType="slide-up"
|
||||
useInvertedBackground={false}
|
||||
/>
|
||||
{/* Upload info */}
|
||||
<div className="bg-background rounded-lg p-6">
|
||||
<h3 className="font-semibold text-foreground mb-4">Upload Tips:</h3>
|
||||
<ul className="space-y-2 text-foreground/75 text-sm">
|
||||
<li>• Photos up to 50MB each</li>
|
||||
<li>• Videos up to 500MB each</li>
|
||||
<li>• Organize into collections for easy management</li>
|
||||
<li>• Set privacy controls for each upload</li>
|
||||
<li>• Add tags and descriptions for better discovery</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CTA Section */}
|
||||
<div id="process" data-section="process">
|
||||
<InlineImageSplitTextAbout
|
||||
heading={[
|
||||
{ type: "text", content: "Ready to" },
|
||||
{
|
||||
type: "image",
|
||||
src: "http://img.b2bpic.net/free-vector/cute-influencer-talent-agency-logo-template_742173-17610.jpg",
|
||||
alt: "MediaHub logo",
|
||||
},
|
||||
{ type: "text", content: "Share Your Work?" },
|
||||
]}
|
||||
buttons={[
|
||||
{ text: "Begin Uploading", href: "/upload" },
|
||||
{ text: "Explore Gallery", href: "/gallery" },
|
||||
]}
|
||||
useInvertedBackground={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div id="footer" data-section="footer">
|
||||
<FooterSimple
|
||||
columns={[
|
||||
{
|
||||
title: "Platform",
|
||||
items: [
|
||||
{ label: "Upload", href: "/upload" },
|
||||
{ label: "Explore", href: "/gallery" },
|
||||
{ label: "Collections", href: "#collections" },
|
||||
{ label: "Trending", href: "#trending" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Community",
|
||||
items: [
|
||||
{ label: "Creators", href: "#creators" },
|
||||
{ label: "Challenges", href: "#challenges" },
|
||||
{ label: "Support", href: "/support" },
|
||||
{ label: "Blog", href: "#blog" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Resources",
|
||||
items: [
|
||||
{ label: "Help Center", href: "#help" },
|
||||
{ label: "API Docs", href: "#docs" },
|
||||
{ label: "Contact", href: "mailto:support@mediahub.com" },
|
||||
{ label: "Status", href: "#status" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Legal",
|
||||
items: [
|
||||
{ label: "Privacy Policy", href: "#privacy" },
|
||||
{ label: "Terms of Service", href: "#terms" },
|
||||
{ label: "Cookie Policy", href: "#cookies" },
|
||||
{ label: "Guidelines", href: "#guidelines" },
|
||||
],
|
||||
},
|
||||
]}
|
||||
bottomLeftText="© 2025 MediaHub. All rights reserved."
|
||||
bottomRightText="Made with creativity and passion"
|
||||
/>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
155
src/hooks/useAuth.ts
Normal file
155
src/hooks/useAuth.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
interface AuthState {
|
||||
user: User | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3001";
|
||||
|
||||
export function useAuth() {
|
||||
const [state, setState] = useState<AuthState>({
|
||||
user: null,
|
||||
loading: true,
|
||||
error: null,
|
||||
});
|
||||
const router = useRouter();
|
||||
|
||||
// Check session on mount
|
||||
useEffect(() => {
|
||||
checkSession();
|
||||
}, []);
|
||||
|
||||
const checkSession = useCallback(async () => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/auth/session`, {
|
||||
credentials: "include"});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setState({
|
||||
user: data.user,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
} else {
|
||||
setState({
|
||||
user: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
setState({
|
||||
user: null,
|
||||
loading: false,
|
||||
error: "Failed to check session"});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const login = useCallback(
|
||||
async (email: string, password: string) => {
|
||||
setState((prev) => ({ ...prev, loading: true, error: null }));
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/auth/login`, {
|
||||
method: "POST", headers: {
|
||||
"Content-Type": "application/json"},
|
||||
credentials: "include", body: JSON.stringify({ email, password }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.message || "Login failed");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
setState({
|
||||
user: data.user,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
} catch (error: any) {
|
||||
setState({
|
||||
user: null,
|
||||
loading: false,
|
||||
error: error.message || "Login failed"});
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const signup = useCallback(
|
||||
async (email: string, password: string) => {
|
||||
setState((prev) => ({ ...prev, loading: true, error: null }));
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/auth/signup`, {
|
||||
method: "POST", headers: {
|
||||
"Content-Type": "application/json"},
|
||||
credentials: "include", body: JSON.stringify({ email, password }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.message || "Signup failed");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
setState({
|
||||
user: data.user,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
} catch (error: any) {
|
||||
setState({
|
||||
user: null,
|
||||
loading: false,
|
||||
error: error.message || "Signup failed"});
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const logout = useCallback(async () => {
|
||||
setState((prev) => ({ ...prev, loading: true }));
|
||||
|
||||
try {
|
||||
await fetch(`${API_BASE_URL}/api/auth/logout`, {
|
||||
method: "POST", credentials: "include"});
|
||||
|
||||
setState({
|
||||
user: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
} catch (error: any) {
|
||||
setState({
|
||||
user: null,
|
||||
loading: false,
|
||||
error: error.message || "Logout failed"});
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
user: state.user,
|
||||
loading: state.loading,
|
||||
error: state.error,
|
||||
login,
|
||||
signup,
|
||||
logout,
|
||||
checkSession,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user