Skip to content

Commit

Permalink
remove auth from checkout page, make order history show no orders whe…
Browse files Browse the repository at this point in the history
…n signed in, use 2 server actions depending on button click
  • Loading branch information
RhysSullivan committed Oct 19, 2024
1 parent a34f7cb commit 17e8a3f
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 136 deletions.
86 changes: 39 additions & 47 deletions src/app/(login)/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,48 +12,10 @@ import { authRateLimit, signUpRateLimit } from "@/lib/rate-limit";
const authSchema = z.object({
username: z.string().min(1),
password: z.string().min(1),
mode: z.enum(["signin", "signup"]),
});

async function signIn(input: z.infer<typeof authSchema>) {
const { username, password } = input;
const ip = (await headers()).get("x-real-ip") ?? "local";
const rl = await authRateLimit.limit(ip);

if (!rl.success) {
return {
error: {
code: "AUTH_ERROR",
message: "Too many attempts. Try again later",
},
};
}
const user = await db
.select({
user: users,
})
.from(users)
.where(eq(users.username, username))
.limit(1);

if (user.length === 0) {
return { error: "Invalid email or password. Please try again." };
}

const { user: foundUser } = user[0];

const isPasswordValid = await comparePasswords(
password,
foundUser.passwordHash,
);

if (!isPasswordValid) {
return { error: "Invalid email or password. Please try again." };
}
await setSession(foundUser);
}
async function signUp(input: z.infer<typeof authSchema>) {
const { username, password } = input;
export const signUp = validatedAction(authSchema, async (data) => {
const { username, password } = data;
const ip = (await headers()).get("x-real-ip") ?? "local";
const rl2 = await signUpRateLimit.limit(ip);
if (!rl2.success) {
Expand Down Expand Up @@ -88,14 +50,44 @@ async function signUp(input: z.infer<typeof authSchema>) {
return { error: "Failed to create user. Please try again." };
}
await setSession(createdUser);
}
export const signInSignUp = validatedAction(authSchema, async (data) => {
const { mode } = data;
if (mode === "signin") {
return signIn(data);
} else {
return signUp(data);
});

export const signIn = validatedAction(authSchema, async (data) => {
const { username, password } = data;
const ip = (await headers()).get("x-real-ip") ?? "local";
const rl = await authRateLimit.limit(ip);

if (!rl.success) {
return {
error: {
code: "AUTH_ERROR",
message: "Too many attempts. Try again later",
},
};
}
const user = await db
.select({
user: users,
})
.from(users)
.where(eq(users.username, username))
.limit(1);

if (user.length === 0) {
return { error: "Invalid email or password. Please try again." };
}

const { user: foundUser } = user[0];

const isPasswordValid = await comparePasswords(
password,
foundUser.passwordHash,
);

if (!isPasswordValid) {
return { error: "Invalid email or password. Please try again." };
}
await setSession(foundUser);
});

export async function signOut() {
Expand Down
150 changes: 75 additions & 75 deletions src/app/auth.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,82 @@ import {
PopoverTrigger,
} from "@/components/ui/popover";
import { ActionState } from "@/lib/middleware";
import { signInSignUp } from "./(login)/actions";
import { signIn, signUp } from "./(login)/actions";
import { useActionState } from "react";
import { Button } from "@/components/ui/button";

export function SignInSignUp() {
const [state, formAction, pending] = useActionState<ActionState, FormData>(
signInSignUp,
{ error: "" },
export function LoginForm() {
const [signInState, signInFormAction, signInPending] = useActionState<
ActionState,
FormData
>(signIn, { error: "" });
const [signUpState, signUpFormAction, signUpPending] = useActionState<
ActionState,
FormData
>(signUp, { error: "" });
const pending = signInPending || signUpPending;
const state = signInState.error ? signInState : signUpState;

return (
<form className="flex flex-col space-y-6">
<div className="flex flex-col gap-4">
<div className="mt-1">
<Input
id="username"
name="username"
aria-label="Username"
type="text"
autoComplete="username"
spellCheck={false}
required
maxLength={50}
className="relative block w-full appearance-none rounded-[1px] border px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:border-orange-500 focus:outline-none focus:ring-orange-500 sm:text-sm"
placeholder="Username"
/>
</div>

<div>
<div className="mt-1">
<Input
id="password"
name="password"
aria-label="Password"
type="password"
required
maxLength={100}
className="relative block w-full appearance-none rounded-[1px] border px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:border-orange-500 focus:outline-none focus:ring-orange-500 sm:text-sm"
placeholder="Password"
/>
</div>
</div>

<Button
type="submit"
className="rounded-[1px] bg-green-800 px-4 py-2 text-xs font-semibold text-white shadow-sm hover:bg-green-900 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2"
disabled={pending}
formAction={signInFormAction}
>
{"Log in"}
</Button>

<Button
type="submit"
variant={"ghost"}
className="rounded-[2px] border-[1px] border-green-800 bg-white px-4 py-2 text-xs font-semibold text-green-800"
disabled={pending}
formAction={signUpFormAction}
>
{"Create login"}
</Button>
</div>
{state?.error && (
<div className="text-sm text-red-500">{state.error}</div>
)}
</form>
);
}

export function SignInSignUp() {
return (
<Popover>
<PopoverTrigger className="flex flex-row items-center gap-1">
Expand All @@ -23,76 +90,9 @@ export function SignInSignUp() {
<polygon points="0,0 5,6 10,0"></polygon>
</svg>
</PopoverTrigger>
<PopoverContent className="flex flex-col px-8 py-4">
<form className="space-y-6" action={formAction}>
<span className="text-sm font-semibold text-green-800">Log in</span>
<div className="flex flex-col gap-4">
<div className="mt-1">
<Input
id="username"
name="username"
aria-label="Username"
type="text"
autoComplete="username"
spellCheck={false}
required
maxLength={50}
className="relative block w-full appearance-none rounded-[1px] border px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:border-orange-500 focus:outline-none focus:ring-orange-500 sm:text-sm"
placeholder="Username"
/>
</div>

<div>
<div className="mt-1">
<Input
id="password"
name="password"
aria-label="Password"
type="password"
required
maxLength={100}
className="relative block w-full appearance-none rounded-[1px] border px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:border-orange-500 focus:outline-none focus:ring-orange-500 sm:text-sm"
placeholder="Password"
/>
</div>
</div>
{/* todo: rather than using a hidden input, we could use the formAction prop on button */}
{/* store sign in / sign up here */}
<input type="hidden" id="mode" name="mode" />

<Button
type="submit"
className="rounded-[1px] bg-green-800 px-4 py-2 text-xs font-semibold text-white shadow-sm hover:bg-green-900 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2"
disabled={pending}
onClick={() => {
const mode = document.getElementById("mode");
if (mode && mode instanceof HTMLInputElement) {
mode.value = "signin";
}
}}
>
{"Log in"}
</Button>

<Button
type="submit"
variant={"ghost"}
className="rounded-[2px] border-[1px] border-green-800 bg-white px-4 py-2 text-xs font-semibold text-green-800"
disabled={pending}
onClick={() => {
const mode = document.getElementById("mode");
if (mode && mode instanceof HTMLInputElement) {
mode.value = "signup";
}
}}
>
{"Create login"}
</Button>
</div>
{state?.error && (
<div className="text-sm text-red-500">{state.error}</div>
)}
</form>
<PopoverContent className="px-8 py-4">
<span className="text-sm font-semibold text-green-800">Log in</span>
<LoginForm />
</PopoverContent>
</Popover>
);
Expand Down
15 changes: 14 additions & 1 deletion src/app/auth.server.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getUser } from "@/lib/queries";
import { SignInSignUp, SignOut } from "./auth.client";
import { LoginForm, SignInSignUp, SignOut } from "./auth.client";

export async function AuthServer() {
const user = await getUser();
Expand All @@ -8,3 +8,16 @@ export async function AuthServer() {
}
return <SignOut username={user.username} />;
}

export async function PlaceOrderAuth() {
const user = await getUser();
if (user) {
return null;
}
return (
<>
<p className="font-semibold text-green-800">Log in to place an order</p>
<LoginForm />
</>
);
}
22 changes: 14 additions & 8 deletions src/app/order-history/page.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import { LoginForm } from "@/components/login-form";
import { getUser } from "@/lib/queries";
import { Metadata } from "next";

export const metadata: Metadata = {
title: "Order History",
};

export default function Page() {
export default async function Page() {
const user = await getUser();
return (
<main className="min-h-screen p-4">
<div className="mx-auto flex max-w-md flex-col gap-4">
<h1 className="font-futura text-2xl text-green-800">ORDER HISTORY</h1>
<p className="font-semibold text-green-800">
Log in to view order history
</p>
<LoginForm />
<h1 className="w-full border-b-2 border-green-800 text-left font-futura text-2xl text-green-800">
Order History
</h1>
<div className="mx-auto flex max-w-md flex-col gap-4 text-black">
{user ? (
<p className="font-semibold text-black">You have no orders yet.</p>
) : (
<p className="font-semibold text-black">
Log in to view order history
</p>
)}
</div>
</main>
);
Expand Down
9 changes: 4 additions & 5 deletions src/app/order/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { LoginForm } from "@/components/login-form";
import { Metadata } from "next";
import { Suspense } from "react";
import { CartItems, TotalCost } from "./dynamic";
import { PlaceOrderAuth } from "../auth.server";

export const metadata: Metadata = {
title: "Order",
Expand Down Expand Up @@ -34,10 +34,9 @@ export default async function Page() {
Applicable shipping and tax will be added.
</p>
</div>
<p className="font-semibold text-green-800">
Log in to place an order
</p>
<LoginForm />
<Suspense>
<PlaceOrderAuth />
</Suspense>
</div>
</div>
</div>
Expand Down

0 comments on commit 17e8a3f

Please sign in to comment.