Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .content-collections/generated/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// generated by content-collections at Wed Dec 25 2024 20:28:37 GMT-0500 (Eastern Standard Time)
// generated by content-collections at Sat Jan 04 2025 18:53:35 GMT+0100 (Central European Standard Time)

import allDocs from "./allDocs.js";
import allPages from "./allPages.js";
Expand Down
17 changes: 17 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# TODOs

- decide design
- write 1st version of docs
- set up npm package
- landing page

decide how to organize the codebase
do we need js files in .content-collections?

- add at least 3 components before making a video about it:
- auth
- landing page
- stripe

landing page:
- design it for our website and then use a very simple version to release
12 changes: 12 additions & 0 deletions app/(auth)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"use client";

import { ReactNode } from "react";
import { SessionProvider } from "next-auth/react";

interface AuthLayoutProps {
children: ReactNode;
}

export default function AuthLayout({ children }: AuthLayoutProps) {
return <SessionProvider>{children}</SessionProvider>;
}
121 changes: 121 additions & 0 deletions app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"use client";

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { signIn } from "next-auth/react";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Icons } from "@/components/ui/icons/icons";

const loginSchema = z.object({
email: z.string().email("Invalid email address"),
password: z.string().min(1, "Password is required"),
});

type LoginFormValues = z.infer<typeof loginSchema>;

export default function LoginPage() {
const router = useRouter();
const form = useForm<LoginFormValues>({
resolver: zodResolver(loginSchema),
defaultValues: {
email: "",
password: "",
},
});

const onSubmit = async (values: LoginFormValues) => {
const result = await signIn("credentials", {
redirect: false,
email: values.email,
password: values.password,
});

if (result?.error) {
toast.error(result.error);
} else {
toast.success("Successfully logged in!");
router.push("/");
}
};

const handleGitHubSignIn = () => {
signIn("github", { callbackUrl: "/" });
toast.success("Successfully signed in with GitHub!");
};

const handleGoogleSignIn = () => {
signIn("google", { callbackUrl: "/" });
};

return (
<div className="flex justify-center items-center min-h-screen">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="w-full max-w-md">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="your-email@example.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input type="password" placeholder="•••••••" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full mt-4">
Login
</Button>

<div className="relative my-4">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Or continue with
</span>
</div>
</div>

<div className="grid grid-cols-2 gap-6">
<Button onClick={handleGoogleSignIn} variant="outline" type="button">
<Icons.google className="mr-2 h-4 w-4" />
Google
</Button>
<Button variant="outline" onClick={handleGitHubSignIn} type="button">
<Icons.gitHub className="mr-2 h-4 w-4" />
Github
</Button>
</div>
</form>
</Form>
</div>
);
}
132 changes: 132 additions & 0 deletions app/(auth)/signup/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
"use client";

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import axios from "axios";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Icons } from "@/components/ui/icons/icons";
import { signIn } from "next-auth/react";

const signupSchema = z.object({
name: z.string().min(1, "Name is required"),
email: z.string().email("Invalid email address"),
password: z.string().min(6, "Password must be at least 6 characters"),
});

type SignupFormValues = z.infer<typeof signupSchema>;

export default function SignupPage() {
const router = useRouter();
const form = useForm<SignupFormValues>({
resolver: zodResolver(signupSchema),
defaultValues: {
name: "",
email: "",
password: "",
},
});

const onSubmit = async (values: SignupFormValues) => {
try {
await axios.post("/api/auth/signup", values);
toast.success("Account created successfully!");
router.push("/auth/login");
} catch (error) {
toast.error((error as Error).message || "Something went wrong");
}
};

const handleGitHubSignIn = () => {
signIn("github", { callbackUrl: "/" });
toast.success("Successfully signed in with GitHub!");
};

const handleGoogleSignIn = () => {
signIn("google", { callbackUrl: "/" });
};

return (
<div className="flex justify-center items-center min-h-screen">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="w-full max-w-md">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="John Doe" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="your-email@example.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input type="password" placeholder="•••••••" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full mt-4">
Sign Up
</Button>

<div className="relative my-4">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Or continue with
</span>
</div>
</div>

<div className="grid grid-cols-2 gap-6">
<Button onClick={handleGoogleSignIn} variant="outline" type="button">
<Icons.google className="mr-2 h-4 w-4" />
Google
</Button>
<Button variant="outline" onClick={handleGitHubSignIn} type="button">
<Icons.gitHub className="mr-2 h-4 w-4" />
Github
</Button>
</div>
</form>
</Form>
</div>
);
}
8 changes: 4 additions & 4 deletions app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { authOptions } from "@/lib/auth";
import NextAuth from "next-auth/next";
import NextAuth from "next-auth"
import { authOptions } from "@/lib/auth"

const handler = NextAuth(authOptions);
const handler = NextAuth(authOptions)

export { handler as GET, handler as POST };
export { handler as GET, handler as POST }
Loading