Compare commits
3 Commits
c028abd323
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d339d15db | |||
| 4872745edc | |||
| d36ee6ab53 |
@@ -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,
|
||||||
|
|||||||
128
app/page.tsx
128
app/page.tsx
@@ -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>
|
|
||||||
<p className="method-card-value">{row.value}</p>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs uppercase tracking-[0.12em] text-muted">{row.actions}</p>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
))}
|
))}
|
||||||
</ul>
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
2
next-env.d.ts
vendored
2
next-env.d.ts
vendored
@@ -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
1
public/.gitkeep
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
Reference in New Issue
Block a user