repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
Projectflow
stars
latest
clone command
git clone gitlawb://did:key:z6Mkfh4Y...QBEi/projectflowgit clone gitlawb://did:key:z6Mkfh4Y.../projectflowb3cded1async from playground1d ago| #1 | import { useState } from "react"; |
| #2 | import { motion } from "framer-motion"; |
| #3 | import { Zap, ArrowRight, Eye, EyeOff } from "lucide-react"; |
| #4 | import { useStore } from "../../application/stores"; |
| #5 | |
| #6 | export function LoginPage() { |
| #7 | const { login } = useStore(); |
| #8 | const [email, setEmail] = useState("alex@projectflow.dev"); |
| #9 | const [password, setPassword] = useState("password"); |
| #10 | const [showPassword, setShowPassword] = useState(false); |
| #11 | const [error, setError] = useState(""); |
| #12 | const [loading, setLoading] = useState(false); |
| #13 | const [view, setView] = useState<"login" | "register">("login"); |
| #14 | |
| #15 | const handleSubmit = async (e: React.FormEvent) => { |
| #16 | e.preventDefault(); |
| #17 | setError(""); |
| #18 | setLoading(true); |
| #19 | await new Promise((r) => setTimeout(r, 500)); |
| #20 | const success = login(email, password); |
| #21 | if (!success) { |
| #22 | setError("Invalid credentials"); |
| #23 | } |
| #24 | setLoading(false); |
| #25 | }; |
| #26 | |
| #27 | const handleRegister = async (e: React.FormEvent) => { |
| #28 | e.preventDefault(); |
| #29 | setLoading(true); |
| #30 | await new Promise((r) => setTimeout(r, 500)); |
| #31 | login(email, password); |
| #32 | setLoading(false); |
| #33 | }; |
| #34 | |
| #35 | return ( |
| #36 | <div className="min-h-screen flex items-center justify-center bg-surface p-4"> |
| #37 | <div className="fixed inset-0 overflow-hidden pointer-events-none"> |
| #38 | <div className="absolute -top-40 -right-40 w-80 h-80 bg-accent/10 rounded-full blur-3xl" /> |
| #39 | <div className="absolute -bottom-40 -left-40 w-80 h-80 bg-violet-500/10 rounded-full blur-3xl" /> |
| #40 | </div> |
| #41 | |
| #42 | <motion.div |
| #43 | initial={{ opacity: 0, y: 20 }} |
| #44 | animate={{ opacity: 1, y: 0 }} |
| #45 | transition={{ duration: 0.4 }} |
| #46 | className="relative w-full max-w-sm" |
| #47 | > |
| #48 | <div className="text-center mb-8"> |
| #49 | <div className="inline-flex items-center justify-center w-12 h-12 rounded-2xl bg-accent mb-4"> |
| #50 | <Zap size={24} className="text-white" /> |
| #51 | </div> |
| #52 | <h1 className="text-xl font-semibold text-zinc-100"> |
| #53 | {view === "login" ? "Welcome back" : "Create account"} |
| #54 | </h1> |
| #55 | <p className="text-sm text-zinc-500 mt-1"> |
| #56 | {view === "login" |
| #57 | ? "Sign in to your ProjectFlow account" |
| #58 | : "Get started with ProjectFlow"} |
| #59 | </p> |
| #60 | </div> |
| #61 | |
| #62 | <div className="card"> |
| #63 | <form |
| #64 | onSubmit={view === "login" ? handleSubmit : handleRegister} |
| #65 | className="space-y-4" |
| #66 | > |
| #67 | {view === "register" && ( |
| #68 | <div> |
| #69 | <label className="block text-2xs font-medium text-zinc-400 uppercase tracking-wider mb-1.5"> |
| #70 | Full Name |
| #71 | </label> |
| #72 | <input |
| #73 | type="text" |
| #74 | placeholder="John Doe" |
| #75 | className="input" |
| #76 | /> |
| #77 | </div> |
| #78 | )} |
| #79 | |
| #80 | <div> |
| #81 | <label className="block text-2xs font-medium text-zinc-400 uppercase tracking-wider mb-1.5"> |
| #82 | |
| #83 | </label> |
| #84 | <input |
| #85 | type="email" |
| #86 | value={email} |
| #87 | onChange={(e) => setEmail(e.target.value)} |
| #88 | placeholder="you@example.com" |
| #89 | className="input" |
| #90 | required |
| #91 | /> |
| #92 | </div> |
| #93 | |
| #94 | <div> |
| #95 | <label className="block text-2xs font-medium text-zinc-400 uppercase tracking-wider mb-1.5"> |
| #96 | Password |
| #97 | </label> |
| #98 | <div className="relative"> |
| #99 | <input |
| #100 | type={showPassword ? "text" : "password"} |
| #101 | value={password} |
| #102 | onChange={(e) => setPassword(e.target.value)} |
| #103 | placeholder="••••••••" |
| #104 | className="input pr-10" |
| #105 | required |
| #106 | /> |
| #107 | <button |
| #108 | type="button" |
| #109 | onClick={() => setShowPassword(!showPassword)} |
| #110 | className="absolute right-3 top-1/2 -translate-y-1/2 text-zinc-500 hover:text-zinc-300" |
| #111 | > |
| #112 | {showPassword ? <EyeOff size={16} /> : <Eye size={16} />} |
| #113 | </button> |
| #114 | </div> |
| #115 | </div> |
| #116 | |
| #117 | {error && ( |
| #118 | <p className="text-sm text-red-400">{error}</p> |
| #119 | )} |
| #120 | |
| #121 | <button |
| #122 | type="submit" |
| #123 | disabled={loading} |
| #124 | className="btn-primary w-full" |
| #125 | > |
| #126 | {loading ? ( |
| #127 | <div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" /> |
| #128 | ) : ( |
| #129 | <> |
| #130 | {view === "login" ? "Sign in" : "Create account"} |
| #131 | <ArrowRight size={16} /> |
| #132 | </> |
| #133 | )} |
| #134 | </button> |
| #135 | </form> |
| #136 | |
| #137 | <div className="mt-4 pt-4 border-t border-border text-center"> |
| #138 | {view === "login" ? ( |
| #139 | <p className="text-sm text-zinc-500"> |
| #140 | Don't have an account?{" "} |
| #141 | <button |
| #142 | onClick={() => setView("register")} |
| #143 | className="text-accent hover:text-accent-hover" |
| #144 | > |
| #145 | Sign up |
| #146 | </button> |
| #147 | </p> |
| #148 | ) : ( |
| #149 | <p className="text-sm text-zinc-500"> |
| #150 | Already have an account?{" "} |
| #151 | <button |
| #152 | onClick={() => setView("login")} |
| #153 | className="text-accent hover:text-accent-hover" |
| #154 | > |
| #155 | Sign in |
| #156 | </button> |
| #157 | </p> |
| #158 | )} |
| #159 | </div> |
| #160 | </div> |
| #161 | |
| #162 | <div className="mt-4 text-center"> |
| #163 | <p className="text-2xs text-zinc-600"> |
| #164 | Demo: Use any email to sign in |
| #165 | </p> |
| #166 | </div> |
| #167 | </motion.div> |
| #168 | </div> |
| #169 | ); |
| #170 | } |
| #171 |