Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
96f8332
Add Better Auth integration with user, session, account, and verifica…
jakejarvis Oct 26, 2025
518c162
Update .env.example to include NEXT_PUBLIC_APP_URL and enhance AppHea…
jakejarvis Oct 26, 2025
f201795
Add authentication middleware and create login and dashboard pages
jakejarvis Oct 26, 2025
a290b2c
Add test router and implement authentication middleware for session m…
jakejarvis Oct 26, 2025
0276c53
Add user_domains table and verification method enum to database schema
jakejarvis Oct 26, 2025
727dcfa
Add domains router for domain ownership verification and implement ve…
jakejarvis Oct 26, 2025
c05fc2b
Add email service integration with Resend for domain registration exp…
jakejarvis Oct 26, 2025
918fba7
Add email templates for password reset and email verification
jakejarvis Oct 26, 2025
119ee49
Integrate magic link authentication and simplify user menu sign-in flow
jakejarvis Oct 26, 2025
e2b85aa
Add domain snapshots and notification log/preferences tables to datab…
jakejarvis Oct 26, 2025
54257c7
Add notification system for domain changes, including email templates…
jakejarvis Oct 26, 2025
babec1a
Refactor dashboard page to use client-side session management and enh…
jakejarvis Oct 26, 2025
242505b
Refactor routing imports to use custom useRouter hook across componen…
jakejarvis Oct 26, 2025
17a6932
Remove unused authentication demo and test pages to streamline the ap…
jakejarvis Oct 26, 2025
2be6ed1
Add @react-email/render dependency, remove magic link example, and up…
jakejarvis Oct 26, 2025
a031568
Enhance section revalidation by converting domains to registrable for…
jakejarvis Oct 26, 2025
d16cd24
Implement login page with Better Auth integration, including magic li…
jakejarvis Oct 26, 2025
508f82a
Refactor copy functionality to use custom hook for clipboard operatio…
jakejarvis Oct 26, 2025
9718faa
Add settings page with account and notification management; implement…
jakejarvis Oct 26, 2025
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
17 changes: 17 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,20 @@ NEXT_PUBLIC_MAPBOX_TOKEN=

# Optional: override user agent sent with upstream requests
EXTERNAL_USER_AGENT=

# Better Auth authentication
BETTER_AUTH_SECRET=
BETTER_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_APP_URL=http://localhost:3000

# Optional: GitHub OAuth
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=

# Optional: Google OAuth
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=

# Resend email service
RESEND_API_KEY=
RESEND_FROM_EMAIL=
4 changes: 4 additions & 0 deletions app/api/auth/[...all]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { toNextJsHandler } from "better-auth/next-js";
import { auth } from "@/lib/auth/config";

export const { GET, POST } = toNextJsHandler(auth.handler);
3 changes: 2 additions & 1 deletion app/api/inngest/route.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { serve } from "inngest/next";
import { inngest } from "@/lib/inngest/client";
import { checkExpirations } from "@/lib/inngest/functions/check-expirations";
import { sectionRevalidate } from "@/lib/inngest/functions/section-revalidate";

// opt out of caching per Inngest docs
export const dynamic = "force-dynamic";

export const { GET, POST, PUT } = serve({
client: inngest,
functions: [sectionRevalidate],
functions: [sectionRevalidate, checkExpirations],
});
51 changes: 51 additions & 0 deletions app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"use client";

import { DomainList } from "@/components/dashboard/domain-list";
import { useRouter } from "@/hooks/use-router";
import { useSession } from "@/lib/auth/client";

export default function DashboardPage() {
const { data: session, isPending } = useSession();
const router = useRouter();

// Show loading state while checking auth
if (isPending) {
return (
<div className="container mx-auto max-w-6xl px-4 py-6">
<div className="space-y-6">
<div className="flex items-center justify-between">
<div className="space-y-2">
<div className="h-9 w-48 animate-pulse rounded-md bg-muted" />
<div className="h-4 w-64 animate-pulse rounded bg-muted" />
</div>
</div>
<div className="h-96 animate-pulse rounded-xl bg-muted" />
</div>
</div>
);
}

if (!session) {
router.push("/login?redirect=/dashboard");
return null;
}

return (
<div className="container mx-auto max-w-6xl px-4 py-6">
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="font-semibold text-3xl tracking-tight">
My Domains
</h1>
<p className="text-muted-foreground text-sm">
Monitor and manage your verified domains
</p>
</div>
</div>

<DomainList />
</div>
</div>
);
}
65 changes: 65 additions & 0 deletions app/domains/verify/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"use client";

