Skip to content

Commit 2069082

Browse files
authored
Merge pull request #22 from hackclub/backend_setup
internal structure started
2 parents 87877db + 177f42e commit 2069082

20 files changed

Lines changed: 2004 additions & 284 deletions

client/src/App.jsx

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,24 @@ 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";
89
import { TestPage } from "./components/TestPage.jsx";
10+
import { AdminPage } from "./components/AdminPage.jsx";
11+
import { AdminShopPage } from "./components/AdminShopPage.jsx";
12+
import { AdminShopOrdersPage } from "./components/AdminShopOrdersPage.jsx";
913
import { UserAreaPage } from "./components/UserAreaPage.jsx";
1014

11-
const PROTECTED = new Set(["/main", "/shop", "/projects", "/faq", "/user", "/test"]);
15+
const PROTECTED = new Set(["/main", "/shop", "/projects", "/faq", "/user", "/test", "/admin"]);
16+
const ADMIN_ONLY = new Set(["/admin"]);
1217

1318
export default function App() {
1419
const [auth, setAuth] = useState({ status: "loading", user: null });
20+
const pathname = window.location.pathname.replace(/\/+$/, "") || "/";
21+
const isLocalhost = ["localhost", "127.0.0.1"].includes(window.location.hostname);
22+
const isAdminPath = pathname === "/admin" || pathname.startsWith("/admin/");
1523

1624
const loadMe = useCallback(async () => {
1725
try {
@@ -27,8 +35,6 @@ export default function App() {
2735
loadMe();
2836
}, [loadMe]);
2937

30-
const pathname = window.location.pathname.replace(/\/+$/, "") || "/";
31-
3238
if (auth.status === "loading") {
3339
return (
3440
<div className="app-auth-loading" aria-busy="true">
@@ -37,9 +43,14 @@ export default function App() {
3743
);
3844
}
3945

40-
if (PROTECTED.has(pathname) && !auth.user) {
46+
if ((PROTECTED.has(pathname) || isAdminPath) && !auth.user) {
4147
const returnTo = `${pathname}${window.location.search}${window.location.hash}`;
42-
window.location.replace(`/api/auth/hackclub/login?returnTo=${encodeURIComponent(returnTo)}`);
48+
window.location.replace(`/login?returnTo=${encodeURIComponent(returnTo)}`);
49+
return null;
50+
}
51+
52+
if ((ADMIN_ONLY.has(pathname) || isAdminPath) && auth.user?.role !== "admin") {
53+
window.location.replace("/main");
4354
return null;
4455
}
4556

@@ -48,6 +59,11 @@ export default function App() {
4859
return null;
4960
}
5061

62+
if (pathname === "/login" && auth.user) {
63+
window.location.replace("/main");
64+
return null;
65+
}
66+
5167
const contextValue = {
5268
user: auth.user,
5369
status: auth.status,
@@ -59,8 +75,11 @@ export default function App() {
5975
case "/main":
6076
page = <MainPage />;
6177
break;
78+
case "/login":
79+
page = <LoginPage />;
80+
break;
6281
case "/test":
63-
page = <TestPage />;
82+
page = isLocalhost ? <TestPage /> : <AdminPage />;
6483
break;
6584
case "/projects":
6685
page = <ProjectsPage />;
@@ -74,6 +93,15 @@ export default function App() {
7493
case "/user":
7594
page = <UserAreaPage />;
7695
break;
96+
case "/admin":
97+
page = <AdminPage />;
98+
break;
99+
case "/admin/shop":
100+
page = <AdminShopPage />;
101+
break;
102+
case "/admin/shop/orders":
103+
page = <AdminShopOrdersPage />;
104+
break;
77105
default:
78106
page = <Hero />;
79107
}
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
.admin-page {
2+
position: fixed;
3+
inset: 0;
4+
z-index: 0;
5+
overflow-y: auto;
6+
}
7+
8+
.admin-page::before {
9+
content: "";
10+
position: fixed;
11+
inset: 0;
12+
background: linear-gradient(145deg, #0b1226 0%, #171f3e 48%, #2a1452 100%);
13+
z-index: -1;
14+
}
15+
16+
.admin-content {
17+
position: relative;
18+
max-width: 900px;
19+
margin: 0 auto;
20+
padding: 100px 2rem 4rem;
21+
font-family: Georgia, "Times New Roman", serif;
22+
color: #f0e6d2;
23+
}
24+
25+
.admin-content h1 {
26+
font-size: 2.4rem;
27+
color: #8af4ff;
28+
text-shadow: 1px 2px 4px rgb(0 0 0 / 60%);
29+
margin: 0 0 2rem;
30+
text-align: center;
31+
font-style: italic;
32+
}
33+
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+
53+
.admin-links {
54+
display: flex;
55+
flex-direction: column;
56+
gap: 1rem;
57+
margin: 2rem 0;
58+
}
59+
60+
.admin-link {
61+
display: flex;
62+
align-items: center;
63+
gap: 1rem;
64+
padding: 1.2rem 1.5rem;
65+
background: rgb(9 18 42 / 82%);
66+
border: 2px solid #4ba2ff;
67+
border-radius: 12px;
68+
text-decoration: none;
69+
font-family: Georgia, serif;
70+
font-size: 1.3rem;
71+
font-weight: 700;
72+
color: #98e8ff;
73+
transition: all 0.2s;
74+
}
75+
76+
.admin-link:hover {
77+
background: rgb(21 34 70 / 95%);
78+
border-color: #a765ff;
79+
transform: translateX(4px);
80+
}
81+
82+
.admin-link-icon {
83+
font-size: 1.6rem;
84+
}
85+
86+
.admin-link-desc {
87+
display: block;
88+
font-size: 0.85rem;
89+
font-weight: 400;
90+
color: #c8d4ff;
91+
margin-top: 0.2rem;
92+
}
93+
94+
.admin-super {
95+
margin-top: 0.75rem;
96+
display: flex;
97+
align-items: center;
98+
gap: 0.8rem;
99+
color: #f0e6d2;
100+
}
101+
102+
.admin-super-icon {
103+
font-size: 1.2rem;
104+
color: #a765ff;
105+
}
106+
107+
.admin-super-copy {
108+
display: flex;
109+
flex-direction: column;
110+
}
111+
112+
.admin-super-copy span {
113+
opacity: 0.85;
114+
}
115+
116+
.admin-silly {
117+
margin-top: 3rem;
118+
text-align: center;
119+
opacity: 0.6;
120+
}
121+
122+
.admin-silly pre {
123+
font-size: 0.8rem;
124+
line-height: 1;
125+
color: #c4b89a;
126+
}
127+
128+
.admin-silly .admin-emojis {
129+
font-size: 1.2rem;
130+
margin: 0.5rem 0;
131+
}
132+
133+
.admin-silly .admin-tagline {
134+
font-family: Georgia, serif;
135+
font-size: 1.5rem;
136+
color: #79a6ff;
137+
font-style: italic;
138+
}
139+
140+
.admin-airtable {
141+
margin-top: 2.2rem;
142+
padding: 1.2rem;
143+
border: 2px solid #4ba2ff;
144+
border-radius: 12px;
145+
background: rgb(10 18 43 / 78%);
146+
}
147+
148+
.admin-airtable h2 {
149+
margin: 0 0 0.5rem;
150+
color: #8af4ff;
151+
}
152+
153+
.admin-airtable-subtitle {
154+
margin: 0 0 1rem;
155+
color: #c8d4ff;
156+
}
157+
158+
.admin-airtable-sync-row {
159+
display: flex;
160+
align-items: center;
161+
justify-content: space-between;
162+
gap: 1rem;
163+
padding: 0.8rem;
164+
margin-bottom: 1rem;
165+
border: 1px solid #4ba2ff;
166+
border-radius: 10px;
167+
background: rgb(11 28 62 / 70%);
168+
}
169+
170+
.admin-airtable-sync-row div {
171+
display: grid;
172+
gap: 0.25rem;
173+
}
174+
175+
.admin-airtable-sync-row button {
176+
border: 1px solid #7f5cff;
177+
background: linear-gradient(135deg, #43c6ff, #8d4dff);
178+
color: #fff;
179+
border-radius: 999px;
180+
padding: 0.45rem 0.95rem;
181+
cursor: pointer;
182+
}
183+
184+
.admin-airtable-sync-row button:disabled {
185+
opacity: 0.7;
186+
cursor: not-allowed;
187+
}
188+
189+
.admin-airtable-error {
190+
color: #ffb5b5;
191+
}
192+
193+
.admin-airtable-success {
194+
color: #9cffcf;
195+
}
196+
197+
.admin-airtable-details {
198+
margin-top: 1rem;
199+
padding: 0.8rem;
200+
border-radius: 10px;
201+
background: rgb(20 32 67 / 62%);
202+
}
203+
204+
.admin-airtable-details p {
205+
margin: 0.35rem 0;
206+
}
207+
208+
.admin-airtable-rows {
209+
margin-top: 1rem;
210+
display: grid;
211+
gap: 0.75rem;
212+
}
213+
214+
.admin-airtable-rows pre {
215+
margin: 0;
216+
padding: 0.75rem;
217+
overflow-x: auto;
218+
border: 1px solid #4ba2ff;
219+
border-radius: 8px;
220+
background: rgb(6 10 26 / 95%);
221+
color: #e9f4ff;
222+
}

0 commit comments

Comments
 (0)