Skip to content

Commit 177f42e

Browse files
committed
ADD - admin role and false email login
1 parent b056fc0 commit 177f42e

12 files changed

Lines changed: 422 additions & 174 deletions

File tree

client/src/App.jsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useState } from "react";
22
import { AuthContext } from "./auth/AuthContext.jsx";
33
import { FaqPage } from "./components/FaqPage.jsx";
44
import { Hero } from "./components/Hero.jsx";
5+
import { LoginPage } from "./components/LoginPage.jsx";
56
import { MainPage } from "./components/MainPage.jsx";
67
import { ProjectsPage } from "./components/ProjectsPage.jsx";
78
import { ShopPage } from "./components/ShopPage.jsx";
@@ -44,7 +45,7 @@ export default function App() {
4445

4546
if ((PROTECTED.has(pathname) || isAdminPath) && !auth.user) {
4647
const returnTo = `${pathname}${window.location.search}${window.location.hash}`;
47-
window.location.replace(`/api/auth/hackclub/login?returnTo=${encodeURIComponent(returnTo)}`);
48+
window.location.replace(`/login?returnTo=${encodeURIComponent(returnTo)}`);
4849
return null;
4950
}
5051

@@ -58,6 +59,11 @@ export default function App() {
5859
return null;
5960
}
6061

62+
if (pathname === "/login" && auth.user) {
63+
window.location.replace("/main");
64+
return null;
65+
}
66+
6167
const contextValue = {
6268
user: auth.user,
6369
status: auth.status,
@@ -69,6 +75,9 @@ export default function App() {
6975
case "/main":
7076
page = <MainPage />;
7177
break;
78+
case "/login":
79+
page = <LoginPage />;
80+
break;
7281
case "/test":
7382
page = isLocalhost ? <TestPage /> : <AdminPage />;
7483
break;

client/src/components/AdminPage.css

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,25 @@
3131
font-style: italic;
3232
}
3333

34+
.admin-back-link {
35+
display: inline-flex;
36+
align-items: center;
37+
gap: 0.4rem;
38+
padding: 0.55rem 0.9rem;
39+
border: 2px solid #4ba2ff;
40+
border-radius: 999px;
41+
background: rgb(9 18 42 / 82%);
42+
color: #98e8ff;
43+
font-family: Georgia, serif;
44+
font-weight: 700;
45+
text-decoration: none;
46+
}
47+
48+
.admin-back-link:hover {
49+
border-color: #a765ff;
50+
background: rgb(21 34 70 / 95%);
51+
}
52+
3453
.admin-links {
3554
display: flex;
3655
flex-direction: column;

client/src/components/AdminPage.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ export function AdminPage() {
100100
return (
101101
<main className="admin-page" aria-label="Admin page">
102102
<section className="admin-content">
103+
<a className="admin-back-link" href="/main">
104+
← Back to platform
105+
</a>
103106
<h1>Admin Panel</h1>
104107

105108
<div className="admin-links">

client/src/components/Hero.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export function Hero() {
133133
</a>
134134
</div>
135135

136-
<a className="section-two__join" href="/api/auth/hackclub/login" aria-label="Sign in with Hack Club to join Stack">
136+
<a className="section-two__join" href="/login" aria-label="Sign up or log in with email to join Stack">
137137
<img src={joinButton} width={580} height={300} alt="Join!" />
138138
</a>
139139

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
.login-page {
2+
min-height: 100vh;
3+
display: grid;
4+
place-items: center;
5+
padding: 24px;
6+
background: linear-gradient(145deg, #73a5ca 0%, #bfdfff 55%, #fff8d8 100%);
7+
}
8+
9+
.login-card {
10+
width: min(460px, 100%);
11+
padding: 28px;
12+
border: 4px solid #162456;
13+
border-radius: 24px;
14+
background: #fff8d8;
15+
color: #162456;
16+
box-shadow: 8px 8px 0 #162456;
17+
}
18+
19+
.login-card__back {
20+
color: #162456;
21+
font-weight: 700;
22+
text-decoration: none;
23+
}
24+
25+
.login-card h1 {
26+
margin: 14px 0 8px;
27+
font-size: clamp(2rem, 6vw, 3.5rem);
28+
}
29+
30+
.login-card p {
31+
margin: 0 0 18px;
32+
}
33+
34+
.login-card form {
35+
display: grid;
36+
gap: 14px;
37+
}
38+
39+
.login-card label {
40+
display: grid;
41+
gap: 6px;
42+
font-weight: 700;
43+
}
44+
45+
.login-card input {
46+
border: 3px solid #162456;
47+
border-radius: 12px;
48+
padding: 12px 14px;
49+
background: #fff;
50+
color: #162456;
51+
font: inherit;
52+
}
53+
54+
.login-card button {
55+
border: 3px solid #162456;
56+
border-radius: 999px;
57+
padding: 12px 18px;
58+
background: #ffd84d;
59+
color: #162456;
60+
font: inherit;
61+
font-weight: 800;
62+
cursor: pointer;
63+
box-shadow: 4px 4px 0 #162456;
64+
}
65+
66+
.login-card button:disabled {
67+
cursor: not-allowed;
68+
opacity: 0.7;
69+
}
70+
71+
.login-card__secondary {
72+
background: #fff !important;
73+
}
74+
75+
.login-card__message,
76+
.login-card__dev-code {
77+
margin-top: 16px !important;
78+
color: #146c2e;
79+
font-weight: 700;
80+
}
81+
82+
.login-card__error {
83+
margin-top: 16px !important;
84+
color: #b3261e;
85+
font-weight: 700;
86+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { useMemo, useState } from "react";
2+
import { useAuth } from "../auth/AuthContext.jsx";
3+
import "./LoginPage.css";
4+
5+
function normalizeReturnTo(value) {
6+
if (!value || !value.startsWith("/") || value.startsWith("//")) return "/main";
7+
return value;
8+
}
9+
10+
export function LoginPage() {
11+
const { reload } = useAuth();
12+
const params = new URLSearchParams(window.location.search);
13+
const returnTo = useMemo(() => normalizeReturnTo(params.get("returnTo")), [params]);
14+
const [email, setEmail] = useState("");
15+
const [password, setPassword] = useState("");
16+
const [error, setError] = useState("");
17+
const [submitting, setSubmitting] = useState(false);
18+
19+
async function handleSubmit(event) {
20+
event.preventDefault();
21+
setSubmitting(true);
22+
setError("");
23+
24+
try {
25+
const response = await fetch("/api/auth/password/login", {
26+
method: "POST",
27+
credentials: "include",
28+
headers: { "Content-Type": "application/json" },
29+
body: JSON.stringify({ email, password }),
30+
});
31+
const data = await response.json();
32+
if (!response.ok) throw new Error(data.error || "Failed to log in.");
33+
await reload();
34+
window.location.href = returnTo;
35+
} catch (err) {
36+
setError(err.message);
37+
} finally {
38+
setSubmitting(false);
39+
}
40+
}
41+
42+
return (
43+
<main className="login-page">
44+
<section className="login-card">
45+
<a className="login-card__back" href="/">
46+
← Back
47+
</a>
48+
<h1>Join Stack</h1>
49+
<p>Use your email and a password. If this email is new, we’ll create your account.</p>
50+
51+
<form onSubmit={handleSubmit}>
52+
<label>
53+
Email
54+
<input
55+
type="email"
56+
value={email}
57+
onChange={(event) => setEmail(event.target.value)}
58+
placeholder="you@example.com"
59+
autoComplete="email"
60+
required
61+
/>
62+
</label>
63+
<label>
64+
Password
65+
<input
66+
type="password"
67+
value={password}
68+
onChange={(event) => setPassword(event.target.value)}
69+
placeholder="At least 6 characters"
70+
autoComplete="current-password"
71+
required
72+
/>
73+
</label>
74+
<button type="submit" disabled={submitting}>
75+
{submitting ? "Logging in..." : "Log in / Sign up"}
76+
</button>
77+
</form>
78+
79+
{error ? <p className="login-card__error">{error}</p> : null}
80+
</section>
81+
</main>
82+
);
83+
}

client/src/components/PlatformStatusBar.css

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,18 @@
6666
margin-left: 25px;
6767
}
6868

69+
.platform-status__username {
70+
margin-left: 0.55rem;
71+
max-width: 10rem;
72+
overflow: hidden;
73+
color: #ffffff;
74+
font-size: clamp(1rem, 1.35vw, 1.45rem);
75+
line-height: 1;
76+
text-overflow: ellipsis;
77+
text-shadow: 0 2px 0 #1b3a7a;
78+
white-space: nowrap;
79+
}
80+
6981
.platform-status__text {
7082
margin-left: 0.45rem;
7183
color: #4bb3ff;
@@ -87,12 +99,15 @@
8799
}
88100

89101
.platform-status__menu-item {
102+
display: block;
103+
box-sizing: border-box;
90104
width: 100%;
91105
padding: 0.5rem 1rem;
92106
border: none;
93107
background: transparent;
94108
color: #e8f4ff;
95109
font: inherit;
110+
text-decoration: none;
96111
text-align: left;
97112
cursor: pointer;
98113
}

client/src/components/PlatformStatusBar.jsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export function PlatformStatusBar({ user, className = "" }) {
1313
const rootRef = useRef(null);
1414

1515
const avatarSrc = user?.profileImageUrl || defaultAvatar;
16+
const username = user?.name || user?.slug || user?.email?.split("@")[0] || "User";
17+
const isAdmin = user?.role === "admin";
1618

1719
useEffect(() => {
1820
function onDocClick(event) {
@@ -52,6 +54,11 @@ export function PlatformStatusBar({ user, className = "" }) {
5254
</button>
5355
{menuOpen && (
5456
<div className="platform-status__menu" role="menu">
57+
{isAdmin ? (
58+
<a className="platform-status__menu-item" role="menuitem" href="/admin">
59+
Admin Panel
60+
</a>
61+
) : null}
5562
<button type="button" className="platform-status__menu-item" role="menuitem" onClick={handleLogout}>
5663
Log out
5764
</button>
@@ -64,6 +71,7 @@ export function PlatformStatusBar({ user, className = "" }) {
6471
</div>
6572
<div className="platform-status__row">
6673
<img className="platform-status__icon platform-status__icon--hearts" src={heartsIcon} alt="" aria-hidden="true" />
74+
<span className="platform-status__username">{username}</span>
6775
</div>
6876
</div>
6977
</section>

0 commit comments

Comments
 (0)