feat(frontend): add discord callback page
this only handles existing accounts for now, still need to write an action function
This commit is contained in:
parent
116d0577a7
commit
ff22530f0a
13 changed files with 196 additions and 66 deletions
|
@ -6,6 +6,15 @@ export type AuthResponse = {
|
|||
expires_at: string;
|
||||
};
|
||||
|
||||
export type CallbackResponse = {
|
||||
has_account: boolean;
|
||||
ticket?: string;
|
||||
remote_username?: string;
|
||||
user?: User;
|
||||
token?: string;
|
||||
expires_at?: string;
|
||||
};
|
||||
|
||||
export type AuthUrls = {
|
||||
discord?: string;
|
||||
google?: string;
|
||||
|
|
|
@ -23,6 +23,7 @@ import "./app.scss";
|
|||
import getLocalSettings from "./lib/settings.server";
|
||||
import { LANGUAGE } from "~/env.server";
|
||||
import { errorCodeDesc } from "./components/ErrorAlert";
|
||||
import { Container } from "react-bootstrap";
|
||||
|
||||
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
const meta = await serverRequest<Meta>("GET", "/meta");
|
||||
|
@ -141,7 +142,9 @@ export default function App() {
|
|||
return (
|
||||
<>
|
||||
<Navbar meta={meta} user={meUser} settings={settings} />
|
||||
<Outlet />
|
||||
<Container>
|
||||
<Outlet />
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
84
Foxnouns.Frontend/app/routes/auth.callback.discord/route.tsx
Normal file
84
Foxnouns.Frontend/app/routes/auth.callback.discord/route.tsx
Normal file
|
@ -0,0 +1,84 @@
|
|||
import { json, LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { type ApiError, ErrorCode } from "~/lib/api/error";
|
||||
import serverRequest, { writeCookie } from "~/lib/request.server";
|
||||
import { CallbackResponse } from "~/lib/api/auth";
|
||||
import { Form as RemixForm, Link, useLoaderData } from "@remix-run/react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import Form from "react-bootstrap/Form";
|
||||
import Button from "react-bootstrap/Button";
|
||||
|
||||
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
const url = new URL(request.url);
|
||||
|
||||
const code = url.searchParams.get("code");
|
||||
const state = url.searchParams.get("state");
|
||||
|
||||
if (!code || !state)
|
||||
throw { status: 400, code: ErrorCode.BadRequest, message: "Missing code or state" } as ApiError;
|
||||
|
||||
const resp = await serverRequest<CallbackResponse>("POST", "/auth/discord/callback", {
|
||||
body: { code, state }
|
||||
});
|
||||
|
||||
if (resp.has_account) {
|
||||
return json(
|
||||
{ hasAccount: true, user: resp.user!, ticket: null, remoteUser: null },
|
||||
{
|
||||
headers: {
|
||||
"Set-Cookie": writeCookie("pronounscc-token", resp.token!)
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return json({
|
||||
hasAccount: false,
|
||||
user: null,
|
||||
ticket: resp.ticket!,
|
||||
remoteUser: resp.remote_username!
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: action function
|
||||
|
||||
export default function DiscordCallbackPage() {
|
||||
const { t } = useTranslation();
|
||||
const data = useLoaderData<typeof loader>();
|
||||
|
||||
if (data.hasAccount) {
|
||||
const username = data.user!.username;
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>{t("log-in.callback.success")}</h1>
|
||||
<p>
|
||||
<Trans t={t} i18nKey={"log-in.callback.success-link"} values={{ username: data.user!.username }}>
|
||||
{/* @ts-expect-error react-i18next handles interpolation here */}
|
||||
Welcome back, <Link to={`/@${data.user!.username}`}>@{{username}}</Link>!
|
||||
</Trans>
|
||||
<br />
|
||||
{t("log-in.callback.redirect-hint")}
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RemixForm method="POST">
|
||||
<Form as="div">
|
||||
<Form.Group className="mb-3" controlId="remote-username">
|
||||
<Form.Label>{t("log-in.callback.remote-username.discord")}</Form.Label>
|
||||
<Form.Control type="text" readOnly={true} value={data.remoteUser!} />
|
||||
</Form.Group>
|
||||
<Form.Group className="mb-3" controlId="username">
|
||||
<Form.Label>{t("log-in.callback.username")}</Form.Label>
|
||||
<Form.Control name="username" type="text" required />
|
||||
</Form.Group>
|
||||
<input type="hidden" name="ticket" value={data.ticket!} />
|
||||
<Button variant="primary" type="submit">
|
||||
{t("log-in.callback.sign-up-button")}
|
||||
</Button>
|
||||
</Form>
|
||||
</RemixForm>
|
||||
);
|
||||
}
|
|
@ -10,7 +10,7 @@ import Form from "react-bootstrap/Form";
|
|||
import Button from "react-bootstrap/Button";
|
||||
import ButtonGroup from "react-bootstrap/ButtonGroup";
|
||||
import ListGroup from "react-bootstrap/ListGroup";
|
||||
import { Container, Row, Col } from "react-bootstrap";
|
||||
import { Row, Col } from "react-bootstrap";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import i18n from "~/i18next.server";
|
||||
import serverRequest, { getToken, writeCookie } from "~/lib/request.server";
|
||||
|
@ -72,7 +72,7 @@ export default function LoginPage() {
|
|||
const actionData = useActionData<typeof action>();
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<>
|
||||
<Row>
|
||||
<Col md className="mb-4">
|
||||
<h2>{t("log-in.form-title")}</h2>
|
||||
|
@ -121,7 +121,7 @@ export default function LoginPage() {
|
|||
</ListGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,42 +1,52 @@
|
|||
{
|
||||
"error": {
|
||||
"heading": "An error occurred",
|
||||
"errors": {
|
||||
"authentication-error": "There was an error validating your credentials.",
|
||||
"authentication-required": "You need to log in.",
|
||||
"bad-request": "Server rejected your input, please check anything for errors.",
|
||||
"forbidden": "You are not allowed to perform that action.",
|
||||
"generic-error": "An unknown error occurred.",
|
||||
"internal-server-error": "Server experienced an internal error, please try again later.",
|
||||
"member-not-found": "Member not found, please check your spelling and try again.",
|
||||
"user-not-found": "User not found, please check your spelling and try again."
|
||||
},
|
||||
"title": "Error"
|
||||
},
|
||||
"navbar": {
|
||||
"view-profile": "View profile",
|
||||
"settings": "Settings",
|
||||
"log-out": "Log out",
|
||||
"log-in": "Log in or sign up",
|
||||
"theme": "Theme",
|
||||
"theme-auto": "Automatic",
|
||||
"theme-dark": "Dark",
|
||||
"theme-light": "Light"
|
||||
},
|
||||
"log-in": {
|
||||
"title": "Log in",
|
||||
"form-title": "Log in with email",
|
||||
"email": "Email address",
|
||||
"password": "Password",
|
||||
"log-in-button": "Log in",
|
||||
"register-with-email": "Register with email",
|
||||
"3rd-party": {
|
||||
"title": "Log in with another service",
|
||||
"desc": "If you prefer, you can also log in with one of these services:",
|
||||
"discord": "Log in with Discord",
|
||||
"google": "Log in with Google",
|
||||
"tumblr": "Log in with Tumblr"
|
||||
},
|
||||
"invalid-credentials": "Invalid email address or password, please check your spelling and try again."
|
||||
}
|
||||
"error": {
|
||||
"heading": "An error occurred",
|
||||
"errors": {
|
||||
"authentication-error": "There was an error validating your credentials.",
|
||||
"authentication-required": "You need to log in.",
|
||||
"bad-request": "Server rejected your input, please check anything for errors.",
|
||||
"forbidden": "You are not allowed to perform that action.",
|
||||
"generic-error": "An unknown error occurred.",
|
||||
"internal-server-error": "Server experienced an internal error, please try again later.",
|
||||
"member-not-found": "Member not found, please check your spelling and try again.",
|
||||
"user-not-found": "User not found, please check your spelling and try again."
|
||||
},
|
||||
"title": "Error"
|
||||
},
|
||||
"navbar": {
|
||||
"view-profile": "View profile",
|
||||
"settings": "Settings",
|
||||
"log-out": "Log out",
|
||||
"log-in": "Log in or sign up",
|
||||
"theme": "Theme",
|
||||
"theme-auto": "Automatic",
|
||||
"theme-dark": "Dark",
|
||||
"theme-light": "Light"
|
||||
},
|
||||
"log-in": {
|
||||
"callback": {
|
||||
"success": "Successfully logged in!",
|
||||
"success-link": "Welcome back, <1>@{{username}}</1>!",
|
||||
"redirect-hint": "If you're not redirected to your profile in a few seconds, press the link above.",
|
||||
"remote-username": {
|
||||
"discord": "Your discord username"
|
||||
},
|
||||
"username": "Username",
|
||||
"sign-up-button": "Sign up"
|
||||
},
|
||||
"title": "Log in",
|
||||
"form-title": "Log in with email",
|
||||
"email": "Email address",
|
||||
"password": "Password",
|
||||
"log-in-button": "Log in",
|
||||
"register-with-email": "Register with email",
|
||||
"3rd-party": {
|
||||
"title": "Log in with another service",
|
||||
"desc": "If you prefer, you can also log in with one of these services:",
|
||||
"discord": "Log in with Discord",
|
||||
"google": "Log in with Google",
|
||||
"tumblr": "Log in with Tumblr"
|
||||
},
|
||||
"invalid-credentials": "Invalid email address or password, please check your spelling and try again."
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue