Skip to content

Commit

Permalink
feat: implement sign up page
Browse files Browse the repository at this point in the history
  • Loading branch information
sunaurus committed Apr 14, 2024
1 parent 7f719f0 commit f33b675
Show file tree
Hide file tree
Showing 14 changed files with 342 additions and 38 deletions.
4 changes: 4 additions & 0 deletions src/app/(utils)/getFormBoolean.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const getFormBoolean = (
formData: FormData,
fieldName: string,
): boolean => formData.get(fieldName)?.toString() === "on";
11 changes: 8 additions & 3 deletions src/app/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,14 @@ export const Navbar = async () => {
/>
</StyledLink>
) : (
<StyledLink className={"text-neutral-200"} href={"/login"}>
{"Log in"}
</StyledLink>
<>
<StyledLink className={"mx-2 text-neutral-200"} href={"/login"}>
{"Log in"}
</StyledLink>
<StyledLink className={"text-neutral-200"} href={"/signup"}>
{"Sign up"}
</StyledLink>
</>
)}
</span>
</nav>
Expand Down
18 changes: 16 additions & 2 deletions src/app/apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ const fetchWithNextConfig = async (
): Promise<Response> => {
const additionalHeaders: Record<string, string> = {};
const jwt = await getJwtFromAuthCookie();
let isAuthenticatedUser = false;

if (jwt) {
additionalHeaders["authorization"] = `Bearer ${jwt}`;
isAuthenticatedUser = true;
}

const incomingHeaders = headers();
Expand All @@ -33,15 +35,27 @@ const fetchWithNextConfig = async (
additionalHeaders["x-real-ip"] = realIp;
}

// Cache API responses for 15 seconds by default
let revalidate = 15;

// Don't cache responses for authenticated users
if (isAuthenticatedUser) {
revalidate = 0;
}

// Never cache captchas
if (input.toString().includes("/api/v3/user/get_captcha")) {
revalidate = 0;
}

const res = await fetch(input, {
...init,
headers: {
...init?.headers,
...additionalHeaders,
},
next: {
// Cache API responses for 15 seconds for logged out users
revalidate: jwt !== null ? 0 : 15,
revalidate,
},
});

Expand Down
2 changes: 1 addition & 1 deletion src/app/create_post/PostEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export const PostEditor = (props: {
/>
</div>
<input
autoComplete={"false"}
autoComplete={"off"}
className={"hidden"}
id={"honey"}
name={"honey"}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const PrivateMessageEditor = (props: {
/>
</div>
<input
autoComplete={"false"}
autoComplete={"off"}
className={"hidden"}
id={"honey"}
name={"honey"}
Expand Down
6 changes: 3 additions & 3 deletions src/app/error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const Error = ({
return (
<main className={"flex h-full flex-col items-center justify-center"}>
<h2 className={"mt-20 text-center text-2xl"}>
{"Something went wrong!"}
{error.message ? `Error: ${error.message}` : "Something went wrong!"}
</h2>
<div>
{
Expand All @@ -31,8 +31,8 @@ export const Error = ({
</>
)}
<button
className={`bg-primary-500 hover:bg-primary-400 mt-4 rounded-md px-4 py-2 text-sm text-white
transition-colors`}
className={`mt-4 rounded-md bg-primary-500 px-4 py-2 text-sm text-white transition-colors
hover:bg-primary-400`}
onClick={() => reset()}
>
{"Try again"}
Expand Down
4 changes: 2 additions & 2 deletions src/app/login/authActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const loginAction = async (
throw new Error("Authentication failed");
}

setAuthCookie(loginResponse.jwt);
await setAuthCookie(loginResponse.jwt);

redirect(redirectUrl ?? "/");
};
Expand Down Expand Up @@ -60,7 +60,7 @@ export const isLoggedIn = async (): Promise<boolean> => {

const oneMonthMillis = 30 * 24 * 60 * 60 * 1000;

const setAuthCookie = (token: string) => {
export const setAuthCookie = async (token: string) => {
cookies().set({
name: AUTH_COOKIE_NAME,
value: token,
Expand Down
7 changes: 2 additions & 5 deletions src/app/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const LoginPage = async (props: {
return (
<div
className={
"flex min-h-full flex-1 flex-col items-center justify-center px-4 py-12 lg:px-8"
"mt-8 flex flex-1 flex-col items-center justify-center px-4 lg:px-8"
}
>
{siteView.site.banner && (
Expand All @@ -35,10 +35,7 @@ const LoginPage = async (props: {

<p className={"mt-10 text-center text-sm"}>
{"No account?"}{" "}
<StyledLink
className={"hover:text-primary-400 font-semibold leading-6"}
href={"/signup"}
>
<StyledLink className={"font-semibold leading-6"} href={"/signup"}>
{"Sign up here"}
</StyledLink>
</p>
Expand Down
11 changes: 4 additions & 7 deletions src/app/login_reset/sent/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { Input } from "@/app/(ui)/form/Input";
import { SubmitButton } from "@/app/(ui)/button/SubmitButton";

const ForgotPasswordPage = () => {
const ResetLinkSentPage = () => {
return (
<div className={"m-2 lg:mx-4"}>
<h1 className={"text-xl font-bold"}>{"Forgot password"}</h1>
<div className={"mt-10 max-w-[600px] text-neutral-300"}>
<h1 className={"mb-2 text-xl font-bold"}>{"Forgot password"}</h1>
<div className={"max-w-[600px] text-neutral-300"}>
{"A password reset link has been sent to your e-mail."}
</div>
</div>
);
};

export default ForgotPasswordPage;
export default ResetLinkSentPage;
21 changes: 10 additions & 11 deletions src/app/settings/loggedInUserActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
SortType,
} from "lemmy-js-client";
import { revalidatePath } from "next/cache";
import { getFormBoolean } from "@/app/(utils)/getFormBoolean";

export type UnreadCounts = {
inbox: GetUnreadCountResponse;
Expand Down Expand Up @@ -47,20 +48,18 @@ export const updateSettingsAction = async (formData: FormData) => {
| ListingType
| undefined,
default_sort_type: formData.get("sort")?.toString() as SortType | undefined,
show_nsfw: getFormBoolean(formData.get("nsfw_show")),
blur_nsfw: getFormBoolean(formData.get("nsfw_blur")),
auto_expand: getFormBoolean(formData.get("auto_expand")),
show_scores: getFormBoolean(formData.get("show_scores")),
bot_account: getFormBoolean(formData.get("is_bot")),
show_bot_accounts: getFormBoolean(formData.get("show_bots")),
show_read_posts: getFormBoolean(formData.get("show_read_posts")),
show_nsfw: getFormBoolean(formData, "nsfw_show"),
blur_nsfw: getFormBoolean(formData, "nsfw_blur"),
auto_expand: getFormBoolean(formData, "auto_expand"),
show_scores: getFormBoolean(formData, "show_scores"),
bot_account: getFormBoolean(formData, "is_bot"),
show_bot_accounts: getFormBoolean(formData, "show_bots"),
show_read_posts: getFormBoolean(formData, "show_read_posts"),
send_notifications_to_email: getFormBoolean(
formData.get("notification_emails"),
formData,
"notification_emails",
),
});
revalidatePath("/settings");
revalidatePath("/");
};

const getFormBoolean = (value: FormDataEntryValue | null): boolean =>
value?.toString() === "on";
169 changes: 169 additions & 0 deletions src/app/signup/SignupForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
"use client";

import { Input } from "@/app/(ui)/form/Input";
import { SubmitButton } from "@/app/(ui)/button/SubmitButton";
import { signupAction } from "@/app/signup/signupActions";
import { CaptchaResponse, SiteView } from "lemmy-js-client";
import { TextArea } from "@/app/(ui)/form/TextArea";
import { Markdown } from "@/app/(ui)/markdown/Markdown";

export const SignupForm = (props: {
readonly siteView: SiteView;
readonly captcha?: CaptchaResponse;
}) => {
return (
<form
action={signupAction.bind(null, props.captcha?.uuid)}
className={"space-y-6"}
>
<input
autoComplete={"off"}
className={"hidden"}
id={"honey"}
name={"honey"}
type={"text"}
/>
<div>
<label
className={"block text-sm font-medium leading-6"}
htmlFor={"username"}
>
{"Username"}
</label>
<Input
className={"mt-2"}
id={"username"}
minLength={3}
name={"username"}
pattern={"[a-zA-Z0-9_]+"}
required
/>
</div>
<div>
<label
className={"block text-sm font-medium leading-6"}
htmlFor={"email"}
>
{"E-mail"}
</label>
<Input
autoComplete={"email"}
className={"mt-2"}
id={"email"}
name={"email"}
required={props.siteView.local_site.require_email_verification}
type={"email"}
/>
</div>

<div>
<div>
<label
className={"block text-sm font-medium leading-6"}
htmlFor={"password"}
>
{"Password"}
</label>
</div>
<Input
autoComplete={"new-password"}
className={"mt-2"}
id={"password"}
maxLength={60}
minLength={10}
name={"password"}
required
type={"password"}
/>
</div>

<div>
<div>
<label
className={"block text-sm font-medium leading-6"}
htmlFor={"password_verify"}
>
{"Verify password"}
</label>
</div>
<Input
autoComplete={"new-password"}
className={"mt-2"}
id={"password_verify"}
maxLength={60}
minLength={10}
name={"password_verify"}
required
type={"password"}
/>
</div>

{props.siteView.local_site.registration_mode === "RequireApplication" && (
<>
<div>
<div
className={
"rounded border border-amber-600 bg-amber-950 p-2 text-sm text-amber-200"
}
>
{
"To join this server, you need to fill out an application, and wait to be accepted."
}
</div>
<div className={"flex items-center justify-between"}>
<label
className={"block text-sm font-medium leading-6"}
htmlFor={"twofactor"}
>
{"Application instructions"}
</label>
</div>
<Markdown
content={props.siteView.local_site.application_question ?? ""}
/>{" "}
</div>
<div>
<div className={"flex items-center justify-between"}>
<label
className={"block text-sm font-medium leading-6"}
htmlFor={"twofactor"}
>
{"Answer"}
</label>
</div>
<TextArea className={"mt-2"} id={"answer"} name={"answer"} />
</div>
</>
)}

{props.captcha && (
<div>
<img
alt={"Captcha"}
className={"rounded"}
src={`data:image/png;base64,${props.captcha.png}`}
/>
<label
className={"mt-2 block text-sm font-medium leading-6"}
htmlFor={"captcha_answer"}
>
{"Enter text displayed on the captcha image"}
</label>
<Input
className={"mt-2"}
id={"captcha_answer"}
minLength={3}
name={"captcha_answer"}
required
/>
</div>
)}

<div>
<SubmitButton className={"w-full leading-6"} color={"primary"}>
{"Sign up"}
</SubmitButton>
</div>
</form>
);
};
Loading

0 comments on commit f33b675

Please sign in to comment.