180 lines
5.4 KiB
TypeScript
180 lines
5.4 KiB
TypeScript
import Link from "next/link";
|
|
import QRCode from "qrcode";
|
|
import { PaymentMethodType } from "@prisma/client";
|
|
import { auth } from "@/lib/auth";
|
|
import { buildPaymentUri } from "@/lib/payment-uri";
|
|
import { ProfileHeader } from "@/components/public/profile-header";
|
|
import { PaymentMethodCard } from "@/components/public/payment-method-card";
|
|
import { buttonStyles } from "@/components/ui/button";
|
|
|
|
const previewProfile = {
|
|
displayName: "Your Name",
|
|
username: "yourname",
|
|
bio: "A public page where people can copy your payment details in one place.",
|
|
avatarUrl: null,
|
|
};
|
|
|
|
const previewMethodsBase: Array<{
|
|
id: string;
|
|
type: PaymentMethodType;
|
|
label: string;
|
|
value: string;
|
|
network: string | null;
|
|
description: string | null;
|
|
isVisible: boolean;
|
|
}> = [
|
|
{
|
|
id: "preview-monero",
|
|
type: "BITCOIN",
|
|
label: "Bitcoin",
|
|
value: "bc1qrp8uudvq5rr5l0nuepkxvxayyny2ws2w0m8jz3",
|
|
network: null,
|
|
description: null,
|
|
isVisible: true,
|
|
},
|
|
{
|
|
id: "preview-paypal",
|
|
type: "PAYPAL",
|
|
label: "PayPal",
|
|
value: "https://paypal.me/yourname",
|
|
network: null,
|
|
description: null,
|
|
isVisible: true,
|
|
},
|
|
];
|
|
|
|
const howItWorks = [
|
|
"Create an account and choose your public username.",
|
|
"Add payment methods and optional social/contact links.",
|
|
"Share your profile URL so people can copy details or scan QR.",
|
|
];
|
|
|
|
const whyPayMe = [
|
|
"No custody: PayMe does not hold funds.",
|
|
"No processing: PayMe does not execute transactions.",
|
|
"Self-hosted: run it on your own infrastructure.",
|
|
"Open source: inspect and modify everything.",
|
|
"Privacy-friendly: minimal profile data, no tracking layer built in.",
|
|
];
|
|
|
|
export default async function HomePage() {
|
|
const session = await auth();
|
|
const previewMethods = await Promise.all(
|
|
previewMethodsBase.map(async (method) => {
|
|
const qrPayload = buildPaymentUri(
|
|
method.type,
|
|
method.value,
|
|
method.network,
|
|
);
|
|
let qrDataUrl: string | null = null;
|
|
|
|
try {
|
|
qrDataUrl = await QRCode.toDataURL(qrPayload, {
|
|
margin: 1,
|
|
width: 220,
|
|
});
|
|
} catch {
|
|
qrDataUrl = null;
|
|
}
|
|
|
|
return {
|
|
...method,
|
|
qrPayload,
|
|
qrDataUrl,
|
|
};
|
|
}),
|
|
);
|
|
|
|
const primaryHref = session?.user ? "/dashboard" : "/register";
|
|
const primaryLabel = session?.user ? "Open dashboard" : "Create your profile";
|
|
|
|
return (
|
|
<main className="terminal-shell md:px-8 md:py-16">
|
|
<section className="space-y-7">
|
|
<div className="space-y-4">
|
|
<p className="terminal-heading">PayMe</p>
|
|
<h1 className="max-w-3xl text-3xl font-semibold leading-tight md:text-5xl">
|
|
One public page for every way people can pay you.
|
|
</h1>
|
|
<p className="max-w-3xl text-lg leading-relaxed text-muted">
|
|
Self-hosted payment profile pages for creators, freelancers, and OSS
|
|
maintainers. No custody. No transaction processing. Just clear
|
|
payment details and links.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex flex-wrap items-center gap-3 md:gap-4">
|
|
<Link
|
|
href={primaryHref}
|
|
className={buttonStyles({
|
|
variant: "primary",
|
|
className:
|
|
"relative -top-1 min-h-[3.1rem] min-w-[13rem] justify-center px-6",
|
|
})}
|
|
>
|
|
{primaryLabel}
|
|
</Link>
|
|
<a
|
|
href="#example"
|
|
className={buttonStyles({
|
|
variant: "secondary",
|
|
className:
|
|
"relative -top-1 min-h-[3.1rem] min-w-[10.75rem] justify-center px-5",
|
|
})}
|
|
style={{ marginLeft: "14px" }}
|
|
>
|
|
View example
|
|
</a>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="example" className="mt-8 space-y-5 md:mt-10">
|
|
<div className="space-y-2">
|
|
<h2 className="text-2xl font-bold">Profile preview</h2>
|
|
<p className="text-sm text-muted">
|
|
How a public PayMe profile is presented.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="space-y-8">
|
|
<ProfileHeader
|
|
displayName={previewProfile.displayName}
|
|
username={previewProfile.username}
|
|
bio={previewProfile.bio}
|
|
avatarUrl={previewProfile.avatarUrl}
|
|
/>
|
|
|
|
<section className="terminal-section space-y-4">
|
|
<h3 className="terminal-heading">Payment Methods</h3>
|
|
<div className="space-y-3">
|
|
{previewMethods.map((method) => (
|
|
<PaymentMethodCard key={method.id} method={method} />
|
|
))}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="terminal-section grid gap-5 md:grid-cols-2 md:gap-6 md:pt-10">
|
|
<div className="terminal-card space-y-4 md:p-6">
|
|
<h2 className="text-2xl font-bold">How it works</h2>
|
|
<ol className="list-decimal space-y-3 pl-5 text-sm leading-relaxed text-muted">
|
|
{howItWorks.map((item) => (
|
|
<li key={item}>{item}</li>
|
|
))}
|
|
</ol>
|
|
</div>
|
|
|
|
<div className="terminal-card space-y-4 md:p-6">
|
|
<h2 className="text-2xl font-bold">Why PayMe</h2>
|
|
<ul className="list-none space-y-3 p-0 text-sm leading-relaxed text-muted">
|
|
{whyPayMe.map((item) => (
|
|
<li key={item}>{item}</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
);
|
|
}
|