333 lines
11 KiB
TypeScript
333 lines
11 KiB
TypeScript
import { ChevronRight, LogOut, User } from "lucide-react";
|
|
import { useAuthStore } from "../../stores/authStore";
|
|
import { useNavigate } from "react-router-dom";
|
|
import {
|
|
Avatar,
|
|
AvatarFallback,
|
|
AvatarImage,
|
|
} from "../../components/ui/avatar";
|
|
|
|
const DOTS = [
|
|
{ size: 10, color: "#f97316", top: "6%", left: "4%", delay: "0s" },
|
|
{ size: 7, color: "#a855f7", top: "28%", left: "2%", delay: "1.2s" },
|
|
{ size: 9, color: "#22c55e", top: "62%", left: "3%", delay: "0.6s" },
|
|
{ size: 12, color: "#3b82f6", top: "10%", right: "4%", delay: "1.8s" },
|
|
{ size: 7, color: "#f43f5e", top: "48%", right: "2%", delay: "0.9s" },
|
|
{ size: 9, color: "#eab308", top: "76%", right: "5%", delay: "0.4s" },
|
|
];
|
|
|
|
const STYLES = `
|
|
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;800;900&family=Nunito+Sans:wght@400;600;700&display=swap');
|
|
|
|
.pf-screen {
|
|
min-height: 100vh;
|
|
background: #fffbf4;
|
|
font-family: 'Nunito', sans-serif;
|
|
position: relative;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
.pf-blob { position: fixed; pointer-events: none; z-index: 0; filter: blur(48px); opacity: 0.35; }
|
|
.pf-blob-1 { width:240px;height:240px;background:#fde68a;top:-80px;left:-80px;border-radius:60% 40% 70% 30%/50% 60% 40% 50%;animation:pfWobble1 14s ease-in-out infinite; }
|
|
.pf-blob-2 { width:190px;height:190px;background:#a5f3c0;bottom:-50px;left:6%;border-radius:40% 60% 30% 70%/60% 40% 60% 40%;animation:pfWobble2 16s ease-in-out infinite; }
|
|
.pf-blob-3 { width:210px;height:210px;background:#fbcfe8;top:15%;right:-60px;border-radius:70% 30% 50% 50%/40% 60% 40% 60%;animation:pfWobble1 18s ease-in-out infinite reverse; }
|
|
.pf-blob-4 { width:150px;height:150px;background:#bfdbfe;bottom:12%;right:2%;border-radius:50% 50% 30% 70%/60% 40% 60% 40%;animation:pfWobble2 12s ease-in-out infinite; }
|
|
|
|
@keyframes pfWobble1 {
|
|
0%,100%{border-radius:60% 40% 70% 30%/50% 60% 40% 50%;transform:translate(0,0) rotate(0deg);}
|
|
50%{border-radius:40% 60% 30% 70%/60% 40% 60% 40%;transform:translate(12px,16px) rotate(8deg);}
|
|
}
|
|
@keyframes pfWobble2 {
|
|
0%,100%{border-radius:40% 60% 30% 70%/60% 40% 60% 40%;transform:translate(0,0) rotate(0deg);}
|
|
50%{border-radius:60% 40% 70% 30%/40% 60% 40% 60%;transform:translate(-10px,12px) rotate(-6deg);}
|
|
}
|
|
|
|
.pf-dot { position:fixed;border-radius:50%;pointer-events:none;z-index:0;opacity:0.3;animation:pfFloat 7s ease-in-out infinite; }
|
|
@keyframes pfFloat {
|
|
0%,100%{transform:translateY(0) rotate(0deg);}
|
|
50%{transform:translateY(-12px) rotate(180deg);}
|
|
}
|
|
|
|
.pf-inner {
|
|
position: relative; z-index: 1;
|
|
max-width: 520px; margin: 0 auto;
|
|
padding: 2rem 1.25rem 4rem;
|
|
display: flex; flex-direction: column; gap: 1.5rem;
|
|
}
|
|
|
|
@keyframes pfPopIn {
|
|
from { opacity:0; transform: scale(0.92) translateY(12px); }
|
|
to { opacity:1; transform: scale(1) translateY(0); }
|
|
}
|
|
.pf-anim { animation: pfPopIn 0.4s cubic-bezier(0.34,1.56,0.64,1) both; }
|
|
.pf-anim-1 { animation-delay: 0.05s; }
|
|
.pf-anim-2 { animation-delay: 0.1s; }
|
|
.pf-anim-3 { animation-delay: 0.15s; }
|
|
.pf-anim-4 { animation-delay: 0.2s; }
|
|
.pf-anim-5 { animation-delay: 0.25s; }
|
|
|
|
/* Page title */
|
|
.pf-page-title {
|
|
font-size: 1.8rem; font-weight: 900; color: #1e1b4b;
|
|
letter-spacing: -0.02em; text-align: center;
|
|
animation: pfPopIn 0.4s cubic-bezier(0.34,1.56,0.64,1) both;
|
|
}
|
|
|
|
/* Avatar hero card */
|
|
.pf-hero {
|
|
background: white; border: 2.5px solid #f3f4f6; border-radius: 24px;
|
|
padding: 1.5rem; box-shadow: 0 6px 20px rgba(0,0,0,0.05);
|
|
display: flex; align-items: center; gap: 1rem;
|
|
}
|
|
.pf-avatar-wrap {
|
|
width: 64px; height: 64px; flex-shrink: 0;
|
|
border-radius: 50%;
|
|
background: linear-gradient(135deg, #a855f7, #7c3aed);
|
|
display: flex; align-items: center; justify-content: center;
|
|
box-shadow: 0 4px 0 #5b21b655;
|
|
}
|
|
.pf-user-name {
|
|
font-size: 1.15rem; font-weight: 900; color: #1e1b4b; line-height: 1.2;
|
|
}
|
|
.pf-user-email {
|
|
font-family: 'Nunito Sans', sans-serif;
|
|
font-size: 0.8rem; font-weight: 600; color: #9ca3af; margin-top: 0.2rem;
|
|
}
|
|
.pf-user-badge {
|
|
display: inline-flex; align-items: center; gap: 0.3rem;
|
|
background: #f3e8ff; border: 2px solid #e9d5ff; border-radius: 100px;
|
|
padding: 0.2rem 0.6rem; margin-top: 0.35rem;
|
|
font-size: 0.62rem; font-weight: 800; letter-spacing: 0.1em;
|
|
text-transform: uppercase; color: #9333ea;
|
|
width: fit-content;
|
|
}
|
|
|
|
/* Section */
|
|
.pf-section { display: flex; flex-direction: column; gap: 0.5rem; }
|
|
.pf-section-label {
|
|
font-size: 0.62rem; font-weight: 800; letter-spacing: 0.18em;
|
|
text-transform: uppercase; color: #9ca3af; padding: 0 0.25rem;
|
|
}
|
|
|
|
/* Settings group card */
|
|
.pf-group {
|
|
background: white; border: 2.5px solid #f3f4f6; border-radius: 20px;
|
|
overflow: hidden; box-shadow: 0 4px 14px rgba(0,0,0,0.04);
|
|
}
|
|
.pf-row {
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
padding: 0.9rem 1.1rem; gap: 0.75rem; cursor: pointer;
|
|
background: white; border: none; width: 100%; text-align: left;
|
|
transition: background 0.15s ease;
|
|
border-bottom: 2px solid #f9fafb;
|
|
opacity: 0.5;
|
|
}
|
|
.pf-row:last-child { border-bottom: none; }
|
|
.pf-row:hover { background: #fafaf9; }
|
|
.pf-row:active { background: #f5f3ef; }
|
|
|
|
.pf-row-left { display: flex; align-items: center; gap: 0.75rem; }
|
|
.pf-row-icon {
|
|
width: 34px; height: 34px; border-radius: 10px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
flex-shrink: 0;
|
|
}
|
|
.pf-row-label {
|
|
font-size: 0.9rem; font-weight: 800; color: #1e1b4b;
|
|
}
|
|
.pf-row-sub {
|
|
font-family: 'Nunito Sans', sans-serif;
|
|
font-size: 0.72rem; font-weight: 600; color: #9ca3af;
|
|
}
|
|
.pf-chevron { color: #d1d5db; flex-shrink: 0; }
|
|
|
|
/* Sign out button */
|
|
.pf-signout-btn {
|
|
width: 100%;
|
|
background: #f97316; color: white; border: none;
|
|
border-radius: 100px; padding: 1rem;
|
|
font-family: 'Nunito', sans-serif; font-size: 0.95rem; font-weight: 800;
|
|
cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 0.5rem;
|
|
box-shadow: 0 6px 0 #c2560e, 0 8px 20px rgba(249,115,22,0.25);
|
|
transition: transform 0.1s ease, box-shadow 0.1s ease;
|
|
}
|
|
.pf-signout-btn:hover { transform:translateY(-2px); box-shadow:0 8px 0 #c2560e,0 12px 24px rgba(249,115,22,0.3); }
|
|
.pf-signout-btn:active { transform:translateY(3px); box-shadow:0 3px 0 #c2560e; }
|
|
`;
|
|
|
|
type RowDef = {
|
|
icon: string;
|
|
iconBg: string;
|
|
label: string;
|
|
sub?: string;
|
|
onClick?: () => void;
|
|
};
|
|
|
|
const SettingsGroup = ({ rows }: { rows: RowDef[] }) => (
|
|
<div className="pf-group">
|
|
{rows.map((row, i) => (
|
|
<button key={i} className="pf-row" onClick={row.onClick}>
|
|
<div className="pf-row-left">
|
|
<div className="pf-row-icon" style={{ background: row.iconBg }}>
|
|
<span style={{ fontSize: "1rem" }}>{row.icon}</span>
|
|
</div>
|
|
<div>
|
|
<p className="pf-row-label">{row.label}</p>
|
|
{row.sub && <p className="pf-row-sub">{row.sub}</p>}
|
|
</div>
|
|
</div>
|
|
<ChevronRight size={17} className="pf-chevron" />
|
|
</button>
|
|
))}
|
|
</div>
|
|
);
|
|
|
|
export const Profile = () => {
|
|
const user = useAuthStore((state) => state.user);
|
|
const navigate = useNavigate();
|
|
|
|
const handleLogout = () => {
|
|
useAuthStore.getState().logout();
|
|
navigate("/login");
|
|
};
|
|
|
|
const ACCOUNT_ROWS: RowDef[] = [
|
|
{
|
|
icon: "👤",
|
|
iconBg: "#f3e8ff",
|
|
label: "Account details",
|
|
sub: "Name, email, password",
|
|
},
|
|
{
|
|
icon: "🎟️",
|
|
iconBg: "#fff7ed",
|
|
label: "Redeem a code",
|
|
sub: "Enter your access code",
|
|
},
|
|
{
|
|
icon: "💳",
|
|
iconBg: "#f0fdf4",
|
|
label: "Manage subscription",
|
|
sub: "Plans & billing",
|
|
},
|
|
{
|
|
icon: "⚙️",
|
|
iconBg: "#f8fafc",
|
|
label: "Preferences",
|
|
sub: "Notifications & display",
|
|
},
|
|
];
|
|
|
|
const LEGAL_ROWS: RowDef[] = [
|
|
{ icon: "📄", iconBg: "#eff6ff", label: "Terms of Use" },
|
|
{ icon: "🔒", iconBg: "#fff1f2", label: "Privacy Policy" },
|
|
];
|
|
|
|
const SUPPORT_ROWS: RowDef[] = [
|
|
{
|
|
icon: "💬",
|
|
iconBg: "#f0fdf4",
|
|
label: "Contact Us",
|
|
sub: "We usually respond within 24h",
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div className="pf-screen pb-12">
|
|
<style>{STYLES}</style>
|
|
|
|
{/* Blobs */}
|
|
<div className="pf-blob pf-blob-1" />
|
|
<div className="pf-blob pf-blob-2" />
|
|
<div className="pf-blob pf-blob-3" />
|
|
<div className="pf-blob pf-blob-4" />
|
|
|
|
{/* Dots */}
|
|
{DOTS.map((d, i) => (
|
|
<div
|
|
key={i}
|
|
className="pf-dot"
|
|
style={
|
|
{
|
|
width: d.size,
|
|
height: d.size,
|
|
background: d.color,
|
|
top: d.top,
|
|
left: d.left,
|
|
right: d.right,
|
|
animationDelay: d.delay,
|
|
animationDuration: `${5 + i * 0.5}s`,
|
|
} as React.CSSProperties
|
|
}
|
|
/>
|
|
))}
|
|
|
|
<div className="pf-inner">
|
|
<h1 className="pf-page-title">Profile</h1>
|
|
|
|
{/* Hero card */}
|
|
<div className="pf-hero pf-anim pf-anim-1">
|
|
<Avatar style={{ width: 64, height: 64, flexShrink: 0 }}>
|
|
<AvatarImage src={user?.avatar_url} />
|
|
<AvatarFallback
|
|
style={{
|
|
width: 64,
|
|
height: 64,
|
|
borderRadius: "50%",
|
|
background: "linear-gradient(135deg,#a855f7,#7c3aed)",
|
|
color: "white",
|
|
fontWeight: 900,
|
|
fontSize: "1.5rem",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
boxShadow: "0 4px 0 #5b21b655",
|
|
}}
|
|
>
|
|
{user?.name?.slice(0, 1).toUpperCase()}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div>
|
|
<p className="pf-user-name">{user?.name}</p>
|
|
<p className="pf-user-email">{user?.email}</p>
|
|
<div className="pf-user-badge">
|
|
<User size={9} />
|
|
{user?.role === "STUDENT"
|
|
? "Student"
|
|
: user?.role === "ADMIN"
|
|
? "Admin"
|
|
: "Teacher"}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Account */}
|
|
<section className="pf-section pf-anim pf-anim-2">
|
|
<p className="pf-section-label">Account</p>
|
|
<SettingsGroup rows={ACCOUNT_ROWS} />
|
|
</section>
|
|
|
|
{/* Legal */}
|
|
<section className="pf-section pf-anim pf-anim-3">
|
|
<p className="pf-section-label">Legal</p>
|
|
<SettingsGroup rows={LEGAL_ROWS} />
|
|
</section>
|
|
|
|
{/* Support */}
|
|
<section className="pf-section pf-anim pf-anim-4">
|
|
<p className="pf-section-label">Support</p>
|
|
<SettingsGroup rows={SUPPORT_ROWS} />
|
|
</section>
|
|
|
|
{/* Sign out */}
|
|
<button
|
|
className="pf-signout-btn pf-anim pf-anim-5"
|
|
onClick={handleLogout}
|
|
>
|
|
<LogOut size={18} /> Sign Out
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|