feat(frontend): log in with Discord
This commit is contained in:
		
							parent
							
								
									e4d028bbad
								
							
						
					
					
						commit
						4a8e1bb54f
					
				
					 8 changed files with 158 additions and 3 deletions
				
			
		
							
								
								
									
										10
									
								
								frontend/components/Loading.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								frontend/components/Loading.tsx
									
										
									
									
									
										Normal 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> | ||||
|   ); | ||||
| } | ||||
|  | @ -56,7 +56,9 @@ export default function Navigation() { | |||
| 
 | ||||
|   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="/logout">Log out</NavItem> | ||||
|     </> | ||||
|  |  | |||
|  | @ -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<T>( | ||||
|   path: string, | ||||
|  |  | |||
|  | @ -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" | ||||
|   }, | ||||
|  |  | |||
							
								
								
									
										74
									
								
								frontend/pages/login/discord.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								frontend/pages/login/discord.tsx
									
										
									
									
									
										Normal 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 } }; | ||||
|   } | ||||
| }; | ||||
							
								
								
									
										41
									
								
								frontend/pages/login/index.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								frontend/pages/login/index.tsx
									
										
									
									
									
										Normal 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 }; | ||||
|   } | ||||
| }; | ||||
|  | @ -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 ( | ||||
|     <> | ||||
|       <Head> | ||||
|         <title key="title">@{user.username} - pronouns.cc</title> | ||||
|       </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="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 && ( | ||||
|             <Image | ||||
|             <img | ||||
|               className="max-w-xs rounded-full" | ||||
|               src={user.avatar_url} | ||||
|               //width="20rem"
 | ||||
|               //height="20rem"
 | ||||
|               alt={`@${user.username}'s avatar`} | ||||
|             /> | ||||
|           )} | ||||
|  |  | |||
|  | @ -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" | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue