diff --git a/frontend/components/Loading.tsx b/frontend/components/Loading.tsx new file mode 100644 index 0000000..26e5e8c --- /dev/null +++ b/frontend/components/Loading.tsx @@ -0,0 +1,10 @@ +import { ThreeDots } from "react-bootstrap-icons"; + +export default function Loading() { + return ( +
+
+ ); +} diff --git a/frontend/components/Navigation.tsx b/frontend/components/Navigation.tsx index 9f29cfc..592a281 100644 --- a/frontend/components/Navigation.tsx +++ b/frontend/components/Navigation.tsx @@ -56,7 +56,9 @@ export default function Navigation() { const nav = user ? ( <> - @{user.username} + + @{user.username} + Settings Log out diff --git a/frontend/lib/fetch.ts b/frontend/lib/fetch.ts index e9a823e..21fad71 100644 --- a/frontend/lib/fetch.ts +++ b/frontend/lib/fetch.ts @@ -1,6 +1,6 @@ import type { APIError } from "./types"; -const apiBase = process.env.API_BASE ?? "http://localhost:8080"; +const apiBase = process.env.API_BASE ?? "/api"; export default async function fetchAPI( path: string, diff --git a/frontend/package.json b/frontend/package.json index a8df638..56464f2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,6 +14,7 @@ "react-dom": "18.2.0", "react-markdown": "^8.0.3", "react-sortablejs": "^6.1.4", + "react-toast": "^1.0.3", "recoil": "^0.7.5", "sortablejs": "^1.15.0" }, diff --git a/frontend/pages/login/discord.tsx b/frontend/pages/login/discord.tsx new file mode 100644 index 0000000..726cb8e --- /dev/null +++ b/frontend/pages/login/discord.tsx @@ -0,0 +1,74 @@ +import { useEffect } from "react"; +import { useRouter } from "next/router"; +import { GetServerSideProps } from "next"; +import { useRecoilState } from "recoil"; +import fetchAPI from "../../lib/fetch"; +import { userState } from "../../lib/state"; +import { MeUser } from "../../lib/types"; + +interface CallbackResponse { + has_account: boolean; + token?: string; + user?: MeUser; + + discord?: string; + ticket?: string; + require_invite?: boolean; +} + +interface State { + hasAccount: boolean; + isLoading: boolean; + token?: string; + user?: MeUser; + discord?: string; + ticket?: string; + error?: any; +} + +export default function Discord(props: State) { + const router = useRouter(); + + const [user, setUser] = useRecoilState(userState); + + useEffect(() => { + // we got a token + user, save it and return to the home page + if (props.token) { + localStorage.setItem("pronouns-token", props.token); + setUser(props.user!); + + router.push("/"); + } + }, [props.token, props.user, setUser, router]); + + return <>wow such login; +} + +export const getServerSideProps: GetServerSideProps = async ( + context +) => { + try { + const resp = await fetchAPI( + "/auth/discord/callback", + "POST", + { + callback_domain: process.env.DOMAIN, + code: context.query.code, + state: context.query.state, + } + ); + + return { + props: { + hasAccount: resp.has_account, + isLoading: false, + token: resp.token, + user: resp.user, + discord: resp.discord || null, + ticket: resp.ticket || null, + }, + }; + } catch (e) { + return { props: { error: e } }; + } +}; diff --git a/frontend/pages/login/index.tsx b/frontend/pages/login/index.tsx new file mode 100644 index 0000000..410f41a --- /dev/null +++ b/frontend/pages/login/index.tsx @@ -0,0 +1,41 @@ +import { GetServerSideProps } from "next"; +import { useRouter } from "next/router"; +import { useRecoilValue } from "recoil"; +import Head from "next/head"; +import fetchAPI from "../../lib/fetch"; +import { userState } from "../../lib/state"; + +interface URLsResponse { + discord: string; +} + +export default function Login({ urls }: { urls: URLsResponse }) { + const router = useRouter(); + + if (useRecoilValue(userState) !== null) { + router.push("/"); + } + + return ( + <> + + Login - pronouns.cc + + Login with Discord + + ); +} + +export const getServerSideProps: GetServerSideProps = async (context) => { + try { + const urls = await fetchAPI("/auth/urls", "POST", { + callback_domain: process.env.DOMAIN, + }); + + return { props: { urls } }; + } catch (e) { + console.log(e); + + return { notFound: true }; + } +}; diff --git a/frontend/pages/u/[user]/index.tsx b/frontend/pages/u/[user]/index.tsx index 816fef2..d7c4cef 100644 --- a/frontend/pages/u/[user]/index.tsx +++ b/frontend/pages/u/[user]/index.tsx @@ -6,23 +6,45 @@ import FieldCard from "../../../components/FieldCard"; import Card from "../../../components/Card"; import ReactMarkdown from "react-markdown"; import Image from "next/image"; +import { userState } from "../../../lib/state"; +import { useRecoilValue } from "recoil"; +import Link from "next/link"; interface Props { user: User; } export default function Index({ user }: Props) { + const isMeUser = useRecoilValue(userState)?.id === user.id; + return ( <> @{user.username} - pronouns.cc + {isMeUser && ( +
+ + You are currently viewing your{" "} + public profile. + +
+ + Edit your profile + +
+ )}
{user.avatar_url && ( - {`@${user.username}'s )} diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 45f26fe..905d73c 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2097,6 +2097,11 @@ react-sortablejs@^6.1.4: classnames "2.3.1" tiny-invariant "1.2.0" +react-toast@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/react-toast/-/react-toast-1.0.3.tgz#cbe2cd946c5762736642dd2981a7e5d666c5448e" + integrity sha512-gL3+O5hlLaoBmd36oXWKrjFeUyLCMQ04AIh48LrnUvdeg2vhJQ0E803TgVemgJvYUXKlutMVn9+/QS2DDnk26Q== + react@18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"