feat: frontend layout skeleton

This commit is contained in:
Sam 2022-05-10 16:33:29 +02:00
parent 2e4b8b9823
commit 9c5a9a72d0
20 changed files with 1401 additions and 87 deletions

View file

@ -0,0 +1,5 @@
import React from "react";
export default function Container(props: React.PropsWithChildren<{}>) {
return <div className="m-2 lg:m-4">{props.children}</div>;
}

View file

@ -0,0 +1,91 @@
import Logo from "./logo";
import { useState } from "react";
import { Link } from "react-router-dom";
import { MoonStars, Sun, List } from "react-bootstrap-icons";
function Navigation() {
const [darkTheme, setDarkTheme] = useState<boolean>(
localStorage.theme === "dark" ||
(!("theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
);
const [showMenu, setShowMenu] = useState(false);
if (darkTheme) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
const storeTheme = (system: boolean) => {
if (system) {
localStorage.removeItem("theme");
} else {
localStorage.setItem("theme", darkTheme ? "dark" : "light");
}
};
return (
<div className="bg-white/75 dark:bg-slate-800/75 w-full backdrop-blur border-b-slate-200 dark:border-b-slate-900">
<div className="max-w-8xl mx-auto">
<div className="py-4 mx-4">
<div className="flex items-center">
<Link to="/">
<Logo />
</Link>
<div className="ml-auto flex items-center">
<nav className="hidden lg:flex">
<ul className="flex space-x-8 font-bold">
<li>
<Link
className="hover:text-sky-500 dark:hover:text-sky-400"
to="/login"
>
Log in
</Link>
</li>
</ul>
</nav>
<div className="flex border-l border-slate-200 ml-4 pl-4 lg:ml-6 lg:pl-6 lg:mr-2 dark:border-slate-500 space-x-2 lg:space-x-4">
<div
onClick={() => {
storeTheme(false);
setDarkTheme(!darkTheme);
}}
title={
darkTheme ? "Switch to light mode" : "Switch to dark mode"
}
className="cursor-pointer"
>
{darkTheme ? (
<Sun className="hover:text-sky-400" size={24} />
) : (
<MoonStars size={24} className="hover:text-sky-500" />
)}
</div>
<div onClick={() => setShowMenu(!showMenu)} title="Show menu" className="cursor-pointer flex lg:hidden">
<List className="dark:hover:text-sky-400 hover:text-sky-500" size={24} />
</div>
</div>
</div>
</div>
</div>
<nav className={`lg:hidden my-2 p-4 border-slate-200 dark:border-slate-500 border-t border-b ${showMenu ? "flex" : "hidden"}`}>
<ul className="flex space-x-8 font-bold">
<li>
<Link
className="hover:text-sky-500 dark:hover:text-sky-400"
to="/login"
>
Log in
</Link>
</li>
</ul>
</nav>
</div>
</div>
);
}
export default Navigation;

16
frontend/src/lib/logo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

45
frontend/src/lib/logo.tsx Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,20 +1,26 @@
import axios from "axios";
import { atom } from "recoil";
import type { APIError, MeUser } from "./types";
import { atom, useRecoilState, useRecoilValue } from "recoil";
import { APIError, ErrorCode, MeUser } from "./types";
export const userState = atom<MeUser | null>({
export const userState = atom<MeUser>({
key: "userState",
default: getCurrentUser(),
});
async function getCurrentUser(): Promise<MeUser | null> {
async function getCurrentUser() {
const token = localStorage.getItem("pronouns-token");
if (!token) return null;
try {
const resp = await axios.get<MeUser | APIError>("/api/v1/users/@me");
if ("id" in resp.data) return resp.data as MeUser;
return null;
if (resp.status === 200) {
return resp.data as MeUser;
}
// if we got a forbidden error, the token is invalid
if ((resp.data as APIError).code === ErrorCode.Forbidden) {
localStorage.removeItem("pronouns-token");
}
} catch (e) {
console.log("Error fetching /users/@me:", e);
}

View file

@ -10,6 +10,22 @@ export interface MeUser {
discord_username: string | null;
}
export interface User {
id: string;
username: string;
display_name: string | null;
bio: string | null;
avatar_source: string | null;
links: string[] | null;
members: PartialMember[];
}
export interface PartialMember {
id: string;
name: string;
avatar_url: string | null;
}
export interface APIError {
code: ErrorCode;
message?: string;