Skip to content

Commit b9af172

Browse files
authored
Merge pull request #30 from fogo-sh/login-frontend
login frontend
2 parents 0066b88 + fdfce45 commit b9af172

File tree

8 files changed

+350
-24
lines changed

8 files changed

+350
-24
lines changed

frontend/package-lock.json

+33
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
"prettier": "prettier --write ."
99
},
1010
"dependencies": {
11+
"@headlessui/react": "^1.4.2",
1112
"date-fns": "^2.26.0",
1213
"graphql": "^15.7.2",
14+
"js-cookie": "^3.0.1",
1315
"react": "^17.0.2",
1416
"react-dom": "^17.0.2",
1517
"react-icons": "^4.3.1",

frontend/src/App.jsx

+54-22
Original file line numberDiff line numberDiff line change
@@ -8,36 +8,68 @@ import { ProjectsPage } from "./pages/ProjectsPage";
88
import { ProjectPage } from "./pages/ProjectPage";
99
import { UsersPage } from "./pages/UsersPage";
1010
import { UserPage } from "./pages/UserPage";
11+
import { LoginPage } from "./pages/LoginPage";
12+
import { LogoutPage } from "./pages/LogoutPage";
13+
import Cookies from "js-cookie";
1114

1215
import grack from "./grack.png";
16+
import { AuthProvider, useAuth } from "./providers/AuthProvider";
17+
18+
function Header() {
19+
const { currentUser } = useAuth();
20+
21+
return (
22+
<>
23+
<div className="flex items-center justify-between">
24+
<div className="w-[3.5rem] flex">
25+
<img src={grack} className="h-16 mb-1 mx-auto" />
26+
</div>
27+
{currentUser && <span>Logged in as {currentUser.username}</span>}
28+
</div>
29+
<div className="flex justify-between">
30+
<div className="flex gap-3">
31+
<Link to="/">GrackDB</Link>
32+
<Link to="/users">Users</Link>
33+
<Link to="/projects">Projects</Link>
34+
</div>
35+
<div className="flex gap-3">
36+
<a href="/playground">GraphQL Playground</a>
37+
{currentUser ? (
38+
<Link to="/logout">Logout</Link>
39+
) : (
40+
<Link to="/login">Login</Link>
41+
)}
42+
</div>
43+
</div>
44+
</>
45+
);
46+
}
47+
48+
function AppRoutes() {
49+
return (
50+
<Routes>
51+
<Route path="/" element={<HomepagePage />} />
52+
<Route path="/projects" element={<ProjectsPage />} />
53+
<Route path="/project/:projectId" element={<ProjectPage />} />
54+
<Route path="/users" element={<UsersPage />} />
55+
<Route path="/user/:username" element={<UserPage />} />
56+
<Route path="/login" element={<LoginPage />} />
57+
<Route path="/logout" element={<LogoutPage />} />
58+
</Routes>
59+
);
60+
}
1361

1462
function App() {
1563
return (
1664
<BrowserRouter>
1765
<Provider value={client}>
18-
<div className="w-11/12 sm:w-2/3 lg:w-7/12 xl:w-1/2 2xl:w-5/12 mx-auto my-3">
19-
<div className="w-[3.5rem]">
20-
<img src={grack} className="h-16 mb-1 mx-auto" />
21-
</div>
22-
<div className="flex justify-between">
23-
<div className="flex gap-3">
24-
<Link to="/">GrackDB</Link>
25-
<Link to="/users">Users</Link>
26-
<Link to="/projects">Projects</Link>
27-
</div>
28-
<div className="flex gap-3">
29-
<a href="/playground">GraphQL Playground</a>
30-
</div>
66+
<AuthProvider>
67+
<div className="w-11/12 sm:w-2/3 lg:w-7/12 xl:w-1/2 2xl:w-5/12 mx-auto my-3">
68+
<Header />
69+
<hr className="my-3" />
70+
<AppRoutes />
3171
</div>
32-
<hr className="my-3" />
33-
<Routes>
34-
<Route path="/" element={<HomepagePage />} />
35-
<Route path="/projects" element={<ProjectsPage />} />
36-
<Route path="/project/:projectId" element={<ProjectPage />} />
37-
<Route path="/users" element={<UsersPage />} />
38-
<Route path="/user/:username" element={<UserPage />} />
39-
</Routes>
40-
</div>
72+
</AuthProvider>
4173
</Provider>
4274
</BrowserRouter>
4375
);

frontend/src/index.css

+4
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,7 @@ h2 {
2222
a {
2323
@apply text-blue-700 font-semibold;
2424
}
25+
26+
.btn {
27+
@apply border border-gray-500 rounded-sm px-1 py-1.5;
28+
}

frontend/src/pages/LoginPage.jsx

+206
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import React, { useCallback, useEffect, useState } from "react";
2+
import { useQuery, useMutation } from "urql";
3+
import { Listbox } from "@headlessui/react";
4+
import {
5+
FaDiscord,
6+
FaGithub,
7+
FaCrown,
8+
FaCaretDown,
9+
FaCheck,
10+
} from "react-icons/fa";
11+
import { useAuth } from "../providers/AuthProvider";
12+
import { useNavigate } from "react-router-dom";
13+
14+
const LOGIN_QUERY = `
15+
query Login {
16+
availableAuthProviders {
17+
url
18+
type
19+
}
20+
developmentMode
21+
users {
22+
edges {
23+
node {
24+
id
25+
username
26+
}
27+
}
28+
}
29+
}
30+
`;
31+
32+
const DEVELOPMENT_ASSUME_USER_QUERY = `
33+
query AssumableUsers {
34+
users {
35+
edges {
36+
node {
37+
id
38+
username
39+
}
40+
}
41+
}
42+
}
43+
`;
44+
45+
const DEVELOPMENT_ASSUME_USER_MUTATION = `
46+
mutation AssumeDevelopmentUser($id: ID!) {
47+
assumeDevelopmentUser(id: $id) {
48+
id
49+
}
50+
}
51+
`;
52+
53+
function DevelopmentAssumeUser() {
54+
const [options, setOptions] = useState(null);
55+
const [selectedUser, setSelectedUser] = useState(null);
56+
57+
const navigate = useNavigate();
58+
const { refreshCurrentUser } = useAuth();
59+
60+
const [{ fetching, data }] = useQuery({
61+
query: DEVELOPMENT_ASSUME_USER_QUERY,
62+
});
63+
64+
const [{ data: assumeDevelopmentUserData }, assumeDevelopmentUser] =
65+
useMutation(DEVELOPMENT_ASSUME_USER_MUTATION);
66+
67+
useEffect(() => {
68+
if (data === undefined) {
69+
return;
70+
}
71+
72+
const users = data.users.edges.map((edge) => edge.node);
73+
setOptions(users);
74+
setSelectedUser(users[0]);
75+
}, [data]);
76+
77+
const assume = useCallback(() => {
78+
assumeDevelopmentUser({ id: selectedUser.id });
79+
}, [selectedUser]);
80+
81+
useEffect(() => {
82+
if (assumeDevelopmentUserData === undefined) {
83+
return;
84+
}
85+
86+
refreshCurrentUser();
87+
navigate("/");
88+
}, [assumeDevelopmentUserData]);
89+
90+
if (fetching || selectedUser === null) {
91+
return null;
92+
}
93+
94+
return (
95+
<>
96+
<p className="text-center mb-3 italic font-semibold">
97+
⭐ Super Secret Developer Assume User Tool ⭐
98+
</p>
99+
100+
<div className="flex justify-center items-center gap-2 h-10">
101+
<Listbox value={selectedUser} onChange={setSelectedUser}>
102+
<div className="relative min-w-[15rem] h-full">
103+
<Listbox.Button className="relative w-full h-full border border-gray-500 rounded-sm pl-3 pr-2 py-1 flex items-center justify-between mx-auto">
104+
{selectedUser.username}{" "}
105+
<FaCaretDown className="ml-1 opacity-50" />
106+
</Listbox.Button>
107+
<Listbox.Options className="absolute w-full mt-1 overflow-auto text-base bg-white rounded-m max-h-60 border border-gray-500">
108+
{options.map((option) => (
109+
<Listbox.Option key={option.id} value={option}>
110+
{({ selected, active }) => (
111+
<div
112+
className={`${
113+
active ? "bg-gray-100" : ""
114+
} flex items-center px-2.5 py-1.5`}
115+
>
116+
<span
117+
className={`${
118+
selected ? "font-medium" : "font-normal"
119+
} block truncate`}
120+
>
121+
{option.username}
122+
</span>
123+
{selected ? (
124+
<FaCheck
125+
className="ml-2 opacity-50"
126+
aria-hidden="true"
127+
/>
128+
) : null}
129+
</div>
130+
)}
131+
</Listbox.Option>
132+
))}
133+
</Listbox.Options>
134+
</div>
135+
</Listbox>
136+
137+
<button
138+
className="btn flex w-[6rem] my-3 h-full items-center"
139+
onClick={assume}
140+
>
141+
<div className="flex items-center mx-auto">
142+
<FaCrown className="mr-1" /> Assume
143+
</div>
144+
</button>
145+
</div>
146+
</>
147+
);
148+
}
149+
150+
export function LoginPage() {
151+
const [{ fetching, data }] = useQuery({
152+
query: LOGIN_QUERY,
153+
});
154+
155+
if (fetching) {
156+
return null;
157+
}
158+
159+
const developmentMode = data.developmentMode;
160+
161+
const availableAuthProviders = Object.fromEntries(
162+
data.availableAuthProviders.map(({ type, ...authProvider }) => [
163+
type.toLowerCase(),
164+
authProvider,
165+
])
166+
);
167+
168+
return (
169+
<>
170+
<h1 className="text-center">Login</h1>
171+
172+
{data.availableAuthProviders.length === 0 && (
173+
<p className="text-center italic">no available auth providers!</p>
174+
)}
175+
176+
<div className="flex flex-col items-center">
177+
{availableAuthProviders.discord && (
178+
<a href={availableAuthProviders.discord.url}>
179+
<button className="btn flex w-[6rem] my-2">
180+
<div className="flex items-center mx-auto">
181+
<FaDiscord className="mr-1" /> Discord
182+
</div>
183+
</button>
184+
</a>
185+
)}
186+
187+
{availableAuthProviders.github && (
188+
<a href={availableAuthProviders.github.url}>
189+
<button className="btn flex w-[6rem] my-2">
190+
<div className="flex items-center mx-auto">
191+
<FaGithub className="mr-1" /> GitHub
192+
</div>
193+
</button>
194+
</a>
195+
)}
196+
</div>
197+
198+
{developmentMode && (
199+
<>
200+
<br className="my-6" />
201+
<DevelopmentAssumeUser />
202+
</>
203+
)}
204+
</>
205+
);
206+
}

0 commit comments

Comments
 (0)