0xmetaschool/ Castle.ai

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.

app
api
home
learn
play
puzzle
globals.css
layout.js
page.js
page.module.css
components
lib
.gitignore
components.json
jsconfig.json
LICENSE
next.config.mjs
package.json
postcss.config.js
README.md
tailwind.config.js
appseparatorpage.js
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
Downloads

0

Version

1.0.0

License

MIT

Platform

OpenAI GPT-4

Contributors 3
b0ney-1
aveek-goyal
fareeha25
Open in

Last updated 3 months ago

We are a open source platform and encourage community to contribute to these templates!