feat(frontend): log in with Discord

This commit is contained in:
Sam 2022-09-16 00:49:04 +02:00
parent e4d028bbad
commit 4a8e1bb54f
8 changed files with 158 additions and 3 deletions

View File

@ -0,0 +1,10 @@
import { ThreeDots } from "react-bootstrap-icons";
export default function Loading() {
return (
<div className="flex flex-col pt-32 items-center">
<ThreeDots size={64} className="animate-bounce" aria-hidden="true" />
<span className="font-bold text-xl">Loading...</span>
</div>
);
}

View File

@ -56,7 +56,9 @@ export default function Navigation() {
const nav = user ? ( const nav = user ? (
<> <>
<NavItem href={`/u/${user.username}`}>@{user.username}</NavItem> <NavItem href={`/u/${user.username}`}>
<a>@{user.username}</a>
</NavItem>
<NavItem href="/settings">Settings</NavItem> <NavItem href="/settings">Settings</NavItem>
<NavItem href="/logout">Log out</NavItem> <NavItem href="/logout">Log out</NavItem>
</> </>

View File

@ -1,6 +1,6 @@
import type { APIError } from "./types"; 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<T>( export default async function fetchAPI<T>(
path: string, path: string,

View File

@ -14,6 +14,7 @@
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-markdown": "^8.0.3", "react-markdown": "^8.0.3",
"react-sortablejs": "^6.1.4", "react-sortablejs": "^6.1.4",
"react-toast": "^1.0.3",
"recoil": "^0.7.5", "recoil": "^0.7.5",
"sortablejs": "^1.15.0" "sortablejs": "^1.15.0"
}, },

View File

@ -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<State> = async (
context
) => {
try {
const resp = await fetchAPI<CallbackResponse>(
"/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 } };
}
};

View File

@ -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 (
<>
<Head>
<title key="title">Login - pronouns.cc</title>
</Head>
<a href={urls.discord}>Login with Discord</a>
</>
);
}
export const getServerSideProps: GetServerSideProps = async (context) => {
try {
const urls = await fetchAPI<URLsResponse>("/auth/urls", "POST", {
callback_domain: process.env.DOMAIN,
});
return { props: { urls } };
} catch (e) {
console.log(e);
return { notFound: true };
}
};

View File

@ -6,23 +6,45 @@ import FieldCard from "../../../components/FieldCard";
import Card from "../../../components/Card"; import Card from "../../../components/Card";
import ReactMarkdown from "react-markdown"; import ReactMarkdown from "react-markdown";
import Image from "next/image"; import Image from "next/image";
import { userState } from "../../../lib/state";
import { useRecoilValue } from "recoil";
import Link from "next/link";
interface Props { interface Props {
user: User; user: User;
} }
export default function Index({ user }: Props) { export default function Index({ user }: Props) {
const isMeUser = useRecoilValue(userState)?.id === user.id;
return ( return (
<> <>
<Head> <Head>
<title key="title">@{user.username} - pronouns.cc</title> <title key="title">@{user.username} - pronouns.cc</title>
</Head> </Head>
{isMeUser && (
<div className="lg:w-1/3 mx-auto bg-slate-100 dark:bg-slate-700 shadow rounded-md p-2">
<span>
You are currently viewing your{" "}
<span className="font-bold">public</span> profile.
</span>
<br />
<Link
href="/edit/profile"
className="hover:underline text-sky-500 dark:text-sky-400"
>
Edit your profile
</Link>
</div>
)}
<div className="container mx-auto"> <div className="container mx-auto">
<div className="flex flex-col m-2 p-2 lg:flex-row justify-center lg:justify-start items-center space-y-4 lg:space-y-0 lg:space-x-16 lg:items-start border-b border-slate-200 dark:border-slate-700"> <div className="flex flex-col m-2 p-2 lg:flex-row justify-center lg:justify-start items-center space-y-4 lg:space-y-0 lg:space-x-16 lg:items-start border-b border-slate-200 dark:border-slate-700">
{user.avatar_url && ( {user.avatar_url && (
<Image <img
className="max-w-xs rounded-full" className="max-w-xs rounded-full"
src={user.avatar_url} src={user.avatar_url}
//width="20rem"
//height="20rem"
alt={`@${user.username}'s avatar`} alt={`@${user.username}'s avatar`}
/> />
)} )}

View File

@ -2097,6 +2097,11 @@ react-sortablejs@^6.1.4:
classnames "2.3.1" classnames "2.3.1"
tiny-invariant "1.2.0" 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: react@18.2.0:
version "18.2.0" version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"