diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..bf506ef
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,45 @@
+name: Nextblog CI
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+
+# This cancels previous runs if you push again to the same PR
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ quality-check:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Code
+ uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v3
+ with:
+ version: 9
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: "20"
+ cache: "pnpm"
+
+ - name: Install Dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Run Format Check
+ run: pnpm run format:check
+
+ - name: Run Lint
+ run: pnpm run lint
+
+ - name: Type Check
+ run: pnpm exec tsc --noEmit
+
+ - name: Build Project
+ run: pnpm run build
diff --git a/AGENTS.md b/AGENTS.md
index 8bd0e39..c153a9b 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,5 +1,7 @@
+
# This is NOT the Next.js you know
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
+
diff --git a/app/(shared-layout)/layout.tsx b/app/(shared-layout)/layout.tsx
new file mode 100644
index 0000000..7efb839
--- /dev/null
+++ b/app/(shared-layout)/layout.tsx
@@ -0,0 +1,14 @@
+import Navbar from "@/components/web/navbar";
+
+export default function SharedLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+ <>
+
+ {children}
+ >
+ );
+}
diff --git a/app/page.tsx b/app/(shared-layout)/page.tsx
similarity index 51%
rename from app/page.tsx
rename to app/(shared-layout)/page.tsx
index 2a34784..fdf7bbd 100644
--- a/app/page.tsx
+++ b/app/(shared-layout)/page.tsx
@@ -1,3 +1,3 @@
export default function Page() {
- return
Home Page
-}
\ No newline at end of file
+ return Home Page
;
+}
diff --git a/app/auth/login/page.tsx b/app/auth/login/page.tsx
index 095a345..d50f837 100644
--- a/app/auth/login/page.tsx
+++ b/app/auth/login/page.tsx
@@ -1,3 +1,3 @@
export default function LoginPage() {
- return Login Page
-}
\ No newline at end of file
+ return Login Page
;
+}
diff --git a/app/auth/sign-up/layout.tsx b/app/auth/sign-up/layout.tsx
new file mode 100644
index 0000000..7f2137a
--- /dev/null
+++ b/app/auth/sign-up/layout.tsx
@@ -0,0 +1,21 @@
+import { buttonVariants } from "@/components/ui/button";
+import { ArrowLeft } from "lucide-react";
+import Link from "next/link";
+
+export default function authLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+ );
+}
diff --git a/app/auth/sign-up/page.tsx b/app/auth/sign-up/page.tsx
index 6828276..f99cb57 100644
--- a/app/auth/sign-up/page.tsx
+++ b/app/auth/sign-up/page.tsx
@@ -1,3 +1,107 @@
+"use client";
+
+import { signUpSchema } from "@/app/schemas/auth";
+import { z } from "zod";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { Controller, useForm } from "react-hook-form";
+import { Input } from "@/components/ui/input";
+import {
+ Field,
+ FieldError,
+ FieldGroup,
+ FieldLabel,
+} from "@/components/ui/field";
+import { Button } from "@/components/ui/button";
+
export default function SignupPage() {
- return Sign Up Page
-}
\ No newline at end of file
+ const form = useForm>({
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ resolver: zodResolver(signUpSchema as any),
+ defaultValues: {
+ name: "",
+ email: "",
+ password: "",
+ },
+ });
+
+ const onSubmit = () => {
+ console.log("yoo");
+ };
+
+ return (
+
+
+ Sign Up
+ Create a new account to get started
+
+
+
+
+
+ );
+}
diff --git a/app/globals.css b/app/globals.css
index c56032b..6a25b1b 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -127,4 +127,4 @@
html {
@apply font-sans;
}
-}
\ No newline at end of file
+}
diff --git a/app/layout.tsx b/app/layout.tsx
index 846b50b..bf63635 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,12 +1,11 @@
import type { Metadata } from "next";
import React from "react";
-import { Geist, Geist_Mono } from "next/font/google";
+import { Outfit, Geist_Mono } from "next/font/google";
import "./globals.css";
-import Navbar from "@/components/web/navbar";
import { ThemeProvider } from "@/components/ui/theme-provider";
-const geistSans = Geist({
- variable: "--font-geist-sans",
+const outfit = Outfit({
+ variable: "--font-sans",
subsets: ["latin"],
});
@@ -27,7 +26,7 @@ export default function RootLayout({
return (
-
{children}
diff --git a/app/schemas/auth.ts b/app/schemas/auth.ts
new file mode 100644
index 0000000..90b1d56
--- /dev/null
+++ b/app/schemas/auth.ts
@@ -0,0 +1,10 @@
+import z from "zod";
+
+export const signUpSchema = z.object({
+ name: z
+ .string()
+ .min(3, "Name must be at least 3 characters long")
+ .max(30, "Name must be at most 30 characters long"),
+ email: z.string().email("Invalid email address"),
+ password: z.string().min(6, "Password must be at least 6 characters long"),
+});
diff --git a/components/ui/button.tsx b/components/ui/button.tsx
index c0d08ef..c34204d 100644
--- a/components/ui/button.tsx
+++ b/components/ui/button.tsx
@@ -1,8 +1,8 @@
-import * as React from "react"
-import { cva, type VariantProps } from "class-variance-authority"
-import { Slot } from "radix-ui"
+import * as React from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+import { Slot } from "radix-ui";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
const buttonVariants = cva(
"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
@@ -38,8 +38,8 @@ const buttonVariants = cva(
variant: "default",
size: "default",
},
- }
-)
+ },
+);
function Button({
className,
@@ -49,9 +49,9 @@ function Button({
...props
}: React.ComponentProps<"button"> &
VariantProps & {
- asChild?: boolean
+ asChild?: boolean;
}) {
- const Comp = asChild ? Slot.Root : "button"
+ const Comp = asChild ? Slot.Root : "button";
return (
- )
+ );
}
-export { Button, buttonVariants }
+export { Button, buttonVariants };
diff --git a/components/ui/dropdown-menu.tsx b/components/ui/dropdown-menu.tsx
index c263ad5..3e3e145 100644
--- a/components/ui/dropdown-menu.tsx
+++ b/components/ui/dropdown-menu.tsx
@@ -1,15 +1,15 @@
-"use client"
+"use client";
-import * as React from "react"
-import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"
+import * as React from "react";
+import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
-import { cn } from "@/lib/utils"
-import { CheckIcon, ChevronRightIcon } from "lucide-react"
+import { cn } from "@/lib/utils";
+import { CheckIcon, ChevronRightIcon } from "lucide-react";
function DropdownMenu({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function DropdownMenuPortal({
@@ -17,7 +17,7 @@ function DropdownMenuPortal({
}: React.ComponentProps) {
return (
- )
+ );
}
function DropdownMenuTrigger({
@@ -28,7 +28,7 @@ function DropdownMenuTrigger({
data-slot="dropdown-menu-trigger"
{...props}
/>
- )
+ );
}
function DropdownMenuContent({
@@ -43,11 +43,14 @@ function DropdownMenuContent({
data-slot="dropdown-menu-content"
sideOffset={sideOffset}
align={align}
- className={cn("z-50 max-h-(--radix-dropdown-menu-content-available-height) w-(--radix-dropdown-menu-trigger-width) min-w-32 origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:overflow-hidden data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
+ className={cn(
+ "z-50 max-h-(--radix-dropdown-menu-content-available-height) w-(--radix-dropdown-menu-trigger-width) min-w-32 origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:overflow-hidden data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
+ className,
+ )}
{...props}
/>
- )
+ );
}
function DropdownMenuGroup({
@@ -55,7 +58,7 @@ function DropdownMenuGroup({
}: React.ComponentProps) {
return (
- )
+ );
}
function DropdownMenuItem({
@@ -64,8 +67,8 @@ function DropdownMenuItem({
variant = "default",
...props
}: React.ComponentProps & {
- inset?: boolean
- variant?: "default" | "destructive"
+ inset?: boolean;
+ variant?: "default" | "destructive";
}) {
return (
- )
+ );
}
function DropdownMenuCheckboxItem({
@@ -88,7 +91,7 @@ function DropdownMenuCheckboxItem({
inset,
...props
}: React.ComponentProps & {
- inset?: boolean
+ inset?: boolean;
}) {
return (
-
+
{children}
- )
+ );
}
function DropdownMenuRadioGroup({
@@ -123,7 +125,7 @@ function DropdownMenuRadioGroup({
data-slot="dropdown-menu-radio-group"
{...props}
/>
- )
+ );
}
function DropdownMenuRadioItem({
@@ -132,7 +134,7 @@ function DropdownMenuRadioItem({
inset,
...props
}: React.ComponentProps & {
- inset?: boolean
+ inset?: boolean;
}) {
return (
@@ -149,13 +151,12 @@ function DropdownMenuRadioItem({
data-slot="dropdown-menu-radio-item-indicator"
>
-
+
{children}
- )
+ );
}
function DropdownMenuLabel({
@@ -163,7 +164,7 @@ function DropdownMenuLabel({
inset,
...props
}: React.ComponentProps & {
- inset?: boolean
+ inset?: boolean;
}) {
return (
- )
+ );
}
function DropdownMenuSeparator({
@@ -188,7 +189,7 @@ function DropdownMenuSeparator({
className={cn("-mx-1 my-1 h-px bg-border", className)}
{...props}
/>
- )
+ );
}
function DropdownMenuShortcut({
@@ -200,17 +201,17 @@ function DropdownMenuShortcut({
data-slot="dropdown-menu-shortcut"
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function DropdownMenuSub({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function DropdownMenuSubTrigger({
@@ -219,7 +220,7 @@ function DropdownMenuSubTrigger({
children,
...props
}: React.ComponentProps & {
- inset?: boolean
+ inset?: boolean;
}) {
return (
{children}
- )
+ );
}
function DropdownMenuSubContent({
@@ -244,10 +245,13 @@ function DropdownMenuSubContent({
return (
- )
+ );
}
export {
@@ -266,4 +270,4 @@ export {
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
-}
+};
diff --git a/components/ui/field.tsx b/components/ui/field.tsx
new file mode 100644
index 0000000..9871b0d
--- /dev/null
+++ b/components/ui/field.tsx
@@ -0,0 +1,238 @@
+"use client";
+
+import { useMemo } from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "@/lib/utils";
+import { Label } from "@/components/ui/label";
+import { Separator } from "@/components/ui/separator";
+
+function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
+ return (
+