Castle.ai helps you in your chess journey with AI-powered learning and gameplay. Experience chess like never before by playing against AI personalities that mirror grandmasters' styles. Whether you're a beginner looking to learn openings or an advanced player solving complex puzzles, Castle.ai provides a personalized path to chess mastery. Built with Next.js and AI technology, this open-source template helps developers create their own AI-powered Chess game.
1"use client";
2import React, { useState, useEffect } from "react";
3import Image from "next/image";
4import { useRouter } from "next/navigation";
5import { ArrowLeft, AlertCircle } from "lucide-react";
6import { Alert, AlertDescription } from "@/components/ui/alert";
7import { Input } from "@/components/ui/input";
8import { Button } from "@/components/ui/button";
9import { AnimatePresence, motion } from "framer-motion";
10
11const TypingAnimation = ({ text, className }) => {
12 const [displayText, setDisplayText] = useState("");
13 const [currentIndex, setCurrentIndex] = useState(0);
14
15 useEffect(() => {
16 if (currentIndex < text.length) {
17 const timeout = setTimeout(() => {
18 setDisplayText((prev) => prev + text[currentIndex]);
19 setCurrentIndex((prev) => prev + 1);
20 }, 100);
21 return () => clearTimeout(timeout);
22 }
23 }, [currentIndex, text]);
24
25 return (
26 <span className={`font-light tracking-wide ${className}`}>
27 {displayText}
28 </span>
29 );
30};
31
32export default function Home() {
33 const [view, setView] = useState("initial");
34 const [email, setEmail] = useState("");
35 const [password, setPassword] = useState("");
36 const [isLoading, setIsLoading] = useState(false);
37 const [alertInfo, setAlertInfo] = useState({
38 show: false,
39 message: "",
40 title: "",
41 variant: "default",
42 });
43 const router = useRouter();
44
45 useEffect(() => {
46 localStorage.removeItem("jwtToken");
47 }, []);
48
49 const handleViewChange = (newView) => {
50 setView(newView);
51 };
52
53 const handleDemo = async () => {
54 setIsLoading(true);
55 try {
56 const response = await fetch("/api/auth/login", {
57 method: "POST",
58 headers: {
59 "Content-Type": "application/json",
60 },
61 body: JSON.stringify({
62 email: "test@metaschool.so",
63 password: "test",
64 }),
65 });
66 const data = await response.json();
67
68 if (response.ok) {
69 localStorage.setItem("jwtToken", data.token);
70 localStorage.setItem("userId", data.userId);
71 setAlertInfo({
72 show: true,
73 title: "Success",
74 message: "Demo login successful. Setting up your board..",
75 variant: "default",
76 });
77 setTimeout(() => {
78 router.push(`/home?id=${data.userId}`);
79 }, 2000);
80 } else {
81 throw new Error(data.message || "Demo login failed");
82 }
83 } catch (error) {
84 setAlertInfo({
85 show: true,
86 title: "Error",
87 message: error.message,
88 variant: "destructive",
89 });
90 }
91 };
92
93 const handleSubmit = async (e, type) => {
94 e.preventDefault();
95 setIsLoading(true);
96
97 try {
98 const response = await fetch(`/api/auth/${type}`, {
99 method: "POST",
100 headers: {
101 "Content-Type": "application/json",
102 },
103 body: JSON.stringify({ email, password }),
104 });
105 const data = await response.json();
106
107 if (response.ok) {
108 localStorage.setItem("jwtToken", data.token);
109 localStorage.setItem("userId", data.userId);
110 setAlertInfo({
111 show: true,
112 title: "Success",
113 message: `${
114 type === "login" ? "Login" : "Registration"
115 } successful, Setting up your board..`,
116 variant: "default",
117 });
118 setTimeout(() => {
119 router.push(`/home?id=${data.userId}`);
120 }, 2000);
121 } else {
122 throw new Error(data.message || `${type} failed`);
123 }
124 } catch (error) {
125 setAlertInfo({
126 show: true,
127 title: "Error",
128 message: error.message,
129 variant: "destructive",
130 });
131 setIsLoading(false);
132 }
133 };
134
135 const renderContent = () => {
136 switch (view) {
137 case "initial":
138 return (
139 <motion.div
140 key="initial"
141 initial={{ opacity: 0, x: 50 }}
142 animate={{ opacity: 1, x: 0 }}
143 exit={{ opacity: 0, x: -50 }}
144 transition={{ duration: 0.3 }}
145 className="flex flex-col items-center space-y-8"
146 >
147 <h1 className="text-5xl font-bold font-sans">
148 Welcome to Castle.ai
149 </h1>
150 <TypingAnimation
151 text="Your Move, Powered by AI"
152 className="text-xl text-neutral-600 dark:text-neutral-300 font-light tracking-wider"
153 />
154 <div className="flex space-x-12">
155 <Button
156 variant="default"
157 onClick={() => handleViewChange("login")}
158 className="text-2xl py-6 px-9"
159 >
160 Login
161 </Button>
162 <Button
163 variant="outline"
164 onClick={() => handleViewChange("register")}
165 className="text-2xl py-6 px-9"
166 >
167 Register
168 </Button>
169 <Button
170 variant="outline"
171 onClick={() => handleDemo()}
172 className="text-2xl py-6 px-9"
173 >
174 Try the Demo
175 </Button>
176 </div>
177 </motion.div>
178 );
179 case "login":
180 case "register":
181 return (
182 <motion.div
183 key={view}
184 initial={{ opacity: 0, x: 50 }}
185 animate={{ opacity: 1, x: 0 }}
186 exit={{ opacity: 0, x: -50 }}
187 transition={{ duration: 0.3 }}
188 className="flex flex-col items-center space-y-6 w-full"
189 >
190 <Button
191 variant="ghost"
192 className="self-start"
193 onClick={() => handleViewChange("initial")}
194 >
195 <ArrowLeft className="w-8 h-8" />
196 </Button>
197 <form
198 onSubmit={(e) => handleSubmit(e, view)}
199 className="space-y-6 w-full"
200 >
201 <Input
202 type="text"
203 value={email}
204 onChange={(e) => setEmail(e.target.value)}
205 placeholder="Email"
206 required
207 className="text-xl py-3 px-4"
208 />
209 <Input
210 type="password"
211 value={password}
212 onChange={(e) => setPassword(e.target.value)}
213 placeholder="Password"
214 required
215 className="text-xl py-3 px-4"
216 />
217 <Button
218 type="submit"
219 className="w-full text-2xl py-3"
220 disabled={isLoading}
221 >
222 {isLoading
223 ? view === "login"
224 ? "Logging in..."
225 : "Registering..."
226 : view === "login"
227 ? "Log In"
228 : "Register"}
229 </Button>
230 </form>
231 </motion.div>
232 );
233 default:
234 return null;
235 }
236 };
237
238 return (
239 <div className="flex min-h-screen items-center justify-center">
240 <div className="flex flex-row items-center justify-center space-x-12 w-full max-w-6xl">
241 <motion.div
242 className={`${
243 isLoading
244 ? "fixed inset-0 flex items-center justify-center"
245 : "w-1/2 flex justify-center"
246 }`}
247 animate={{
248 width: isLoading ? "100%" : "50%",
249 scale: isLoading ? 1.2 : 1,
250 }}
251 transition={{ duration: 0.5, ease: "easeInOut" }}
252 >
253 <Image
254 src="/main.gif"
255 alt="Logo"
256 width={600}
257 height={600}
258 className="object-contain"
259 priority
260 unoptimized
261 />
262 </motion.div>
263 {!isLoading && (
264 <motion.div
265 className="w-1/2 h-[600px] flex items-center justify-center"
266 animate={{ opacity: 1 }}
267 exit={{ opacity: 0 }}
268 transition={{ duration: 0.3 }}
269 >
270 <AnimatePresence mode="wait" initial={false}>
271 {renderContent()}
272 </AnimatePresence>
273 </motion.div>
274 )}
275 </div>
276 <AnimatePresence>
277 {alertInfo.show && (
278 <motion.div
279 initial={{ opacity: 0, y: -20 }}
280 animate={{ opacity: 1, y: 0 }}
281 exit={{ opacity: 0, y: -20 }}
282 className="fixed top-6 right-6"
283 >
284 <Alert variant={alertInfo.variant} className="w-96 p-4 shadow-lg">
285 <div className="flex items-center space-x-3">
286 <AlertCircle className="h-6 w-6 flex-shrink-0" />
287 <AlertDescription className="text-lg">
288 {alertInfo.message}
289 </AlertDescription>
290 </div>
291 </Alert>
292 </motion.div>
293 )}
294 </AnimatePresence>
295 </div>
296 );
297}
298
Last updated 3 months ago