Removed unused imports

This commit is contained in:
2026-02-21 20:05:04 +05:30
parent 5062133524
commit 857c889cb1
8 changed files with 183 additions and 38 deletions

View File

@@ -1,7 +1,7 @@
"use client"; "use client";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { Diamond, Users, Trophy, Palette, Target, Heart } from "lucide-react"; import { Diamond, Users, Trophy, Palette, Heart } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
const stats = [ const stats = [

View File

@@ -14,6 +14,8 @@ import {
CheckCircle CheckCircle
} from "lucide-react"; } from "lucide-react";
import Link from "next/link";
const contactMethods = [ const contactMethods = [
{ {
icon: Mail, icon: Mail,
@@ -108,12 +110,12 @@ export default function ContactPage() {
<p className="text-zinc-400 text-lg mb-8"> <p className="text-zinc-400 text-lg mb-8">
Thank you for reaching out. We'll get back to you within 24-48 hours. Thank you for reaching out. We'll get back to you within 24-48 hours.
</p> </p>
<a <Link
href="/" href="/"
className="inline-flex items-center justify-center bg-zinc-900 border border-zinc-800 text-white px-6 py-3 rounded-xl font-semibold hover:border-zinc-700 transition-colors" className="inline-flex items-center justify-center bg-zinc-900 border border-zinc-800 text-white px-6 py-3 rounded-xl font-semibold hover:border-zinc-700 transition-colors"
> >
Back to Home Back to Home
</a> </Link>
</motion.div> </motion.div>
</div> </div>
</main> </main>
@@ -314,6 +316,7 @@ export default function ContactPage() {
onChange={handleChange} onChange={handleChange}
required required
rows={5} rows={5}
maxLength={1000}
placeholder="Describe your question or issue in detail..." placeholder="Describe your question or issue in detail..."
className="w-full bg-zinc-800 border border-zinc-700 rounded-xl px-4 py-3 text-white placeholder-zinc-500 focus:outline-none focus:border-lime-400 transition-colors resize-none" className="w-full bg-zinc-800 border border-zinc-700 rounded-xl px-4 py-3 text-white placeholder-zinc-500 focus:outline-none focus:border-lime-400 transition-colors resize-none"
/> />

View File

@@ -2,10 +2,10 @@
import { useState } from "react"; import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion"; import { motion, AnimatePresence } from "framer-motion";
import Link from "next/link";
import { import {
ChevronDown, ChevronDown,
HelpCircle, HelpCircle,
Users,
Vote, Vote,
Upload, Upload,
Award, Award,
@@ -282,12 +282,12 @@ export default function FAQPage() {
<p className="text-zinc-400 mb-6"> <p className="text-zinc-400 mb-6">
Can't find what you're looking for? We're here to help! Can't find what you're looking for? We're here to help!
</p> </p>
<a <Link
href="/contact" href="/contact"
className="inline-flex items-center justify-center gap-2 bg-lime-400 text-black px-6 py-3 rounded-xl font-bold hover:bg-lime-300 transition-colors" className="inline-flex items-center justify-center gap-2 bg-lime-400 text-black px-6 py-3 rounded-xl font-bold hover:bg-lime-300 transition-colors"
> >
Contact Us Contact Us
</a> </Link>
</div> </div>
</motion.div> </motion.div>
</div> </div>

View File

@@ -1,10 +1,11 @@
"use client"; "use client";
import { useState, useMemo } from "react"; import { useState, useMemo, useEffect, useCallback } from "react";
import { motion, AnimatePresence } from "framer-motion"; import { motion, AnimatePresence } from "framer-motion";
import Image from "next/image";
import { import {
Search, Search,
Filter, Filter,
Heart, Heart,
Monitor, Monitor,
Palette, Palette,
@@ -133,9 +134,38 @@ export default function GalleryPage() {
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const [viewMode, setViewMode] = useState<"grid" | "large">("grid"); const [viewMode, setViewMode] = useState<"grid" | "large">("grid");
const [selectedArtwork, setSelectedArtwork] = useState<typeof mockArtworks[0] | null>(null); const [selectedArtwork, setSelectedArtwork] = useState<typeof mockArtworks[0] | null>(null);
const [votedArtworks, setVotedArtworks] = useState<Set<string>>(new Set()); const [votedArtworks, setVotedArtworks] = useState<Set<string>>(() => {
if (typeof window !== "undefined") {
try {
const stored = localStorage.getItem("artsplash-votes");
return stored ? new Set(JSON.parse(stored)) : new Set();
} catch {
return new Set();
}
}
return new Set();
});
const [showFilters, setShowFilters] = useState(false); const [showFilters, setShowFilters] = useState(false);
// Lock body scroll when modal is open
useEffect(() => {
if (selectedArtwork) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "";
}
return () => { document.body.style.overflow = ""; };
}, [selectedArtwork]);
// Close modal on Escape key
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") setSelectedArtwork(null);
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, []);
// Use a daily seed for consistent randomization throughout the day // Use a daily seed for consistent randomization throughout the day
const dailySeed = useMemo(() => { const dailySeed = useMemo(() => {
const today = new Date(); const today = new Date();
@@ -165,14 +195,16 @@ export default function GalleryPage() {
return seededShuffle(result, dailySeed); return seededShuffle(result, dailySeed);
}, [selectedCategory, searchQuery, dailySeed]); }, [selectedCategory, searchQuery, dailySeed]);
const handleVote = (artworkId: string) => { const handleVote = useCallback((artworkId: string) => {
if (votedArtworks.has(artworkId)) { if (votedArtworks.has(artworkId)) return;
// Already voted - in production, show a message setVotedArtworks(prev => {
return; const next = new Set([...prev, artworkId]);
} try {
setVotedArtworks(prev => new Set([...prev, artworkId])); localStorage.setItem("artsplash-votes", JSON.stringify([...next]));
// TODO: Send vote to API } catch { /* storage full - ignore */ }
}; return next;
});
}, [votedArtworks]);
return ( return (
<main className="min-h-screen bg-black pt-32 pb-20"> <main className="min-h-screen bg-black pt-32 pb-20">
@@ -328,10 +360,13 @@ export default function GalleryPage() {
className="relative aspect-square cursor-pointer overflow-hidden" className="relative aspect-square cursor-pointer overflow-hidden"
onClick={() => setSelectedArtwork(artwork)} onClick={() => setSelectedArtwork(artwork)}
> >
<img <Image
src={artwork.imageUrl} src={artwork.imageUrl}
alt={artwork.title} alt={artwork.title}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500" fill
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
className="object-cover group-hover:scale-105 transition-transform duration-500"
loading="lazy"
/> />
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/40 transition-colors flex items-center justify-center opacity-0 group-hover:opacity-100"> <div className="absolute inset-0 bg-black/0 group-hover:bg-black/40 transition-colors flex items-center justify-center opacity-0 group-hover:opacity-100">
<Eye className="w-8 h-8 text-white" /> <Eye className="w-8 h-8 text-white" />
@@ -400,11 +435,16 @@ export default function GalleryPage() {
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<div className="relative"> <div className="relative">
<img <div className="relative aspect-video">
src={selectedArtwork.imageUrl} <Image
alt={selectedArtwork.title} src={selectedArtwork.imageUrl}
className="w-full aspect-video object-cover" alt={selectedArtwork.title}
/> fill
sizes="(max-width: 1024px) 100vw, 896px"
className="object-cover"
priority
/>
</div>
<button <button
onClick={() => setSelectedArtwork(null)} onClick={() => setSelectedArtwork(null)}
className="absolute top-4 right-4 p-2 bg-black/50 backdrop-blur-sm rounded-full hover:bg-black/70 transition-colors" className="absolute top-4 right-4 p-2 bg-black/50 backdrop-blur-sm rounded-full hover:bg-black/70 transition-colors"

View File

@@ -4,7 +4,6 @@ import { useState } from "react";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { import {
Mail, Mail,
Lock,
User, User,
Phone, Phone,
GraduationCap, GraduationCap,
@@ -38,13 +37,20 @@ export default function RegisterPage() {
agreeToRules: false, agreeToRules: false,
}); });
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [isRegistered, setIsRegistered] = useState(false);
const [isEmailValid, setIsEmailValid] = useState<boolean | null>(null); const [isEmailValid, setIsEmailValid] = useState<boolean | null>(null);
const [isPhoneValid, setIsPhoneValid] = useState<boolean | null>(null);
const validateEmail = (email: string) => { const validateEmail = (email: string) => {
const sjecEmailRegex = /^[a-zA-Z0-9._%+-]+@sjec\.ac\.in$/; const sjecEmailRegex = /^[a-zA-Z0-9._%+-]+@sjec\.ac\.in$/;
return sjecEmailRegex.test(email); return sjecEmailRegex.test(email);
}; };
const validatePhone = (phone: string) => {
const phoneRegex = /^(\+91[\s-]?)?[6-9]\d{9}$/;
return phoneRegex.test(phone.replace(/\s/g, ""));
};
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const email = e.target.value; const email = e.target.value;
setFormData({ ...formData, email }); setFormData({ ...formData, email });
@@ -55,18 +61,72 @@ export default function RegisterPage() {
} }
}; };
const handlePhoneChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const phone = e.target.value;
setFormData({ ...formData, phone });
if (phone.length > 0) {
setIsPhoneValid(validatePhone(phone));
} else {
setIsPhoneValid(null);
}
};
const isFormValid =
formData.name.trim().length > 0 &&
isEmailValid === true &&
isPhoneValid !== false &&
formData.phone.trim().length > 0 &&
formData.department.length > 0 &&
formData.year.length > 0 &&
formData.agreeToRules;
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
if (!isEmailValid) return; if (!isFormValid) return;
setIsSubmitting(true); setIsSubmitting(true);
// TODO: Implement actual registration logic // TODO: Implement actual registration logic
await new Promise(resolve => setTimeout(resolve, 2000)); await new Promise(resolve => setTimeout(resolve, 2000));
setIsSubmitting(false); setIsSubmitting(false);
setIsRegistered(true);
}; };
return ( return (
<main className="min-h-screen bg-black pt-32 pb-20"> <main className="min-h-screen bg-black pt-32 pb-20">
{isRegistered ? (
<div className="max-w-xl mx-auto px-4 sm:px-6 lg:px-8 text-center pt-20">
<motion.div
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ duration: 0.5 }}
>
<div className="w-20 h-20 rounded-full bg-lime-400 flex items-center justify-center mx-auto mb-6">
<CheckCircle className="w-10 h-10 text-black" />
</div>
<h1 className="text-3xl sm:text-4xl font-black text-white mb-4">
Registration Successful!
</h1>
<p className="text-zinc-400 text-lg mb-8">
Welcome to ArtSplash 2026! You can now submit your artwork.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Link
href="/submit"
className="inline-flex items-center justify-center gap-2 bg-lime-400 text-black px-8 py-4 rounded-full font-bold hover:bg-lime-300 transition-all"
>
Submit Artwork
<ArrowRight className="w-5 h-5" />
</Link>
<Link
href="/"
className="inline-flex items-center justify-center gap-2 bg-transparent text-white px-8 py-4 rounded-full font-bold border border-zinc-700 hover:border-zinc-500 transition-all"
>
Back to Home
</Link>
</div>
</motion.div>
</div>
) : (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid lg:grid-cols-2 gap-12 items-start"> <div className="grid lg:grid-cols-2 gap-12 items-start">
{/* Left Column - Info */} {/* Left Column - Info */}
@@ -210,11 +270,31 @@ export default function RegisterPage() {
type="tel" type="tel"
required required
value={formData.phone} value={formData.phone}
onChange={(e) => setFormData({ ...formData, phone: e.target.value })} onChange={handlePhoneChange}
className="w-full bg-zinc-800 border border-zinc-700 rounded-xl pl-12 pr-4 py-3 text-white placeholder-zinc-500 focus:outline-none focus:border-lime-400 transition-colors" className={`w-full bg-zinc-800 border rounded-xl pl-12 pr-12 py-3 text-white placeholder-zinc-500 focus:outline-none transition-colors ${
isPhoneValid === null
? "border-zinc-700 focus:border-lime-400"
: isPhoneValid
? "border-lime-400"
: "border-red-500"
}`}
placeholder="+91 XXXXX XXXXX" placeholder="+91 XXXXX XXXXX"
/> />
{isPhoneValid !== null && (
<div className="absolute right-4 top-1/2 -translate-y-1/2">
{isPhoneValid ? (
<CheckCircle className="w-5 h-5 text-lime-400" />
) : (
<AlertCircle className="w-5 h-5 text-red-500" />
)}
</div>
)}
</div> </div>
{isPhoneValid === false && (
<p className="text-red-500 text-sm mt-2">
Please enter a valid Indian phone number
</p>
)}
</div> </div>
{/* Department & Year */} {/* Department & Year */}
@@ -282,7 +362,7 @@ export default function RegisterPage() {
{/* Submit Button */} {/* Submit Button */}
<button <button
type="submit" type="submit"
disabled={isSubmitting || !isEmailValid} disabled={isSubmitting || !isFormValid}
className="w-full flex items-center justify-center gap-2 bg-lime-400 text-black px-6 py-4 rounded-xl font-bold tracking-wider hover:bg-lime-300 transition-all disabled:opacity-50 disabled:cursor-not-allowed" className="w-full flex items-center justify-center gap-2 bg-lime-400 text-black px-6 py-4 rounded-xl font-bold tracking-wider hover:bg-lime-300 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
> >
{isSubmitting ? ( {isSubmitting ? (
@@ -309,6 +389,7 @@ export default function RegisterPage() {
</motion.div> </motion.div>
</div> </div>
</div> </div>
)}
</main> </main>
); );
} }

View File

@@ -4,7 +4,6 @@ import { useState, useRef } from "react";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { import {
Upload, Upload,
Image as ImageIcon,
FileText, FileText,
X, X,
CheckCircle, CheckCircle,
@@ -46,6 +45,7 @@ export default function SubmitPage() {
}); });
const [file, setFile] = useState<File | null>(null); const [file, setFile] = useState<File | null>(null);
const [preview, setPreview] = useState<string | null>(null); const [preview, setPreview] = useState<string | null>(null);
const [fileError, setFileError] = useState<string | null>(null);
const [isDragging, setIsDragging] = useState(false); const [isDragging, setIsDragging] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [submitSuccess, setSubmitSuccess] = useState(false); const [submitSuccess, setSubmitSuccess] = useState(false);
@@ -55,13 +55,15 @@ export default function SubmitPage() {
const validTypes = ["image/jpeg", "image/png", "application/pdf"]; const validTypes = ["image/jpeg", "image/png", "application/pdf"];
const maxSize = 10 * 1024 * 1024; // 10MB const maxSize = 10 * 1024 * 1024; // 10MB
setFileError(null);
if (!validTypes.includes(selectedFile.type)) { if (!validTypes.includes(selectedFile.type)) {
alert("Please upload a JPG, PNG, or PDF file."); setFileError("Invalid file type. Please upload a JPG, PNG, or PDF file.");
return; return;
} }
if (selectedFile.size > maxSize) { if (selectedFile.size > maxSize) {
alert("File size must be less than 10MB."); setFileError("File too large. Maximum size is 10MB.");
return; return;
} }
@@ -97,6 +99,7 @@ export default function SubmitPage() {
const removeFile = () => { const removeFile = () => {
setFile(null); setFile(null);
setPreview(null); setPreview(null);
setFileError(null);
if (fileInputRef.current) fileInputRef.current.value = ""; if (fileInputRef.current) fileInputRef.current.value = "";
}; };
@@ -125,6 +128,7 @@ export default function SubmitPage() {
setFormData({ title: "", description: "", category: "" }); setFormData({ title: "", description: "", category: "" });
setFile(null); setFile(null);
setPreview(null); setPreview(null);
setFileError(null);
}} }}
className="inline-flex items-center justify-center gap-2 bg-lime-400 text-black px-8 py-4 rounded-full font-bold hover:bg-lime-300 transition-all" className="inline-flex items-center justify-center gap-2 bg-lime-400 text-black px-8 py-4 rounded-full font-bold hover:bg-lime-300 transition-all"
> >
@@ -300,6 +304,12 @@ export default function SubmitPage() {
</button> </button>
)} )}
</div> </div>
{fileError && (
<div className="flex items-center gap-2 mt-3 text-red-500 text-sm">
<AlertCircle className="w-4 h-4 flex-shrink-0" />
{fileError}
</div>
)}
</motion.div> </motion.div>
{/* Title & Description */} {/* Title & Description */}

View File

@@ -8,23 +8,29 @@ function useGridSize() {
const [gridSize, setGridSize] = useState({ cols: 20, rows: 12 }); const [gridSize, setGridSize] = useState({ cols: 20, rows: 12 });
useEffect(() => { useEffect(() => {
let timeout: ReturnType<typeof setTimeout>;
const updateGridSize = () => { const updateGridSize = () => {
const width = window.innerWidth; const width = window.innerWidth;
if (width < 640) { if (width < 640) {
// Mobile
setGridSize({ cols: 8, rows: 10 }); setGridSize({ cols: 8, rows: 10 });
} else if (width < 1024) { } else if (width < 1024) {
// Tablet
setGridSize({ cols: 12, rows: 10 }); setGridSize({ cols: 12, rows: 10 });
} else { } else {
// Desktop
setGridSize({ cols: 20, rows: 12 }); setGridSize({ cols: 20, rows: 12 });
} }
}; };
const debouncedUpdate = () => {
clearTimeout(timeout);
timeout = setTimeout(updateGridSize, 150);
};
updateGridSize(); updateGridSize();
window.addEventListener("resize", updateGridSize); window.addEventListener("resize", debouncedUpdate);
return () => window.removeEventListener("resize", updateGridSize); return () => {
clearTimeout(timeout);
window.removeEventListener("resize", debouncedUpdate);
};
}, []); }, []);
return gridSize; return gridSize;
@@ -68,6 +74,7 @@ function TileGrid() {
return ( return (
<div <div
key={`grid-${cols}-${rows}`}
ref={gridRef} ref={gridRef}
className="absolute inset-0 grid z-0" className="absolute inset-0 grid z-0"
style={{ style={{
@@ -88,7 +95,6 @@ function TileGrid() {
className="w-full h-full relative" className="w-full h-full relative"
style={{ style={{
transformStyle: "preserve-3d", transformStyle: "preserve-3d",
willChange: "transform",
}} }}
> >
{/* Front - Black */} {/* Front - Black */}

View File

@@ -45,6 +45,11 @@ body {
animation-play-state: paused; animation-play-state: paused;
} }
.animate-marquee-slow {
animation: marquee 30s linear infinite;
width: max-content;
}
/* Smooth scrolling */ /* Smooth scrolling */
html { html {
scroll-behavior: smooth; scroll-behavior: smooth;