Compare commits

...

3 Commits

4 changed files with 90 additions and 44 deletions

View File

@@ -14,6 +14,7 @@
* { * {
box-sizing: border-box; box-sizing: border-box;
border-color: rgb(var(--color-border)); border-color: rgb(var(--color-border));
border-radius: 0 !important;
} }
html, html,

View File

@@ -1,32 +1,52 @@
import Link from "next/link"; import Link from "next/link";
import QRCode from "qrcode";
import { PaymentMethodType } from "@prisma/client";
import { auth } from "@/lib/auth"; 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"; import { buttonStyles } from "@/components/ui/button";
const previewRows = [ 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;
}> = [
{ {
title: "PayPal", id: "preview-monero",
meta: "paypal.me/alexdev", type: "BITCOIN",
value: "alexdev", label: "Bitcoin",
actions: "copy · open" value: "bc1qrp8uudvq5rr5l0nuepkxvxayyny2ws2w0m8jz3",
network: null,
description: null,
isVisible: true,
}, },
{ {
title: "USDT", id: "preview-paypal",
meta: "TRC20", type: "PAYPAL",
value: "TQ6...k2S", label: "PayPal",
actions: "copy · qr" value: "https://paypal.me/yourname",
network: null,
description: null,
isVisible: true,
}, },
{
title: "Bank Transfer",
meta: "IBAN",
value: "DE89 3704 0044 0532 0130 00",
actions: "copy"
}
]; ];
const howItWorks = [ const howItWorks = [
"Create an account and choose your public username.", "Create an account and choose your public username.",
"Add payment methods and optional social/contact links.", "Add payment methods and optional social/contact links.",
"Share your profile URL so people can copy details or scan QR." "Share your profile URL so people can copy details or scan QR.",
]; ];
const whyPayMe = [ const whyPayMe = [
@@ -34,11 +54,36 @@ const whyPayMe = [
"No processing: PayMe does not execute transactions.", "No processing: PayMe does not execute transactions.",
"Self-hosted: run it on your own infrastructure.", "Self-hosted: run it on your own infrastructure.",
"Open source: inspect and modify everything.", "Open source: inspect and modify everything.",
"Privacy-friendly: minimal profile data, no tracking layer built in." "Privacy-friendly: minimal profile data, no tracking layer built in.",
]; ];
export default async function HomePage() { export default async function HomePage() {
const session = await auth(); 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 primaryHref = session?.user ? "/dashboard" : "/register";
const primaryLabel = session?.user ? "Open dashboard" : "Create your profile"; const primaryLabel = session?.user ? "Open dashboard" : "Create your profile";
@@ -52,8 +97,9 @@ export default async function HomePage() {
One public page for every way people can pay you. One public page for every way people can pay you.
</h1> </h1>
<p className="max-w-3xl text-lg leading-relaxed text-muted"> <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 Self-hosted payment profile pages for creators, freelancers, and OSS
transaction processing. Just clear payment details and links. maintainers. No custody. No transaction processing. Just clear
payment details and links.
</p> </p>
</div> </div>
@@ -62,7 +108,8 @@ export default async function HomePage() {
href={primaryHref} href={primaryHref}
className={buttonStyles({ className={buttonStyles({
variant: "primary", variant: "primary",
className: "relative -top-1 min-h-[3.1rem] min-w-[13rem] justify-center px-6" className:
"relative -top-1 min-h-[3.1rem] min-w-[13rem] justify-center px-6",
})} })}
> >
{primaryLabel} {primaryLabel}
@@ -71,7 +118,8 @@ export default async function HomePage() {
href="#example" href="#example"
className={buttonStyles({ className={buttonStyles({
variant: "secondary", variant: "secondary",
className: "relative -top-1 min-h-[3.1rem] min-w-[10.75rem] justify-center px-5" className:
"relative -top-1 min-h-[3.1rem] min-w-[10.75rem] justify-center px-5",
})} })}
style={{ marginLeft: "14px" }} style={{ marginLeft: "14px" }}
> >
@@ -83,31 +131,27 @@ export default async function HomePage() {
<section id="example" className="mt-8 space-y-5 md:mt-10"> <section id="example" className="mt-8 space-y-5 md:mt-10">
<div className="space-y-2"> <div className="space-y-2">
<h2 className="text-2xl font-bold">Profile preview</h2> <h2 className="text-2xl font-bold">Profile preview</h2>
<p className="text-sm text-muted">How a public PayMe profile is presented.</p> <p className="text-sm text-muted">
How a public PayMe profile is presented.
</p>
</div> </div>
<div className="terminal-card space-y-5 md:p-6"> <div className="space-y-8">
<div className="space-y-1 border-b border-border/70 pb-4"> <ProfileHeader
<p className="terminal-heading">Public profile</p> displayName={previewProfile.displayName}
<p className="pt-1 text-2xl font-bold">Alex Rivera</p> username={previewProfile.username}
<p className="text-sm text-muted">@alexdev</p> bio={previewProfile.bio}
<p className="pt-2 text-base text-muted">Open source maintainer. Donations keep maintenance sustainable.</p> avatarUrl={previewProfile.avatarUrl}
</div> />
<ul className="list-none space-y-3 p-0"> <section className="terminal-section space-y-4">
{previewRows.map((row) => ( <h3 className="terminal-heading">Payment Methods</h3>
<li key={row.title} className="method-card"> <div className="space-y-3">
<div className="method-card-grid"> {previewMethods.map((method) => (
<div className="method-card-content"> <PaymentMethodCard key={method.id} method={method} />
<p className="method-card-title">{row.title}</p> ))}
<p className="method-card-meta">{row.meta}</p> </div>
<p className="method-card-value">{row.value}</p> </section>
</div>
<p className="text-xs uppercase tracking-[0.12em] text-muted">{row.actions}</p>
</div>
</li>
))}
</ul>
</div> </div>
</section> </section>

2
next-env.d.ts vendored
View File

@@ -1,6 +1,6 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts"; import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

1
public/.gitkeep Normal file
View File

@@ -0,0 +1 @@