Initial commit
This commit is contained in:
33
app/(dashboard)/dashboard/layout.tsx
Normal file
33
app/(dashboard)/dashboard/layout.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import Link from "next/link";
|
||||
import type { ReactNode } from "react";
|
||||
import { SignOutButton } from "@/components/auth/signout-button";
|
||||
import { Sidebar } from "@/components/dashboard/sidebar";
|
||||
import { requireCurrentUser } from "@/lib/session";
|
||||
|
||||
export default async function DashboardLayout({
|
||||
children
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const user = await requireCurrentUser();
|
||||
|
||||
return (
|
||||
<div className="dashboard-shell flex min-h-screen flex-col">
|
||||
<header className="mb-7 flex flex-wrap items-center justify-between gap-4 border-b border-border/80 pb-5">
|
||||
<div className="space-y-1">
|
||||
<Link href="/" className="terminal-heading">
|
||||
PayMe
|
||||
</Link>
|
||||
<p className="text-sm text-muted">{user.email}</p>
|
||||
</div>
|
||||
|
||||
<SignOutButton />
|
||||
</header>
|
||||
|
||||
<div className="flex flex-col gap-7 md:flex-row">
|
||||
<Sidebar username={user.profile?.username} />
|
||||
<main className="min-w-0 flex-1">{children}</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
43
app/(dashboard)/dashboard/page.tsx
Normal file
43
app/(dashboard)/dashboard/page.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { db } from "@/lib/db";
|
||||
import { requireCurrentUser } from "@/lib/session";
|
||||
|
||||
export default async function DashboardOverviewPage() {
|
||||
const user = await requireCurrentUser();
|
||||
|
||||
const profile = await db.profile.findUnique({
|
||||
where: { userId: user.id },
|
||||
include: {
|
||||
paymentMethods: true,
|
||||
socialLinks: true
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<section className="space-y-8">
|
||||
<div className="space-y-2">
|
||||
<h1 className="text-2xl font-semibold">Overview</h1>
|
||||
<p className="text-sm text-muted">
|
||||
Manage your public payment profile. PayMe only displays payment details and links.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
<article className="terminal-card py-5">
|
||||
<p className="terminal-heading">Payment Methods</p>
|
||||
<p className="mt-4 text-3xl font-semibold">{profile?.paymentMethods.length ?? 0}</p>
|
||||
</article>
|
||||
|
||||
<article className="terminal-card py-5">
|
||||
<p className="terminal-heading">Social Links</p>
|
||||
<p className="mt-4 text-3xl font-semibold">{profile?.socialLinks.length ?? 0}</p>
|
||||
</article>
|
||||
|
||||
<article className="terminal-card py-5">
|
||||
<p className="terminal-heading">Public Visibility</p>
|
||||
<p className="mt-4 text-3xl font-semibold">{profile?.isPublic ? "On" : "Off"}</p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
);
|
||||
}
|
||||
45
app/(dashboard)/dashboard/payment-methods/page.tsx
Normal file
45
app/(dashboard)/dashboard/payment-methods/page.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import { PaymentMethodsForm } from "@/components/dashboard/payment-methods-form";
|
||||
import { PaymentMethodsList } from "@/components/dashboard/payment-methods-list";
|
||||
import { db } from "@/lib/db";
|
||||
import { requireCurrentUser } from "@/lib/session";
|
||||
|
||||
export default async function DashboardPaymentMethodsPage() {
|
||||
const user = await requireCurrentUser();
|
||||
|
||||
const profile = await db.profile.findUnique({
|
||||
where: { userId: user.id },
|
||||
include: {
|
||||
paymentMethods: {
|
||||
select: {
|
||||
id: true,
|
||||
type: true,
|
||||
label: true,
|
||||
value: true,
|
||||
network: true,
|
||||
description: true,
|
||||
sortOrder: true,
|
||||
isVisible: true
|
||||
},
|
||||
orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!profile) {
|
||||
return <p className="text-sm text-red-300">Profile not found.</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="space-y-6">
|
||||
<header className="space-y-2">
|
||||
<h1 className="text-2xl font-semibold">Payment Methods</h1>
|
||||
<p className="text-sm text-muted">
|
||||
Add, edit, reorder, and toggle payment methods. Validation is format-based only and not authoritative.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<PaymentMethodsForm />
|
||||
<PaymentMethodsList methods={profile.paymentMethods} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
37
app/(dashboard)/dashboard/profile/page.tsx
Normal file
37
app/(dashboard)/dashboard/profile/page.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { ProfileForm } from "@/components/dashboard/profile-form";
|
||||
import { db } from "@/lib/db";
|
||||
import { DEFAULT_THEME_ID } from "@/lib/constants";
|
||||
import { requireCurrentUser } from "@/lib/session";
|
||||
|
||||
export default async function DashboardProfilePage() {
|
||||
const user = await requireCurrentUser();
|
||||
const [profile, themes] = await Promise.all([
|
||||
db.profile.findUnique({
|
||||
where: { userId: user.id }
|
||||
}),
|
||||
db.theme.findMany({ orderBy: { name: "asc" } })
|
||||
]);
|
||||
|
||||
return (
|
||||
<section className="space-y-6">
|
||||
<header className="space-y-2">
|
||||
<h1 className="text-2xl font-semibold">Profile</h1>
|
||||
<p className="text-sm text-muted">
|
||||
Update how your public page appears. Username changes update your public URL.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<ProfileForm
|
||||
initialValues={{
|
||||
username: profile?.username ?? "",
|
||||
displayName: profile?.displayName ?? "",
|
||||
bio: profile?.bio ?? "",
|
||||
avatarUrl: profile?.avatarUrl ?? "",
|
||||
themeId: profile?.themeId ?? DEFAULT_THEME_ID,
|
||||
isPublic: profile?.isPublic ?? true
|
||||
}}
|
||||
themes={themes}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
40
app/(dashboard)/dashboard/social-links/page.tsx
Normal file
40
app/(dashboard)/dashboard/social-links/page.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { SocialLinksForm } from "@/components/dashboard/social-links-form";
|
||||
import { db } from "@/lib/db";
|
||||
import { requireCurrentUser } from "@/lib/session";
|
||||
|
||||
export default async function DashboardSocialLinksPage() {
|
||||
const user = await requireCurrentUser();
|
||||
|
||||
const profile = await db.profile.findUnique({
|
||||
where: { userId: user.id },
|
||||
include: {
|
||||
socialLinks: {
|
||||
select: {
|
||||
id: true,
|
||||
label: true,
|
||||
url: true,
|
||||
sortOrder: true,
|
||||
isVisible: true
|
||||
},
|
||||
orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!profile) {
|
||||
return <p className="text-sm text-red-300">Profile not found.</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="space-y-6">
|
||||
<header className="space-y-2">
|
||||
<h1 className="text-2xl font-semibold">Social Links</h1>
|
||||
<p className="text-sm text-muted">
|
||||
Optional links to contact points and social profiles. Keep this short and relevant.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<SocialLinksForm links={profile.socialLinks} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user