Skip to content

Commit

Permalink
Added settings route
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinreber committed Nov 17, 2024
1 parent f9196c0 commit 3f8fe82
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 3 deletions.
59 changes: 59 additions & 0 deletions @/components/ui/alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const alertVariants = cva(
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
{
variants: {
variant: {
default: "bg-background text-foreground",
destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
},
},
defaultVariants: {
variant: "default",
},
}
)

const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
))
Alert.displayName = "Alert"

const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
))
AlertTitle.displayName = "AlertTitle"

const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props}
/>
))
AlertDescription.displayName = "AlertDescription"

export { Alert, AlertTitle, AlertDescription }
139 changes: 139 additions & 0 deletions app/routes/settings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { json, type ActionFunctionArgs } from "@remix-run/node";
import { Form, useActionData } from "@remix-run/react";
import { prisma } from "~/services/prisma.server";
import { requireUserLogin } from "~/services/auth.server";
import { z } from "zod";
import { PageContainer } from "~/components";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { useLoggedInUser } from "~/hooks";

const UsernameSchema = z
.string()
.min(3, "Username must be at least 3 characters long")
.max(30, "Username cannot exceed 30 characters")
.regex(
/^[a-zA-Z0-9_-]+$/,
"Username can only contain letters, numbers, underscores, and hyphens"
)
.transform((username) => username.toLowerCase());

export async function loader({ request }: ActionFunctionArgs) {
const user = await requireUserLogin(request);
return json({ user });
}

export async function action({ request }: ActionFunctionArgs) {
const user = await requireUserLogin(request);
if (!user) {
throw new Response("Unauthorized", { status: 401 });
}

const formData = await request.formData();
const rawUsername = formData.get("username");

const usernameResult = UsernameSchema.safeParse(rawUsername);

if (!usernameResult.success) {
return json(
{ error: usernameResult.error.errors[0].message },
{ status: 400 }
);
}

const username = usernameResult.data;

try {
const existingUser = await prisma.user.findFirst({
where: {
username,
NOT: {
id: user.id,
},
},
});

if (existingUser) {
return json(
{ error: "This username is already taken. Please choose another one." },
{ status: 400 }
);
}

const updatedUser = await prisma.user.update({
where: { id: user.id },
data: { username },
});

return json({ success: true, user: updatedUser });
} catch (error) {
console.error("Error updating username:", error);
return json(
{ error: "Failed to update username. Please try again." },
{ status: 500 }
);
}
}

export default function SettingsPage() {
const user = useLoggedInUser();
const actionData = useActionData<typeof action>();

return (
<PageContainer>
<div className="max-w-2xl mx-auto py-10">
<Card>
<CardHeader>
<CardTitle className="text-2xl font-bold">
Account Settings
</CardTitle>
</CardHeader>
<CardContent>
<Form method="post" className="space-y-6">
<div className="space-y-2">
<Label htmlFor="username">Username</Label>
<Input
type="text"
id="username"
name="username"
defaultValue={user.username}
minLength={3}
maxLength={30}
pattern="^[a-zA-Z0-9_-]+$"
required
className="max-w-md"
/>
<p className="text-sm text-muted-foreground">
Username can only contain letters, numbers, underscores, and
hyphens.
</p>
</div>

{actionData?.error && (
<div className="text-red-500 text-sm font-medium">
{actionData.error}
</div>
)}

{actionData?.success && (
<div className="text-green-500 text-sm font-medium">
Username updated successfully!
</div>
)}

<Button
type="submit"
className="w-full sm:w-auto"
variant="outline"
>
Update Username
</Button>
</Form>
</CardContent>
</Card>
</div>
</PageContainer>
);
}
2 changes: 1 addition & 1 deletion app/services/stripe.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const stripeCheckout = async ({ userId }: { userId: string }) => {
});

return session.url!;
} catch (error: any) {
} catch (error: Error) {
console.error(error);
throw json({ errors: [{ message: error.message }] }, 400);
}
Expand Down
5 changes: 3 additions & 2 deletions app/services/webhook.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const handleStripeEvent = async (
}

switch (type) {
case CHECKOUT_SESSION_COMPLETED:
case CHECKOUT_SESSION_COMPLETED: {
const checkoutSessionCompleted = data.object as {
id: string;
amount: number;
Expand All @@ -29,7 +29,7 @@ export const handleStripeEvent = async (

const creditsToAdd = 100;
console.log("CHECKOUT SESSION COMPLETED: ", checkoutSessionCompleted);
// Update users credits in DB after checkout

const userData = await prisma.user.update({
where: {
id: checkoutSessionCompleted.metadata.userId,
Expand All @@ -42,6 +42,7 @@ export const handleStripeEvent = async (
});
console.log("DONE UPDATING USER DATA: ", userData);
return userData;
}

default:
console.log(`Unhandled event type: ${type}`);
Expand Down

0 comments on commit 3f8fe82

Please sign in to comment.