Skip to content

Commit b056fc0

Browse files
committed
ADD - shop db and system
1 parent a02e649 commit b056fc0

8 files changed

Lines changed: 998 additions & 82 deletions

File tree

client/src/App.jsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,18 @@ import { ProjectsPage } from "./components/ProjectsPage.jsx";
77
import { ShopPage } from "./components/ShopPage.jsx";
88
import { TestPage } from "./components/TestPage.jsx";
99
import { AdminPage } from "./components/AdminPage.jsx";
10+
import { AdminShopPage } from "./components/AdminShopPage.jsx";
11+
import { AdminShopOrdersPage } from "./components/AdminShopOrdersPage.jsx";
1012
import { UserAreaPage } from "./components/UserAreaPage.jsx";
1113

1214
const PROTECTED = new Set(["/main", "/shop", "/projects", "/faq", "/user", "/test", "/admin"]);
1315
const ADMIN_ONLY = new Set(["/admin"]);
1416

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

1923
const loadMe = useCallback(async () => {
2024
try {
@@ -30,8 +34,6 @@ export default function App() {
3034
loadMe();
3135
}, [loadMe]);
3236

33-
const pathname = window.location.pathname.replace(/\/+$/, "") || "/";
34-
3537
if (auth.status === "loading") {
3638
return (
3739
<div className="app-auth-loading" aria-busy="true">
@@ -40,13 +42,13 @@ export default function App() {
4042
);
4143
}
4244

43-
if (PROTECTED.has(pathname) && !auth.user) {
45+
if ((PROTECTED.has(pathname) || isAdminPath) && !auth.user) {
4446
const returnTo = `${pathname}${window.location.search}${window.location.hash}`;
4547
window.location.replace(`/api/auth/hackclub/login?returnTo=${encodeURIComponent(returnTo)}`);
4648
return null;
4749
}
4850

49-
if (ADMIN_ONLY.has(pathname) && auth.user?.role !== "admin") {
51+
if ((ADMIN_ONLY.has(pathname) || isAdminPath) && auth.user?.role !== "admin") {
5052
window.location.replace("/main");
5153
return null;
5254
}
@@ -85,6 +87,12 @@ export default function App() {
8587
case "/admin":
8688
page = <AdminPage />;
8789
break;
90+
case "/admin/shop":
91+
page = <AdminShopPage />;
92+
break;
93+
case "/admin/shop/orders":
94+
page = <AdminShopOrdersPage />;
95+
break;
8896
default:
8997
page = <Hero />;
9098
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import "./AdminShopPage.css";
2+
3+
export function AdminShopOrdersPage() {
4+
return (
5+
<main className="admin-shop-page">
6+
<section className="admin-shop-content">
7+
<a href="/admin/shop" className="admin-shop-back">
8+
← Shop catalog & items
9+
</a>
10+
<h1>Shop orders</h1>
11+
<section className="admin-shop-card">
12+
<p>
13+
Order fulfillment is part of the Jackpot shop structure, but this Stack platform does not have a
14+
<code> shop_orders </code>
15+
table yet.
16+
</p>
17+
<p>
18+
Once purchases are wired up, this page can use the same queue/status structure from Jackpot against the
19+
current database.
20+
</p>
21+
</section>
22+
</section>
23+
</main>
24+
);
25+
}
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
.admin-shop-page {
2+
min-height: 100vh;
3+
background: linear-gradient(145deg, #0b1226 0%, #171f3e 48%, #2a1452 100%);
4+
color: #f0e6ff;
5+
overflow-x: hidden;
6+
}
7+
8+
.admin-shop-content {
9+
width: min(1180px, calc(100% - 32px));
10+
margin: 0 auto;
11+
padding: 56px 0;
12+
}
13+
14+
.admin-shop-back,
15+
.admin-shop-orders-link a,
16+
.admin-shop-table a {
17+
color: #8af4ff;
18+
}
19+
20+
.admin-shop-content h1 {
21+
margin: 0 0 1.2rem;
22+
color: #8af4ff;
23+
font-family: Georgia, "Times New Roman", serif;
24+
font-size: clamp(2.2rem, 5vw, 4rem);
25+
font-style: italic;
26+
text-align: center;
27+
}
28+
29+
.admin-shop-content h2 {
30+
margin: 0 0 1rem;
31+
color: #98e8ff;
32+
}
33+
34+
.admin-shop-orders-link {
35+
display: flex;
36+
align-items: center;
37+
gap: 1rem;
38+
margin: 0 0 1.4rem;
39+
}
40+
41+
.admin-shop-orders-link a {
42+
display: inline-flex;
43+
align-items: center;
44+
gap: 0.5rem;
45+
padding: 12px 20px;
46+
border: 2px solid #4ba2ff;
47+
border-radius: 10px;
48+
background: rgb(9 18 42 / 82%);
49+
font-weight: 700;
50+
text-decoration: none;
51+
}
52+
53+
.admin-shop-orders-link span {
54+
color: #c8d4ff;
55+
}
56+
57+
.admin-shop-card {
58+
margin: 1.2rem 0;
59+
padding: 1.2rem;
60+
border: 2px solid #4ba2ff;
61+
border-radius: 16px;
62+
background: rgb(10 18 43 / 78%);
63+
box-shadow: 0 18px 44px rgb(0 0 0 / 26%);
64+
}
65+
66+
.admin-shop-form {
67+
display: grid;
68+
grid-template-columns: repeat(3, minmax(0, 1fr));
69+
gap: 1rem;
70+
}
71+
72+
.admin-shop-form label {
73+
display: grid;
74+
gap: 0.35rem;
75+
color: #c8d4ff;
76+
font-weight: 700;
77+
}
78+
79+
.admin-shop-form input,
80+
.admin-shop-form textarea {
81+
width: 100%;
82+
box-sizing: border-box;
83+
border: 1px solid #4ba2ff;
84+
border-radius: 10px;
85+
padding: 0.65rem 0.75rem;
86+
background: rgb(6 10 26 / 95%);
87+
color: #e9f4ff;
88+
font: inherit;
89+
}
90+
91+
.admin-shop-form input[type="checkbox"] {
92+
width: auto;
93+
accent-color: #8d4dff;
94+
}
95+
96+
.admin-shop-form textarea {
97+
min-height: 110px;
98+
resize: vertical;
99+
}
100+
101+
.admin-shop-form-wide {
102+
grid-column: 1 / -1;
103+
}
104+
105+
.admin-shop-form button,
106+
.admin-shop-actions button,
107+
.admin-shop-row-actions button {
108+
border: 1px solid #7f5cff;
109+
border-radius: 999px;
110+
padding: 0.5rem 0.95rem;
111+
background: linear-gradient(135deg, #43c6ff, #8d4dff);
112+
color: #fff;
113+
font-weight: 700;
114+
cursor: pointer;
115+
}
116+
117+
.admin-shop-form button {
118+
justify-self: start;
119+
}
120+
121+
.admin-shop-table-header {
122+
display: flex;
123+
align-items: center;
124+
justify-content: space-between;
125+
gap: 1rem;
126+
margin-bottom: 1rem;
127+
}
128+
129+
.admin-shop-actions {
130+
display: flex;
131+
align-items: center;
132+
flex-wrap: wrap;
133+
gap: 0.75rem;
134+
}
135+
136+
.admin-shop-actions label {
137+
display: inline-flex;
138+
align-items: center;
139+
gap: 0.35rem;
140+
color: #c8d4ff;
141+
}
142+
143+
.admin-shop-table-wrap {
144+
overflow-x: auto;
145+
}
146+
147+
.admin-shop-table {
148+
width: 100%;
149+
border-collapse: collapse;
150+
min-width: 980px;
151+
}
152+
153+
.admin-shop-table th,
154+
.admin-shop-table td {
155+
padding: 0.7rem 0.65rem;
156+
border-bottom: 1px solid rgb(75 162 255 / 32%);
157+
text-align: left;
158+
vertical-align: top;
159+
}
160+
161+
.admin-shop-table th {
162+
color: #8af4ff;
163+
font-size: 0.85rem;
164+
text-transform: uppercase;
165+
letter-spacing: 0.04em;
166+
}
167+
168+
.admin-shop-table tr.is-inactive {
169+
opacity: 0.52;
170+
}
171+
172+
.admin-shop-row-actions {
173+
display: flex;
174+
flex-wrap: wrap;
175+
gap: 0.45rem;
176+
}
177+
178+
.admin-shop-error {
179+
color: #ffb5b5;
180+
}
181+
182+
.admin-shop-modal {
183+
position: fixed;
184+
inset: 0;
185+
z-index: 40;
186+
display: grid;
187+
place-items: center;
188+
padding: 1rem;
189+
background: rgb(3 8 20 / 76%);
190+
}
191+
192+
.admin-shop-modal section {
193+
position: relative;
194+
width: min(960px, 100%);
195+
max-height: 88vh;
196+
overflow-y: auto;
197+
padding: 1.2rem;
198+
border: 2px solid #4ba2ff;
199+
border-radius: 16px;
200+
background: #111b3a;
201+
}
202+
203+
.admin-shop-modal-close {
204+
position: absolute;
205+
top: 0.8rem;
206+
right: 0.9rem;
207+
border: 0;
208+
background: transparent;
209+
color: #8af4ff;
210+
font-size: 1.6rem;
211+
cursor: pointer;
212+
}
213+
214+
@media (max-width: 820px) {
215+
.admin-shop-form {
216+
grid-template-columns: 1fr;
217+
}
218+
219+
.admin-shop-table-header,
220+
.admin-shop-orders-link {
221+
align-items: flex-start;
222+
flex-direction: column;
223+
}
224+
}

0 commit comments

Comments
 (0)