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";
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";
const stats = [

View File

@@ -14,6 +14,8 @@ import {
CheckCircle
} from "lucide-react";
import Link from "next/link";
const contactMethods = [
{
icon: Mail,
@@ -108,12 +110,12 @@ export default function ContactPage() {
<p className="text-zinc-400 text-lg mb-8">
Thank you for reaching out. We'll get back to you within 24-48 hours.
</p>
<a
<Link
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"
>
Back to Home
</a>
</Link>
</motion.div>
</div>
</main>
@@ -314,6 +316,7 @@ export default function ContactPage() {
onChange={handleChange}
required
rows={5}
maxLength={1000}
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"
/>

View File

@@ -2,10 +2,10 @@
import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import Link from "next/link";
import {
ChevronDown,
HelpCircle,
Users,
Vote,
Upload,
Award,
@@ -282,12 +282,12 @@ export default function FAQPage() {
<p className="text-zinc-400 mb-6">
Can't find what you're looking for? We're here to help!
</p>
<a
<Link
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"
>
Contact Us
</a>
</Link>
</div>
</motion.div>
</div>

View File

@@ -1,7 +1,8 @@
"use client";
import { useState, useMemo } from "react";
import { useState, useMemo, useEffect, useCallback } from "react";
import { motion, AnimatePresence } from "framer-motion";
import Image from "next/image";
import {
Search,
Filter,
@@ -133,9 +134,38 @@ export default function GalleryPage() {
const [searchQuery, setSearchQuery] = useState("");
const [viewMode, setViewMode] = useState<"grid" | "large">("grid");
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);
// 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
const dailySeed = useMemo(() => {
const today = new Date();
@@ -165,14 +195,16 @@ export default function GalleryPage() {
return seededShuffle(result, dailySeed);
}, [selectedCategory, searchQuery, dailySeed]);
const handleVote = (artworkId: string) => {
if (votedArtworks.has(artworkId)) {
// Already voted - in production, show a message
return;
}
setVotedArtworks(prev => new Set([...prev, artworkId]));
// TODO: Send vote to API
};
const handleVote = useCallback((artworkId: string) => {
if (votedArtworks.has(artworkId)) return;
setVotedArtworks(prev => {
const next = new Set([...prev, artworkId]);
try {
localStorage.setItem("artsplash-votes", JSON.stringify([...next]));
} catch { /* storage full - ignore */ }
return next;
});
}, [votedArtworks]);
return (
<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"
onClick={() => setSelectedArtwork(artwork)}
>
<img
<Image
src={artwork.imageUrl}
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">
<Eye className="w-8 h-8 text-white" />
@@ -400,11 +435,16 @@ export default function GalleryPage() {
onClick={(e) => e.stopPropagation()}
>
<div className="relative">
<img
src={selectedArtwork.imageUrl}
alt={selectedArtwork.title}
className="w-full aspect-video object-cover"
/>
<div className="relative aspect-video">
<Image
src={selectedArtwork.imageUrl}
alt={selectedArtwork.title}
fill
sizes="(max-width: 1024px) 100vw, 896px"
className="object-cover"
priority
/>
</div>
<button
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"

View File

@@ -4,7 +4,6 @@ import { useState } from "react";
import { motion } from "framer-motion";
import {
Mail,
Lock,
User,
Phone,
GraduationCap,
@@ -38,13 +37,20 @@ export default function RegisterPage() {
agreeToRules: false,
});
const [isSubmitting, setIsSubmitting] = useState(false);
const [isRegistered, setIsRegistered] = useState(false);
const [isEmailValid, setIsEmailValid] = useState<boolean | null>(null);
const [isPhoneValid, setIsPhoneValid] = useState<boolean | null>(null);
const validateEmail = (email: string) => {
const sjecEmailRegex = /^[a-zA-Z0-9._%+-]+@sjec\.ac\.in$/;
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 email = e.target.value;
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) => {
e.preventDefault();
if (!isEmailValid) return;
if (!isFormValid) return;
setIsSubmitting(true);
// TODO: Implement actual registration logic
await new Promise(resolve => setTimeout(resolve, 2000));
setIsSubmitting(false);
setIsRegistered(true);
};
return (
<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="grid lg:grid-cols-2 gap-12 items-start">
{/* Left Column - Info */}
@@ -210,11 +270,31 @@ export default function RegisterPage() {
type="tel"
required
value={formData.phone}
onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
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"
onChange={handlePhoneChange}
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"
/>
{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>
{isPhoneValid === false && (
<p className="text-red-500 text-sm mt-2">
Please enter a valid Indian phone number
</p>
)}
</div>
{/* Department & Year */}
@@ -282,7 +362,7 @@ export default function RegisterPage() {
{/* Submit Button */}
<button
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"
>
{isSubmitting ? (
@@ -309,6 +389,7 @@ export default function RegisterPage() {
</motion.div>
</div>
</div>
)}
</main>
);
}

View File

@@ -4,7 +4,6 @@ import { useState, useRef } from "react";
import { motion } from "framer-motion";
import {
Upload,
Image as ImageIcon,
FileText,
X,
CheckCircle,
@@ -46,6 +45,7 @@ export default function SubmitPage() {
});
const [file, setFile] = useState<File | null>(null);
const [preview, setPreview] = useState<string | null>(null);
const [fileError, setFileError] = useState<string | null>(null);
const [isDragging, setIsDragging] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [submitSuccess, setSubmitSuccess] = useState(false);
@@ -55,13 +55,15 @@ export default function SubmitPage() {
const validTypes = ["image/jpeg", "image/png", "application/pdf"];
const maxSize = 10 * 1024 * 1024; // 10MB
setFileError(null);
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;
}
if (selectedFile.size > maxSize) {
alert("File size must be less than 10MB.");
setFileError("File too large. Maximum size is 10MB.");
return;
}
@@ -97,6 +99,7 @@ export default function SubmitPage() {
const removeFile = () => {
setFile(null);
setPreview(null);
setFileError(null);
if (fileInputRef.current) fileInputRef.current.value = "";
};
@@ -125,6 +128,7 @@ export default function SubmitPage() {
setFormData({ title: "", description: "", category: "" });
setFile(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"
>
@@ -300,6 +304,12 @@ export default function SubmitPage() {
</button>
)}
</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>
{/* Title & Description */}

View File

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

View File

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