import { useQuery } from "@tanstack/react-query";
import { notFound, useParams } from "next/navigation";
import { VerificationInstructions } from "@/components/dashboard/verification-instructions";
import { VerificationInstructionsSkeleton } from "@/components/dashboard/verification-instructions-skeleton";
import { useRouter } from "@/hooks/use-router";
import { useSession } from "@/lib/auth/client";
import { useTRPC } from "@/lib/trpc/client";

export default function VerifyDomainPage() {
const params = useParams<{ id: string }>();
const { data: session, isPending: sessionPending } = useSession();
const trpc = useTRPC();
const router = useRouter();

// Fetch user domain details (call hooks before any conditional returns)
const {
data: userDomain,
isPending: domainPending,
isError,
} = useQuery(trpc.domains.getForVerification.queryOptions({ id: params.id }));

// Wait for session
if (sessionPending) {
return (
<div className="container mx-auto max-w-3xl px-4 py-6">
<VerificationInstructionsSkeleton />
</div>
);
}

if (!session) {
router.push("/login?redirect=/dashboard");
return null;
}

if (domainPending) {
return (
<div className="container mx-auto max-w-3xl px-4 py-6">
<VerificationInstructionsSkeleton />
</div>
);
}

if (isError || !userDomain) {
notFound();
}

// If already verified, redirect to dashboard
if (userDomain.verified) {
router.push("/dashboard");
return null;
}

return (
<div className="container mx-auto max-w-3xl px-4 py-6">
<VerificationInstructions
userDomainId={userDomain.id}
domainName={userDomain.domain}
verificationToken={userDomain.verificationToken}
/>
</div>
);
}
36 changes: 36 additions & 0 deletions app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"use client";

import { useSearchParams } from "next/navigation";
import { LoginForm } from "@/components/auth/login-form";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";

/**
* Login page with Better Auth integration.
* Supports magic link email and OAuth (GitHub, Google) authentication.
*/
export default function LoginPage() {
const searchParams = useSearchParams();
const redirectTo = searchParams.get("redirect") || "/dashboard";

return (
<div className="container mx-auto flex min-h-screen items-center justify-center p-4">
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>Sign in</CardTitle>
<CardDescription>
Choose your preferred sign in method
</CardDescription>
</CardHeader>
<CardContent>
<LoginForm redirectTo={redirectTo} />
</CardContent>
</Card>
</div>
);
}
42 changes: 42 additions & 0 deletions app/settings/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"use client";

import { redirect } from "next/navigation";
import { SettingsLayout } from "@/components/settings/settings-layout";
import { useSession } from "@/lib/auth/client";

export default function SettingsPage() {
const { data: session, isPending } = useSession();

if (isPending) {
return (
<div className="container mx-auto max-w-4xl px-4 py-6">
<div className="space-y-6">
<div className="space-y-2">
<div className="h-8 w-48 animate-pulse rounded bg-muted" />
<div className="h-4 w-96 animate-pulse rounded bg-muted" />
</div>
<div className="h-96 animate-pulse rounded-xl bg-muted" />
</div>
</div>
);
}

if (!session) {
redirect("/login?redirect=/settings");
}

return (
<div className="container mx-auto max-w-4xl px-4 py-6">
<div className="space-y-6">
<div>
<h1 className="font-semibold text-3xl tracking-tight">Settings</h1>
<p className="text-muted-foreground text-sm">
Manage your account and notification preferences
</p>
</div>

<SettingsLayout />
</div>
</div>
);
}
4 changes: 4 additions & 0 deletions components/app-header.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Link from "next/link";
import { UserMenu } from "@/components/auth/user-menu";
import { Bookmarklet } from "@/components/bookmarklet";
import { HeaderSearch } from "@/components/domain/header-search";
import { GithubStars } from "@/components/github-stars";
Expand Down Expand Up @@ -26,6 +27,9 @@ export function AppHeader() {
{/* Theme toggle is always shown */}
<Separator orientation="vertical" className="!h-4" />
<ThemeToggle />
{/* User authentication */}
<Separator orientation="vertical" className="!h-4" />
<UserMenu />
</div>
</header>
);
Expand Down
Loading