From 15ec9f8e007e99d0e557d1e224172393ba84a0ca Mon Sep 17 00:00:00 2001 From: Bassgeta Date: Fri, 17 Oct 2025 10:54:47 +0200 Subject: [PATCH 01/22] feat: new sidebar and new homepage --- src/app/globals.css | 6 + src/app/home/page.tsx | 157 ++++++++++++++ src/app/layout.tsx | 8 +- src/app/page.tsx | 2 +- src/components/background-wrapper.tsx | 23 +- src/components/sidebar/sidebar.tsx | 290 ++++++++++++++++++++++++++ tailwind.config.ts | 4 + 7 files changed, 470 insertions(+), 20 deletions(-) create mode 100644 src/app/home/page.tsx create mode 100644 src/components/sidebar/sidebar.tsx diff --git a/src/app/globals.css b/src/app/globals.css index 7be50587..2a796326 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,6 +1,7 @@ @tailwind base; @tailwind components; @tailwind utilities; + @layer base { :root { --background: 0 0% 100%; @@ -32,6 +33,8 @@ --chart-4: 43 74% 66%; --chart-5: 27 87% 67%; --radius: 0.5rem; + --highlight: 171 100% 42%; + --highlight-foreground: 0 0% 3.9%; } .dark { --background: 0 0% 3.9%; @@ -62,8 +65,11 @@ --chart-3: 30 80% 55%; --chart-4: 280 65% 60%; --chart-5: 340 75% 55%; + --highlight: 171 100% 42%; + --highlight-foreground: 0 0% 98%; } } + @layer base { * { @apply border-border; diff --git a/src/app/home/page.tsx b/src/app/home/page.tsx new file mode 100644 index 00000000..3ebc4da4 --- /dev/null +++ b/src/app/home/page.tsx @@ -0,0 +1,157 @@ +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + DollarSign, + FileText, + Globe, + Plus, + Shield, + TrendingUp, + Zap, +} from "lucide-react"; +import Link from "next/link"; + +export default function HomePage() { + return ( +
+
+

+ Welcome to Request Dashboard +

+

+ Your journey to integrating seamless crypto payments starts here. This + is a white-label application built on the power and reliability of + Request Network. +

+
+ + + +
+
+ +
+
+ +
+

+ Powered by Request Network +

+

+ A proven payment infrastructure trusted by thousands of + businesses. +

+
+
+ +
+ + + +
2017
+

Operating Since

+
+
+ + + +
1B+
+

+ Payments Processed +

+
+
+ + + +
$30M
+

Monthly Volume

+
+
+ + + +
20K
+

+ Transactions/Month +

+
+
+
+
+ + {/* Quick Actions */} +
+ + + + + Create Invoice + + + Generate a new invoice for your clients + + + + + + + + + + + + + + View Invoices + + + Manage and track all your invoices + + + + + + + + + + + + + + Payments + + Track payments and payouts + + + + + + + +
+
+ ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index fcac98a3..f497cdb5 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,5 +1,6 @@ import { AppKit } from "@/components/app-kit"; import { BackgroundWrapper } from "@/components/background-wrapper"; +import { Sidebar } from "@/components/sidebar/sidebar"; import { Toaster } from "@/components/ui/sonner"; import VersionDisplay from "@/components/version-badge"; import { TRPCReactProvider } from "@/trpc/react"; @@ -48,7 +49,12 @@ export default function RootLayout({ - {children} +
+ +
+ {children} +
+
diff --git a/src/app/page.tsx b/src/app/page.tsx index f168edca..30fedeee 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -15,7 +15,7 @@ export default async function LoginPage() { const { user } = await getCurrentSession(); if (user) { - return redirect("/dashboard"); + return redirect("/home"); } return ( diff --git a/src/components/background-wrapper.tsx b/src/components/background-wrapper.tsx index a9b11e36..7d815039 100644 --- a/src/components/background-wrapper.tsx +++ b/src/components/background-wrapper.tsx @@ -33,43 +33,30 @@ export function BackgroundWrapper({ setIsMounted(true); }, []); - // Convert Tailwind color names to CSS variables or hex values const getTailwindColor = (colorName: string): string => { const colors: Record = { - // Orange colors "orange-100": "#ffedd5", "orange-200": "#fed7aa", - - // Blue colors "blue-100": "#dbeafe", "blue-200": "#bfdbfe", - - // Indigo colors "indigo-100": "#e0e7ff", "indigo-200": "#c7d2fe", - - // Zinc colors "zinc-100": "#f4f4f5", "zinc-200": "#e4e4e7", - - // Dark mode colors "zinc-800": "#27272a", "zinc-900": "#18181b", "slate-800": "#1e293b", "slate-900": "#0f172a", - - // Add any other colors you need here }; - return colors[colorName] || "#f4f4f5"; // Default to zinc-100 if color not found + return colors[colorName] || "#f4f4f5"; }; - // Only trust theme after mount to keep SSR/CSR output consistent const isDark = isMounted && resolvedTheme === "dark"; return (
- {/* Decorative elements: keep DOM shape stable; toggle visibility */} + {/* Decorative elements - offset for sidebar */}
- {/* Dot pattern background */} + {/* Dot pattern background - offset for sidebar */}
{ + setExpandedSections((prev) => ({ + ...prev, + [section]: !prev[section], + })); + }; + + return ( + + ); +} diff --git a/tailwind.config.ts b/tailwind.config.ts index 0ed1f8a7..6f0d7d19 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -48,6 +48,10 @@ const config: Config = { DEFAULT: "hsl(var(--warning))", foreground: "hsl(var(--warning-foreground))", }, + highlight: { + DEFAULT: "hsl(var(--highlight))", + foreground: "hsl(var(--highlight-foreground))", + }, border: "hsl(var(--border))", input: "hsl(var(--input))", ring: "hsl(var(--ring))", From d30bd7408ea92767334b815b908edac387c05ea9 Mon Sep 17 00:00:00 2001 From: Bassgeta Date: Fri, 17 Oct 2025 13:45:34 +0200 Subject: [PATCH 02/22] feat: move all invoice pages to new routes --- .../get-paid => invoices/clients}/page.tsx | 0 src/app/{invoice-me => invoices/me}/page.tsx | 0 src/app/{dashboard/pay => invoices}/page.tsx | 0 src/components/sidebar/sidebar.tsx | 16 ++++++++-------- 4 files changed, 8 insertions(+), 8 deletions(-) rename src/app/{dashboard/get-paid => invoices/clients}/page.tsx (100%) rename src/app/{invoice-me => invoices/me}/page.tsx (100%) rename src/app/{dashboard/pay => invoices}/page.tsx (100%) diff --git a/src/app/dashboard/get-paid/page.tsx b/src/app/invoices/clients/page.tsx similarity index 100% rename from src/app/dashboard/get-paid/page.tsx rename to src/app/invoices/clients/page.tsx diff --git a/src/app/invoice-me/page.tsx b/src/app/invoices/me/page.tsx similarity index 100% rename from src/app/invoice-me/page.tsx rename to src/app/invoices/me/page.tsx diff --git a/src/app/dashboard/pay/page.tsx b/src/app/invoices/page.tsx similarity index 100% rename from src/app/dashboard/pay/page.tsx rename to src/app/invoices/page.tsx diff --git a/src/components/sidebar/sidebar.tsx b/src/components/sidebar/sidebar.tsx index dbbaf6b9..19152e0f 100644 --- a/src/components/sidebar/sidebar.tsx +++ b/src/components/sidebar/sidebar.tsx @@ -86,9 +86,9 @@ export function Sidebar() { {expandedSections.billing && (
Date: Fri, 17 Oct 2025 14:07:28 +0200 Subject: [PATCH 03/22] feat: move ecommerce and subscriptions to new routes --- src/app/dashboard/layout.tsx | 41 -------------- src/app/ecommerce/layout.tsx | 33 ------------ src/app/subscriptions/page.tsx | 40 ++++++++++++++ src/app/subscriptions/payments/page.tsx | 46 ++++++++++++++++ src/app/subscriptions/subscribers/page.tsx | 46 ++++++++++++++++ .../ecommerce/ecommerce-navigation.tsx | 32 ----------- src/components/sidebar/sidebar.tsx | 54 +++++++++++-------- 7 files changed, 164 insertions(+), 128 deletions(-) delete mode 100644 src/app/dashboard/layout.tsx delete mode 100644 src/app/ecommerce/layout.tsx create mode 100644 src/app/subscriptions/page.tsx create mode 100644 src/app/subscriptions/payments/page.tsx create mode 100644 src/app/subscriptions/subscribers/page.tsx delete mode 100644 src/components/ecommerce/ecommerce-navigation.tsx diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx deleted file mode 100644 index aeb5c5a0..00000000 --- a/src/app/dashboard/layout.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { BackgroundWrapper } from "@/components/background-wrapper"; -import { DashboardNavigation } from "@/components/dashboard-navigation"; -import { Footer } from "@/components/footer"; -import { Header } from "@/components/header"; -import { Button } from "@/components/ui/button"; -import { getCurrentSession } from "@/server/auth"; -import { PlusCircle } from "lucide-react"; -import Link from "next/link"; -import { redirect } from "next/navigation"; - -export default async function DashboardLayout({ - children, -}: { - children: React.ReactNode; -}) { - const { user } = await getCurrentSession(); - if (!user) redirect("/"); - - return ( - -
-
-
-

Dashboard

- -
- - {children} -
-
- - ); -} diff --git a/src/app/ecommerce/layout.tsx b/src/app/ecommerce/layout.tsx deleted file mode 100644 index 34bf16c4..00000000 --- a/src/app/ecommerce/layout.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { BackgroundWrapper } from "@/components/background-wrapper"; -import { EcommerceNavigation } from "@/components/ecommerce/ecommerce-navigation"; -import { Footer } from "@/components/footer"; -import { Header } from "@/components/header"; -import { getCurrentSession } from "@/server/auth"; -import { redirect } from "next/navigation"; - -export default async function EcommerceLayout({ - children, -}: { - children: React.ReactNode; -}) { - const { user } = await getCurrentSession(); - if (!user) redirect("/"); - - return ( - -
-
-

Ecommerce

-

- Create and manage your ecommerce clients and view sales -

- - {children} -
-
- - ); -} diff --git a/src/app/subscriptions/page.tsx b/src/app/subscriptions/page.tsx new file mode 100644 index 00000000..81c1ff57 --- /dev/null +++ b/src/app/subscriptions/page.tsx @@ -0,0 +1,40 @@ +import { Footer } from "@/components/footer"; +import { Header } from "@/components/header"; +import { SubscriptionPlansList } from "@/components/subscription-plans/blocks/subscription-plans-list"; +import { getCurrentSession } from "@/server/auth"; +import { api } from "@/trpc/server"; +import { ArrowLeft } from "lucide-react"; +import Link from "next/link"; +import { redirect } from "next/navigation"; + +export default async function ManagePlansPage() { + const { user } = await getCurrentSession(); + + if (!user) { + redirect("/"); + } + + const subscriptionPlans = await api.subscriptionPlan.getAll.query(); + + return ( + <> +
+
+
+ + + +

+ Manage Subscription Plans +

+
+ + +
+
+ + ); +} diff --git a/src/app/subscriptions/payments/page.tsx b/src/app/subscriptions/payments/page.tsx new file mode 100644 index 00000000..118e2589 --- /dev/null +++ b/src/app/subscriptions/payments/page.tsx @@ -0,0 +1,46 @@ +import { Footer } from "@/components/footer"; +import { Header } from "@/components/header"; +import { PaymentsTable } from "@/components/subscription-plans/blocks/payments-table"; +import { getCurrentSession } from "@/server/auth"; +import { api } from "@/trpc/server"; +import { ArrowLeft } from "lucide-react"; +import Link from "next/link"; +import { redirect } from "next/navigation"; + +export default async function SubscriptionPaymentsPage() { + const { user } = await getCurrentSession(); + + if (!user) { + redirect("/"); + } + + const [allPayments, subscriptionPlans] = await Promise.all([ + api.subscriptionPlan.getAllPayments.query(), + api.subscriptionPlan.getAll.query(), + ]); + + return ( + <> +
+
+
+ + + +

+ Subscription Payments +

+
+ + +
+
+ + ); +} diff --git a/src/app/subscriptions/subscribers/page.tsx b/src/app/subscriptions/subscribers/page.tsx new file mode 100644 index 00000000..02a0109a --- /dev/null +++ b/src/app/subscriptions/subscribers/page.tsx @@ -0,0 +1,46 @@ +import { Footer } from "@/components/footer"; +import { Header } from "@/components/header"; +import { SubscribersTable } from "@/components/subscription-plans/blocks/subscribers-table"; +import { getCurrentSession } from "@/server/auth"; +import { api } from "@/trpc/server"; +import { ArrowLeft } from "lucide-react"; +import Link from "next/link"; +import { redirect } from "next/navigation"; + +export default async function SubscribersPage() { + const { user } = await getCurrentSession(); + + if (!user) { + redirect("/"); + } + + const [allSubscribers, subscriptionPlans] = await Promise.all([ + api.subscriptionPlan.getAllSubscribers.query(), + api.subscriptionPlan.getAll.query(), + ]); + + return ( + <> +
+
+
+ + + +

+ Subscription Subscribers +

+
+ + +
+
+ + ); +} diff --git a/src/components/ecommerce/ecommerce-navigation.tsx b/src/components/ecommerce/ecommerce-navigation.tsx deleted file mode 100644 index 9afd1b3e..00000000 --- a/src/components/ecommerce/ecommerce-navigation.tsx +++ /dev/null @@ -1,32 +0,0 @@ -"use client"; - -import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import Link from "next/link"; -import { usePathname } from "next/navigation"; -import { useEffect, useState } from "react"; - -export function EcommerceNavigation() { - const pathname = usePathname(); - const [activeTab, setActiveTab] = useState("manage"); - - useEffect(() => { - if (pathname.includes("/sales")) { - setActiveTab("sales"); - } else { - setActiveTab("manage"); - } - }, [pathname]); - - return ( - - - - Manage - - - Sales - - - - ); -} diff --git a/src/components/sidebar/sidebar.tsx b/src/components/sidebar/sidebar.tsx index 19152e0f..bf318d33 100644 --- a/src/components/sidebar/sidebar.tsx +++ b/src/components/sidebar/sidebar.tsx @@ -132,40 +132,40 @@ export function Sidebar() {
- {expandedSections.subscriptions && ( + {expandedSections.ecommerce && (
- Create Subscription + Clients - Manage Subscriptions + Sales
)} @@ -174,40 +174,50 @@ export function Sidebar() {
- {expandedSections.ecommerce && ( + {expandedSections.subscriptions && (
- Products + Manage Plans + Subscribers + + - Orders + Payments
)} From c0bcfe629a612ad1d598a184d348d9e484238ea9 Mon Sep 17 00:00:00 2001 From: Bassgeta Date: Fri, 17 Oct 2025 14:19:19 +0200 Subject: [PATCH 04/22] feat: move payouts to new nav and routes --- src/app/{payouts => payments}/batch/page.tsx | 0 .../single => payments/direct}/page.tsx | 0 src/app/{payouts => payments}/page.tsx | 2 +- .../recurring/create/page.tsx | 0 .../recurring/layout.tsx | 0 .../{payouts => payments}/recurring/page.tsx | 0 src/app/payouts/layout.tsx | 33 ----------------- src/components/payout-navigation.tsx | 37 ------------------- src/components/sidebar/sidebar.tsx | 24 +----------- 9 files changed, 3 insertions(+), 93 deletions(-) rename src/app/{payouts => payments}/batch/page.tsx (100%) rename src/app/{payouts/single => payments/direct}/page.tsx (100%) rename src/app/{payouts => payments}/page.tsx (88%) rename src/app/{payouts => payments}/recurring/create/page.tsx (100%) rename src/app/{payouts => payments}/recurring/layout.tsx (100%) rename src/app/{payouts => payments}/recurring/page.tsx (100%) delete mode 100644 src/app/payouts/layout.tsx delete mode 100644 src/components/payout-navigation.tsx diff --git a/src/app/payouts/batch/page.tsx b/src/app/payments/batch/page.tsx similarity index 100% rename from src/app/payouts/batch/page.tsx rename to src/app/payments/batch/page.tsx diff --git a/src/app/payouts/single/page.tsx b/src/app/payments/direct/page.tsx similarity index 100% rename from src/app/payouts/single/page.tsx rename to src/app/payments/direct/page.tsx diff --git a/src/app/payouts/page.tsx b/src/app/payments/page.tsx similarity index 88% rename from src/app/payouts/page.tsx rename to src/app/payments/page.tsx index 401cc40e..f840d9a5 100644 --- a/src/app/payouts/page.tsx +++ b/src/app/payments/page.tsx @@ -8,5 +8,5 @@ export const metadata: Metadata = { }; export default function PayoutsPage() { - return redirect("/payouts/single"); + return redirect("/payouts/direct"); } diff --git a/src/app/payouts/recurring/create/page.tsx b/src/app/payments/recurring/create/page.tsx similarity index 100% rename from src/app/payouts/recurring/create/page.tsx rename to src/app/payments/recurring/create/page.tsx diff --git a/src/app/payouts/recurring/layout.tsx b/src/app/payments/recurring/layout.tsx similarity index 100% rename from src/app/payouts/recurring/layout.tsx rename to src/app/payments/recurring/layout.tsx diff --git a/src/app/payouts/recurring/page.tsx b/src/app/payments/recurring/page.tsx similarity index 100% rename from src/app/payouts/recurring/page.tsx rename to src/app/payments/recurring/page.tsx diff --git a/src/app/payouts/layout.tsx b/src/app/payouts/layout.tsx deleted file mode 100644 index 52f48acb..00000000 --- a/src/app/payouts/layout.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { BackgroundWrapper } from "@/components/background-wrapper"; -import { Footer } from "@/components/footer"; -import { Header } from "@/components/header"; -import { PayoutNavigation } from "@/components/payout-navigation"; -import { getCurrentSession } from "@/server/auth"; -import { redirect } from "next/navigation"; - -export default async function PayoutsLayout({ - children, -}: { - children: React.ReactNode; -}) { - const { user } = await getCurrentSession(); - if (!user) redirect("/"); - - return ( - -
-
-

Payouts

-

- Send single, batch or recurring payouts by creating a request first -

- - {children} -
-
- - ); -} diff --git a/src/components/payout-navigation.tsx b/src/components/payout-navigation.tsx deleted file mode 100644 index 0fff8f58..00000000 --- a/src/components/payout-navigation.tsx +++ /dev/null @@ -1,37 +0,0 @@ -"use client"; - -import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import Link from "next/link"; -import { usePathname } from "next/navigation"; -import { useEffect, useState } from "react"; - -export function PayoutNavigation() { - const pathname = usePathname(); - const [activeTab, setActiveTab] = useState("single"); - - useEffect(() => { - if (pathname.includes("/batch")) { - setActiveTab("batch"); - } else if (pathname.includes("/recurring")) { - setActiveTab("recurring"); - } else { - setActiveTab("single"); - } - }, [pathname]); - - return ( - - - - Single Payout - - - Batch Payout - - - Recurring Payouts - - - - ); -} diff --git a/src/components/sidebar/sidebar.tsx b/src/components/sidebar/sidebar.tsx index bf318d33..7b0ba00f 100644 --- a/src/components/sidebar/sidebar.tsx +++ b/src/components/sidebar/sidebar.tsx @@ -242,19 +242,9 @@ export function Sidebar() { {expandedSections.payouts && (
- Overview - - Recurring Payments - - Vendors -
)}
From ed89978f505f962268a52c6dfa59a65d2ecb48cf Mon Sep 17 00:00:00 2001 From: Bassgeta Date: Mon, 20 Oct 2025 09:01:02 +0200 Subject: [PATCH 05/22] feat: move recurring payouts to new routes, consolidate folder structure --- src/app/dashboard/page.tsx | 2 +- src/app/payments/recurring/create/page.tsx | 2 +- src/app/payments/recurring/page.tsx | 2 +- src/components/dashboard/subscriptions.tsx | 6 ++--- .../recurring-payments-navigation.tsx | 4 ++-- .../blocks/create-recurring-payment-form.tsx | 0 .../create-recurring-payment.tsx | 0 .../blocks/completed-payments.tsx | 0 .../blocks/frequency-badge.tsx | 0 .../blocks/status-badge.tsx | 0 .../view-recurring-payments.tsx | 4 ++-- src/components/sidebar/sidebar.tsx | 22 ++++++++++++++++--- .../blocks/subscribers-table.tsx | 4 ++-- 13 files changed, 31 insertions(+), 15 deletions(-) rename src/components/{ => recurring-payments}/create-recurring-payment/blocks/create-recurring-payment-form.tsx (100%) rename src/components/{ => recurring-payments}/create-recurring-payment/create-recurring-payment.tsx (100%) rename src/components/{ => recurring-payments}/view-recurring-payments/blocks/completed-payments.tsx (100%) rename src/components/{ => recurring-payments}/view-recurring-payments/blocks/frequency-badge.tsx (100%) rename src/components/{ => recurring-payments}/view-recurring-payments/blocks/status-badge.tsx (100%) rename src/components/{ => recurring-payments}/view-recurring-payments/view-recurring-payments.tsx (98%) diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 401a3051..cc7d4bd5 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -1,5 +1,5 @@ import { redirect } from "next/navigation"; export default function DashboardPage() { - redirect("/dashboard/get-paid"); + redirect("/dashboard/subscriptions"); } diff --git a/src/app/payments/recurring/create/page.tsx b/src/app/payments/recurring/create/page.tsx index 26593dd9..6055b309 100644 --- a/src/app/payments/recurring/create/page.tsx +++ b/src/app/payments/recurring/create/page.tsx @@ -1,4 +1,4 @@ -import { CreateRecurringPayment } from "@/components/create-recurring-payment/create-recurring-payment"; +import { CreateRecurringPayment } from "@/components/recurring-payments/create-recurring-payment/create-recurring-payment"; export const metadata = { title: "Recurring Payments | Easy Invoice", diff --git a/src/app/payments/recurring/page.tsx b/src/app/payments/recurring/page.tsx index d5d77746..ac3093b9 100644 --- a/src/app/payments/recurring/page.tsx +++ b/src/app/payments/recurring/page.tsx @@ -1,4 +1,4 @@ -import { ViewRecurringPayments } from "@/components/view-recurring-payments/view-recurring-payments"; +import { ViewRecurringPayments } from "@/components/recurring-payments/view-recurring-payments/view-recurring-payments"; import { getCurrentSession } from "@/server/auth"; import { api } from "@/trpc/server"; import { redirect } from "next/navigation"; diff --git a/src/components/dashboard/subscriptions.tsx b/src/components/dashboard/subscriptions.tsx index 152f1da6..9183e942 100644 --- a/src/components/dashboard/subscriptions.tsx +++ b/src/components/dashboard/subscriptions.tsx @@ -1,5 +1,7 @@ "use client"; +import { CompletedPayments } from "@/components/recurring-payments/view-recurring-payments/blocks/completed-payments"; +import { FrequencyBadge } from "@/components/recurring-payments/view-recurring-payments/blocks/frequency-badge"; import { ShortAddress } from "@/components/short-address"; import { AlertDialog, @@ -19,8 +21,6 @@ import { TableHeader, TableRow, } from "@/components/ui/table/table"; -import { CompletedPayments } from "@/components/view-recurring-payments/blocks/completed-payments"; -import { FrequencyBadge } from "@/components/view-recurring-payments/blocks/frequency-badge"; import { formatCurrencyLabel } from "@/lib/constants/currencies"; import { calculateTotalsByCurrency, @@ -34,12 +34,12 @@ import { addDays, format } from "date-fns"; import { Ban, CreditCard, DollarSign, Loader2 } from "lucide-react"; import { useState } from "react"; import { MultiCurrencyStatCard } from "../multi-currency-stat-card"; +import { StatusBadge } from "../recurring-payments/view-recurring-payments/blocks/status-badge"; import { StatCard } from "../stat-card"; import { Button } from "../ui/button"; import { EmptyState } from "../ui/table/empty-state"; import { Pagination } from "../ui/table/pagination"; import { TableHeadCell } from "../ui/table/table-head-cell"; -import { StatusBadge } from "../view-recurring-payments/blocks/status-badge"; const ITEMS_PER_PAGE = 10; const ACTIVE_STATUSES = ["pending", "active"]; diff --git a/src/components/recurring-payments-navigation.tsx b/src/components/recurring-payments-navigation.tsx index 10fa33c4..1082a7f7 100644 --- a/src/components/recurring-payments-navigation.tsx +++ b/src/components/recurring-payments-navigation.tsx @@ -21,10 +21,10 @@ export function RecurringPaymentsNavigation() { - View Payouts + View Payouts - Create New + Create New diff --git a/src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx b/src/components/recurring-payments/create-recurring-payment/blocks/create-recurring-payment-form.tsx similarity index 100% rename from src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx rename to src/components/recurring-payments/create-recurring-payment/blocks/create-recurring-payment-form.tsx diff --git a/src/components/create-recurring-payment/create-recurring-payment.tsx b/src/components/recurring-payments/create-recurring-payment/create-recurring-payment.tsx similarity index 100% rename from src/components/create-recurring-payment/create-recurring-payment.tsx rename to src/components/recurring-payments/create-recurring-payment/create-recurring-payment.tsx diff --git a/src/components/view-recurring-payments/blocks/completed-payments.tsx b/src/components/recurring-payments/view-recurring-payments/blocks/completed-payments.tsx similarity index 100% rename from src/components/view-recurring-payments/blocks/completed-payments.tsx rename to src/components/recurring-payments/view-recurring-payments/blocks/completed-payments.tsx diff --git a/src/components/view-recurring-payments/blocks/frequency-badge.tsx b/src/components/recurring-payments/view-recurring-payments/blocks/frequency-badge.tsx similarity index 100% rename from src/components/view-recurring-payments/blocks/frequency-badge.tsx rename to src/components/recurring-payments/view-recurring-payments/blocks/frequency-badge.tsx diff --git a/src/components/view-recurring-payments/blocks/status-badge.tsx b/src/components/recurring-payments/view-recurring-payments/blocks/status-badge.tsx similarity index 100% rename from src/components/view-recurring-payments/blocks/status-badge.tsx rename to src/components/recurring-payments/view-recurring-payments/blocks/status-badge.tsx diff --git a/src/components/view-recurring-payments/view-recurring-payments.tsx b/src/components/recurring-payments/view-recurring-payments/view-recurring-payments.tsx similarity index 98% rename from src/components/view-recurring-payments/view-recurring-payments.tsx rename to src/components/recurring-payments/view-recurring-payments/view-recurring-payments.tsx index 43b7199f..bcc6c774 100644 --- a/src/components/view-recurring-payments/view-recurring-payments.tsx +++ b/src/components/recurring-payments/view-recurring-payments/view-recurring-payments.tsx @@ -20,7 +20,6 @@ import { TableHeader, TableRow, } from "@/components/ui/table/table"; -import { CompletedPayments } from "@/components/view-recurring-payments/blocks/completed-payments"; import { formatDate } from "@/lib/date-utils"; import { useCancelRecurringPayment } from "@/lib/hooks/use-cancel-recurring-payment"; import { getCanCancelPayment } from "@/lib/utils"; @@ -28,7 +27,8 @@ import type { RecurringPayment } from "@/server/db/schema"; import { api } from "@/trpc/react"; import { AlertCircle, Ban, Eye, Loader2, RefreshCw } from "lucide-react"; import { useState } from "react"; -import { ShortAddress } from "../short-address"; +import { ShortAddress } from "../../short-address"; +import { CompletedPayments } from "./blocks/completed-payments"; import { FrequencyBadge } from "./blocks/frequency-badge"; import { StatusBadge } from "./blocks/status-badge"; diff --git a/src/components/sidebar/sidebar.tsx b/src/components/sidebar/sidebar.tsx index 7b0ba00f..eca293a7 100644 --- a/src/components/sidebar/sidebar.tsx +++ b/src/components/sidebar/sidebar.tsx @@ -12,24 +12,40 @@ import { } from "lucide-react"; import Link from "next/link"; import { usePathname } from "next/navigation"; -import { useState } from "react"; +import { useEffect, useState } from "react"; + +type Section = "billing" | "subscriptions" | "payouts" | "ecommerce"; export function Sidebar() { const pathname = usePathname(); - const [expandedSections, setExpandedSections] = useState({ + const [expandedSections, setExpandedSections] = useState< + Record + >({ billing: false, subscriptions: false, payouts: false, ecommerce: false, }); - const toggleSection = (section: keyof typeof expandedSections) => { + const toggleSection = (section: Section) => { setExpandedSections((prev) => ({ ...prev, [section]: !prev[section], })); }; + useEffect(() => { + if (pathname.startsWith("/invoices")) { + setExpandedSections((prev) => ({ ...prev, billing: true })); + } else if (pathname.startsWith("/subscriptions")) { + setExpandedSections((prev) => ({ ...prev, subscriptions: true })); + } else if (pathname.startsWith("/payments")) { + setExpandedSections((prev) => ({ ...prev, payouts: true })); + } else if (pathname.startsWith("/ecommerce")) { + setExpandedSections((prev) => ({ ...prev, ecommerce: true })); + } + }, [pathname]); + return (
diff --git a/src/app/(dashboard)/i/[id]/helpers.ts b/src/app/(dashboard)/i/[id]/helpers.ts index 8e972ec8..325a4e03 100644 --- a/src/app/(dashboard)/i/[id]/helpers.ts +++ b/src/app/(dashboard)/i/[id]/helpers.ts @@ -1,9 +1,9 @@ -import { isNotFoundError } from "@/lib/utils"; +import { isNotFoundError } from "@/lib/helpers"; import { api } from "@/trpc/server"; export async function getInvoiceMeLink(id: string) { try { - return await api.invoiceMe.getById.query(id); + return await api.invoiceMe.getById(id); } catch (error) { if (isNotFoundError(error)) { return null; diff --git a/src/app/(dashboard)/invoices/[ID]/_components/payment-section.tsx b/src/app/(dashboard)/invoices/[ID]/_components/payment-section.tsx index b713c22f..63e96c60 100644 --- a/src/app/(dashboard)/invoices/[ID]/_components/payment-section.tsx +++ b/src/app/(dashboard)/invoices/[ID]/_components/payment-section.tsx @@ -123,18 +123,22 @@ export function PaymentSection({ serverInvoice }: PaymentSectionProps) { const [polling, setPolling] = useState(paymentStatus !== "paid"); // Poll the invoice status every 3 seconds until it's paid - api.invoice.getById.useQuery(serverInvoice.id, { - refetchInterval: polling ? 3000 : false, - onSuccess: (data) => { - setInvoice(data); - if (data.status !== "pending") { - setPaymentStatus(data.status); + const { data: invoiceData, isSuccess: isInvoiceDataSuccess } = + api.invoice.getById.useQuery(serverInvoice.id, { + refetchInterval: polling ? 3000 : false, + }); + + useEffect(() => { + if (isInvoiceDataSuccess && invoiceData) { + setInvoice(invoiceData); + if (invoiceData.status !== "pending") { + setPaymentStatus(invoiceData.status); } - if (data.status === "paid") { + if (invoiceData.status === "paid") { setPolling(false); } - }, - }); + } + }, [isInvoiceDataSuccess, invoiceData]); const [paymentProgress, setPaymentProgress] = useState("idle"); const [currentStep, setCurrentStep] = useState(1); diff --git a/src/app/(dashboard)/invoices/[ID]/helpers.ts b/src/app/(dashboard)/invoices/[ID]/helpers.ts index 4d28d977..5d00d5dc 100644 --- a/src/app/(dashboard)/invoices/[ID]/helpers.ts +++ b/src/app/(dashboard)/invoices/[ID]/helpers.ts @@ -1,9 +1,9 @@ -import { isNotFoundError } from "@/lib/utils"; +import { isNotFoundError } from "@/lib/helpers"; import { api } from "@/trpc/server"; export async function getInvoice(id: string) { try { - return await api.invoice.getById.query(id); + return await api.invoice.getById(id); } catch (error) { if (isNotFoundError(error)) { return null; diff --git a/src/app/(dashboard)/invoices/[ID]/page.tsx b/src/app/(dashboard)/invoices/[ID]/page.tsx index cbead46d..d3678011 100644 --- a/src/app/(dashboard)/invoices/[ID]/page.tsx +++ b/src/app/(dashboard)/invoices/[ID]/page.tsx @@ -35,7 +35,7 @@ export default async function PaymentPage({ } const paymentDetailsData = invoice.paymentDetailsId - ? await api.compliance.getPaymentDetailsById.query(invoice.paymentDetailsId) + ? await api.compliance.getPaymentDetailsById(invoice.paymentDetailsId) : null; return ( diff --git a/src/app/(dashboard)/invoices/_components/invoice-row.tsx b/src/app/(dashboard)/invoices/_components/invoice-row.tsx index 109d533a..80e930ba 100644 --- a/src/app/(dashboard)/invoices/_components/invoice-row.tsx +++ b/src/app/(dashboard)/invoices/_components/invoice-row.tsx @@ -129,12 +129,12 @@ export const InvoiceRow = ({ invoice, type, children }: InvoiceRowProps) => { variant="outline" size="icon" onClick={() => setIsStopRecurrenceDialogOpen(true)} - disabled={stopRecurrenceMutation.isLoading} + disabled={stopRecurrenceMutation.isPending} className="h-8 w-8" > - {stopRecurrenceMutation.isLoading + {stopRecurrenceMutation.isPending ? "Stopping..." : "Stop Recurring"} @@ -164,9 +164,9 @@ export const InvoiceRow = ({ invoice, type, children }: InvoiceRowProps) => { - {stopRecurrenceMutation.isLoading + {stopRecurrenceMutation.isPending ? "Stopping..." : "Stop Recurring"} diff --git a/src/app/(dashboard)/invoices/me/_components/invoice-me-links.tsx b/src/app/(dashboard)/invoices/me/_components/invoice-me-links.tsx index b6faa3f9..f805ca90 100644 --- a/src/app/(dashboard)/invoices/me/_components/invoice-me-links.tsx +++ b/src/app/(dashboard)/invoices/me/_components/invoice-me-links.tsx @@ -27,7 +27,7 @@ export function InvoiceMeLinks({ initialLinks }: InvoiceMeLinksProps) { const { mutateAsync: createInvoiceMeLink, - isLoading: isCreatingInvoiceMeLink, + isPending: isCreatingInvoiceMeLink, } = api.invoiceMe.create.useMutation({ onSuccess: () => { trpcContext.invoiceMe.getAll.invalidate(); diff --git a/src/app/(dashboard)/invoices/me/page.tsx b/src/app/(dashboard)/invoices/me/page.tsx index 8355353c..42ec4e2a 100644 --- a/src/app/(dashboard)/invoices/me/page.tsx +++ b/src/app/(dashboard)/invoices/me/page.tsx @@ -6,7 +6,7 @@ import { InvoiceMeLinks } from "./_components/invoice-me-links"; export default async function InvoiceMePage() { await requireAuth(); - const links = await api.invoiceMe.getAll.query(); + const links = await api.invoiceMe.getAll(); return ( <> diff --git a/src/app/(dashboard)/invoices/page.tsx b/src/app/(dashboard)/invoices/page.tsx index 1c7bb3de..39c13929 100644 --- a/src/app/(dashboard)/invoices/page.tsx +++ b/src/app/(dashboard)/invoices/page.tsx @@ -7,8 +7,8 @@ export default async function InvoicesPage() { await requireAuth(); const [sentInvoices, receivedInvoices] = await Promise.all([ - api.invoice.getAllIssuedByMe.query(), - api.invoice.getAllIssuedToMe.query(), + api.invoice.getAllIssuedByMe(), + api.invoice.getAllIssuedToMe(), ]); return ( diff --git a/src/app/(dashboard)/payouts/recurring/_components/view-recurring-payments.tsx b/src/app/(dashboard)/payouts/recurring/_components/view-recurring-payments.tsx index 37c71ae0..89da4463 100644 --- a/src/app/(dashboard)/payouts/recurring/_components/view-recurring-payments.tsx +++ b/src/app/(dashboard)/payouts/recurring/_components/view-recurring-payments.tsx @@ -24,7 +24,7 @@ import { } from "@/components/ui/table/table"; import { formatDate } from "@/lib/date-utils"; import { useCancelRecurringPayment } from "@/lib/hooks/use-cancel-recurring-payment"; -import { getCanCancelPayment } from "@/lib/utils"; +import { getCanCancelPayment } from "@/lib/helpers"; import type { RecurringPayment } from "@/server/db/schema"; import { api } from "@/trpc/react"; import { AlertCircle, Ban, Eye, Loader2, RefreshCw } from "lucide-react"; diff --git a/src/app/(dashboard)/payouts/recurring/page.tsx b/src/app/(dashboard)/payouts/recurring/page.tsx index 0b7a37d9..c5429ade 100644 --- a/src/app/(dashboard)/payouts/recurring/page.tsx +++ b/src/app/(dashboard)/payouts/recurring/page.tsx @@ -10,7 +10,7 @@ export default async function RecurringPayoutsSlot() { await requireAuth(); const recurringPayments = - await api.recurringPayment.getNonSubscriptionRecurringPayments.query(); + await api.recurringPayment.getNonSubscriptionRecurringPayments(); return ; } diff --git a/src/app/(dashboard)/s/[id]/helpers.ts b/src/app/(dashboard)/s/[id]/helpers.ts index af2cc756..f301ef22 100644 --- a/src/app/(dashboard)/s/[id]/helpers.ts +++ b/src/app/(dashboard)/s/[id]/helpers.ts @@ -1,9 +1,9 @@ -import { isNotFoundError } from "@/lib/utils"; +import { isNotFoundError } from "@/lib/helpers"; import { api } from "@/trpc/server"; export async function getSubscriptionPlan(id: string) { try { - return await api.subscriptionPlan.getById.query(id); + return await api.subscriptionPlan.getById(id); } catch (error) { if (isNotFoundError(error)) { return null; diff --git a/src/app/(dashboard)/subscriptions/_components/create-subscription-plan.tsx b/src/app/(dashboard)/subscriptions/_components/create-subscription-plan.tsx index 40b61ad0..b95a87ea 100644 --- a/src/app/(dashboard)/subscriptions/_components/create-subscription-plan.tsx +++ b/src/app/(dashboard)/subscriptions/_components/create-subscription-plan.tsx @@ -56,7 +56,7 @@ export function CreateSubscriptionPlan({ const trpcContext = api.useUtils(); const { address } = useAppKitAccount(); - const { mutateAsync: createSubscriptionPlan, isLoading } = + const { mutateAsync: createSubscriptionPlan, isPending } = api.subscriptionPlan.create.useMutation({ onSuccess: () => { toast.success("Subscription plan created successfully!"); @@ -114,7 +114,7 @@ export function CreateSubscriptionPlan({ @@ -132,7 +132,7 @@ export function CreateSubscriptionPlan({ @@ -151,7 +151,7 @@ export function CreateSubscriptionPlan({ @@ -269,8 +269,8 @@ export function CreateSubscriptionPlan({
- + + ))} +
+ )} + + + + + + + + No currency found. + + {INVOICE_CURRENCIES.map((currency) => ( + handleSelect(currency)} + > + + {formatCurrencyLabel(currency)} + + ))} + + + + + + ); +} diff --git a/src/app/(dashboard)/ecommerce/widget-playground/_components/customize.tsx b/src/app/(dashboard)/ecommerce/widget-playground/_components/customize.tsx index 514ad1dd..a2d59557 100644 --- a/src/app/(dashboard)/ecommerce/widget-playground/_components/customize.tsx +++ b/src/app/(dashboard)/ecommerce/widget-playground/_components/customize.tsx @@ -1,20 +1,29 @@ "use client"; +import { Button } from "@/components/ui/button"; +import { + FormControl, + FormDescription, + FormError, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; import { cn } from "@/lib/utils"; +import { useEffect } from "react"; import { useFormContext } from "react-hook-form"; +import { CurrencyCombobox } from "./currency-combobox"; import type { PlaygroundFormData } from "./validation"; -import { useEffect } from "react"; -import { CurrencyCombobox } from "../../ui/combobox"; -import { Label } from "@/components/ui/label"; -import { Input } from "@/components/ui/input"; -import { FormError } from "@/components/ui/form"; -import { Button } from "@/components/ui/button"; -import { Switch } from "@/components/ui/switch"; const EASY_INVOICE_URL = process.env.NEXT_PUBLIC_APP_URL; export const CustomizeForm = () => { const { + control, register, formState: { errors }, setValue, @@ -86,7 +95,9 @@ export const CustomizeForm = () => { const totalTax = items.reduce((sum, item) => { if (item.tax) { const tax = - typeof item.tax === "string" ? Number.parseFloat(item.tax) || 0 : item.tax; + typeof item.tax === "string" + ? Number.parseFloat(item.tax) || 0 + : item.tax; return sum + tax; } return sum; @@ -107,22 +118,17 @@ export const CustomizeForm = () => { return (
-

- Payment Configuration -

+

Payment Configuration

{errors.recipientWallet?.message && ( {errors.recipientWallet.message} @@ -132,28 +138,25 @@ export const CustomizeForm = () => {
{errors.paymentConfig?.rnApiClientId?.message && ( {errors.paymentConfig.rnApiClientId.message} )} -

+

Get your Client ID on{" "} EasyInvoice @@ -179,23 +182,27 @@ export const CustomizeForm = () => {

- ( + + Accepted Currencies + + + + + Select which currencies you want to accept for payments + + + )} /> - {errors.paymentConfig?.supportedCurrencies?.message && ( - - {errors.paymentConfig.supportedCurrencies.message} - - )}
@@ -216,7 +223,10 @@ export const CustomizeForm = () => { {formValues.receiptInfo.items.map((item, index) => ( -
+

Item {index + 1}

{formValues.receiptInfo.items.length > 1 && ( @@ -272,7 +282,7 @@ export const CustomizeForm = () => { ? Number.parseFloat(item.total) || 0 : item.total } - className="bg-gray-50" + className="bg-muted text-muted-foreground" />
@@ -317,27 +327,29 @@ export const CustomizeForm = () => {
{/* Totals Display */} -
-
-
+
+
+
Subtotal: $ - {(Number.parseFloat(formValues.receiptInfo.totals.total) || 0).toFixed( - 2, - )} + {( + Number.parseFloat(formValues.receiptInfo.totals.total) || 0 + ).toFixed(2)}
-
+
Discount: -$ {( - Number.parseFloat(formValues.receiptInfo.totals.totalDiscount) || 0 + Number.parseFloat( + formValues.receiptInfo.totals.totalDiscount, + ) || 0 ).toFixed(2)}
-
+
Tax: $ @@ -346,7 +358,7 @@ export const CustomizeForm = () => { ).toFixed(2)}
-
+
Total: $ diff --git a/src/app/(dashboard)/ecommerce/widget-playground/_components/playground-form.tsx b/src/app/(dashboard)/ecommerce/widget-playground/_components/playground-form.tsx index 23cbf301..1b5ee851 100644 --- a/src/app/(dashboard)/ecommerce/widget-playground/_components/playground-form.tsx +++ b/src/app/(dashboard)/ecommerce/widget-playground/_components/playground-form.tsx @@ -1,17 +1,17 @@ "use client"; -import { PlaygroundValidation } from "./validation"; +import { PaymentWidget } from "@/components/payment-widget/payment-widget"; +import { Button } from "@/components/ui/button"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { zodResolver } from "@hookform/resolvers/zod"; import { CopyIcon } from "lucide-react"; import { useRef, useState } from "react"; import { FormProvider, useForm } from "react-hook-form"; import type { z } from "zod"; +import { BuyerForm } from "./buyer-info"; import { CustomizeForm } from "./customize"; import { SellerForm } from "./seller-info"; -import { BuyerForm } from "./buyer-info"; -import { PaymentWidget } from "@/components/payment-widget/payment-widget"; -import { Button } from "@/components/ui/button"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { PlaygroundValidation } from "./validation"; export const Playground = () => { const methods = useForm>({ @@ -72,6 +72,7 @@ export const Playground = () => { const formValues = methods.watch(); const [copied, setCopied] = useState(false); + const [copiedInstall, setCopiedInstall] = useState(false); const codeRef = useRef(null); const generateIntegrationCode = () => { @@ -168,6 +169,7 @@ export const Playground = () => { }; const integrationCode = generateIntegrationCode(); + const installCommand = "npx shadcn add @requestnetwork/payment-widget"; const copyToClipboard = () => { navigator.clipboard @@ -181,6 +183,18 @@ export const Playground = () => { }); }; + const copyInstallCommand = () => { + navigator.clipboard + .writeText(installCommand) + .then(() => { + setCopiedInstall(true); + setTimeout(() => setCopiedInstall(false), 2000); + }) + .catch((err) => { + console.error("Failed to copy text: ", err); + }); + }; + return (
@@ -203,7 +217,7 @@ export const Playground = () => {
-
+

Preview

{ Pay with crypto
-
-
- {/* Integration Code */} -
-
-

Integration Code:

-
-
- -
-
-            
-              npx shadcn add @requestnetwork/payment-widget
-            
-          
+
+

Integration Code

-
- + {/* Install Command */} +
+ +
+                  
+                    {installCommand}
+                  
+                
+
+ + {/* Integration Code */} +
+ +
+                  
+                    {integrationCode}
+                  
+                
+
+
-
-            {integrationCode}
-          
-
+
); diff --git a/src/app/(dashboard)/ecommerce/widget-playground/_components/seller-info.tsx b/src/app/(dashboard)/ecommerce/widget-playground/_components/seller-info.tsx index 08f45683..6ec6cfb6 100644 --- a/src/app/(dashboard)/ecommerce/widget-playground/_components/seller-info.tsx +++ b/src/app/(dashboard)/ecommerce/widget-playground/_components/seller-info.tsx @@ -1,11 +1,11 @@ "use client"; +import { FormError } from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { cn } from "@/lib/utils"; import { useFormContext } from "react-hook-form"; import type { PlaygroundFormData } from "./validation"; -import { cn } from "@/lib/utils"; -import { Label } from "@/components/ui/label"; -import { Input } from "@/components/ui/input"; -import { FormError } from "@/components/ui/form"; export const SellerForm = () => { const { @@ -35,16 +35,13 @@ export const SellerForm = () => {
{errors.receiptInfo?.companyInfo?.name?.message && ( @@ -91,7 +88,9 @@ export const SellerForm = () => {
{ : false, })} className={cn( - "border-2", - errors.receiptInfo?.companyInfo?.address?.street - ? "border-red-500" - : "border-gray-200", + errors.receiptInfo?.companyInfo?.address?.street && + "border-destructive", )} /> {errors.receiptInfo?.companyInfo?.address?.street?.message && ( @@ -118,7 +115,9 @@ export const SellerForm = () => {
{ : false, })} className={cn( - "border-2", - errors.receiptInfo?.companyInfo?.address?.city - ? "border-red-500" - : "border-gray-200", + errors.receiptInfo?.companyInfo?.address?.city && + "border-destructive", )} /> {errors.receiptInfo?.companyInfo?.address?.city?.message && ( @@ -143,7 +140,9 @@ export const SellerForm = () => {
{ : false, })} className={cn( - "border-2", - errors.receiptInfo?.companyInfo?.address?.state - ? "border-red-500" - : "border-gray-200", + errors.receiptInfo?.companyInfo?.address?.state && + "border-destructive", )} /> {errors.receiptInfo?.companyInfo?.address?.state?.message && ( @@ -171,7 +168,9 @@ export const SellerForm = () => {
{ : false, })} className={cn( - "border-2", - errors.receiptInfo?.companyInfo?.address?.postalCode - ? "border-red-500" - : "border-gray-200", + errors.receiptInfo?.companyInfo?.address?.postalCode && + "border-destructive", )} /> {errors.receiptInfo?.companyInfo?.address?.postalCode?.message && ( @@ -196,7 +193,9 @@ export const SellerForm = () => {
{ : false, })} className={cn( - "border-2", - errors.receiptInfo?.companyInfo?.address?.country - ? "border-red-500" - : "border-gray-200", + errors.receiptInfo?.companyInfo?.address?.country && + "border-destructive", )} /> {errors.receiptInfo?.companyInfo?.address?.country?.message && ( diff --git a/src/app/(dashboard)/ecommerce/widget-playground/page.tsx b/src/app/(dashboard)/ecommerce/widget-playground/page.tsx index e69de29b..5231b227 100644 --- a/src/app/(dashboard)/ecommerce/widget-playground/page.tsx +++ b/src/app/(dashboard)/ecommerce/widget-playground/page.tsx @@ -0,0 +1,15 @@ +import { PageDescription, PageTitle } from "@/components/page-elements"; +import { Playground } from "./_components/playground-form"; + +export default function WidgetPlaygroundPage() { + return ( + <> + Widget Playground + + Customize and test the Request Network checkout widget. Copy the code to + integrate it into your website. + + + + ); +} diff --git a/src/components/navigation/sidebar.tsx b/src/components/navigation/sidebar.tsx index 3f50f618..50cec294 100644 --- a/src/components/navigation/sidebar.tsx +++ b/src/components/navigation/sidebar.tsx @@ -173,6 +173,16 @@ export function Sidebar() { > Sales + + Widget Playground +
)}
diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx new file mode 100644 index 00000000..59a26452 --- /dev/null +++ b/src/components/ui/command.tsx @@ -0,0 +1,153 @@ +"use client" + +import * as React from "react" +import { type DialogProps } from "@radix-ui/react-dialog" +import { Command as CommandPrimitive } from "cmdk" +import { Search } from "lucide-react" + +import { cn } from "@/lib/utils" +import { Dialog, DialogContent } from "@/components/ui/dialog" + +const Command = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Command.displayName = CommandPrimitive.displayName + +const CommandDialog = ({ children, ...props }: DialogProps) => { + return ( + + + + {children} + + + + ) +} + +const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ + +
+)) + +CommandInput.displayName = CommandPrimitive.Input.displayName + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandList.displayName = CommandPrimitive.List.displayName + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ( + +)) + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandGroup.displayName = CommandPrimitive.Group.displayName + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +CommandSeparator.displayName = CommandPrimitive.Separator.displayName + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandItem.displayName = CommandPrimitive.Item.displayName + +const CommandShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +CommandShortcut.displayName = "CommandShortcut" + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +} diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx new file mode 100644 index 00000000..483dc695 --- /dev/null +++ b/src/components/ui/popover.tsx @@ -0,0 +1,31 @@ +"use client" + +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "@/lib/utils" + +const Popover = PopoverPrimitive.Root + +const PopoverTrigger = PopoverPrimitive.Trigger + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)) +PopoverContent.displayName = PopoverPrimitive.Content.displayName + +export { Popover, PopoverTrigger, PopoverContent } diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx new file mode 100644 index 00000000..bc69cf2d --- /dev/null +++ b/src/components/ui/switch.tsx @@ -0,0 +1,29 @@ +"use client" + +import * as React from "react" +import * as SwitchPrimitives from "@radix-ui/react-switch" + +import { cn } from "@/lib/utils" + +const Switch = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +Switch.displayName = SwitchPrimitives.Root.displayName + +export { Switch } From 252e7e3001449094a0c8c0dff78da1b42f59ee92 Mon Sep 17 00:00:00 2001 From: Bassgeta Date: Fri, 24 Oct 2025 13:47:46 +0200 Subject: [PATCH 16/22] feat: add dropdown for client id select (now that we can) --- .../_components/clientid-select.tsx | 54 +++++++++++ .../_components/customize.tsx | 97 +++++++++---------- 2 files changed, 98 insertions(+), 53 deletions(-) create mode 100644 src/app/(dashboard)/ecommerce/widget-playground/_components/clientid-select.tsx diff --git a/src/app/(dashboard)/ecommerce/widget-playground/_components/clientid-select.tsx b/src/app/(dashboard)/ecommerce/widget-playground/_components/clientid-select.tsx new file mode 100644 index 00000000..0a2b6416 --- /dev/null +++ b/src/app/(dashboard)/ecommerce/widget-playground/_components/clientid-select.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { api } from "@/trpc/react"; + +interface ClientIdSelectProps { + value?: string; + onChange?: (value: string) => void; +} + +export function ClientIdSelect({ value, onChange }: ClientIdSelectProps) { + const { data: clients = [], isLoading } = api.ecommerce.getAll.useQuery(); + + if (isLoading) { + return ( + + ); + } + + if (clients.length === 0) { + return ( + + ); + } + + return ( + + ); +} diff --git a/src/app/(dashboard)/ecommerce/widget-playground/_components/customize.tsx b/src/app/(dashboard)/ecommerce/widget-playground/_components/customize.tsx index a2d59557..dffcaa31 100644 --- a/src/app/(dashboard)/ecommerce/widget-playground/_components/customize.tsx +++ b/src/app/(dashboard)/ecommerce/widget-playground/_components/customize.tsx @@ -16,11 +16,10 @@ import { Switch } from "@/components/ui/switch"; import { cn } from "@/lib/utils"; import { useEffect } from "react"; import { useFormContext } from "react-hook-form"; +import { ClientIdSelect } from "./clientid-select"; import { CurrencyCombobox } from "./currency-combobox"; import type { PlaygroundFormData } from "./validation"; -const EASY_INVOICE_URL = process.env.NEXT_PUBLIC_APP_URL; - export const CustomizeForm = () => { const { control, @@ -135,33 +134,31 @@ export const CustomizeForm = () => { )}
-
- - - {errors.paymentConfig?.rnApiClientId?.message && ( - {errors.paymentConfig.rnApiClientId.message} + ( + + + Request Network API Client ID + * + + + + + + Select your Client ID.{" "} + + Manage Client IDs + + + + )} -

- Get your Client ID on{" "} - - EasyInvoice - -

-
+ />
@@ -179,31 +176,25 @@ export const CustomizeForm = () => { />
-
- - ( - - Accepted Currencies - - - - - Select which currencies you want to accept for payments - - - - )} - /> -
+ ( + + + Supported Currencies + * + + + + + + Select which currencies you want to accept for payments + + + + )} + />
From 4d06646a3bbdb780b614c8b1654bf5f7cbc3c15d Mon Sep 17 00:00:00 2001 From: Bassgeta Date: Fri, 24 Oct 2025 14:04:13 +0200 Subject: [PATCH 17/22] feat: make the default checkout URL be easy invoice instead of checkout --- .env.example | 2 +- src/lib/constants/ecommerce.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index aaf811a3..cb4329f2 100644 --- a/.env.example +++ b/.env.example @@ -18,4 +18,4 @@ REDIS_URL=redis://localhost:7379 # NEXT_PUBLIC_GTM_ID="" # NEXT_PUBLIC_CRYPTO_TO_FIAT_TRUSTED_ORIGINS="" INVOICE_PROCESSING_TTL="60" -NEXT_PUBLIC_DEFAULT_ECOMMERCE_DOMAIN=http://localhost:3001 +NEXT_PUBLIC_DEFAULT_ECOMMERCE_DOMAIN=http://localhost:3000 diff --git a/src/lib/constants/ecommerce.ts b/src/lib/constants/ecommerce.ts index d8f6dc2b..52e02432 100644 --- a/src/lib/constants/ecommerce.ts +++ b/src/lib/constants/ecommerce.ts @@ -1,3 +1,3 @@ export const DEFAULT_CLIENT_ID_DOMAIN = process.env.NEXT_PUBLIC_DEFAULT_ECOMMERCE_DOMAIN || - "https://checkout.request.network"; + "https://easyinvoice.request.network"; From 844f7cbe3389b9a4e638fd94976171ab23fc08c4 Mon Sep 17 00:00:00 2001 From: Bassgeta Date: Fri, 24 Oct 2025 15:10:02 +0200 Subject: [PATCH 18/22] fix: recurring payment nav routes to payouts --- .../recurring/_components/recurring-payments-navigation.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/(dashboard)/payouts/recurring/_components/recurring-payments-navigation.tsx b/src/app/(dashboard)/payouts/recurring/_components/recurring-payments-navigation.tsx index 1082a7f7..10fa33c4 100644 --- a/src/app/(dashboard)/payouts/recurring/_components/recurring-payments-navigation.tsx +++ b/src/app/(dashboard)/payouts/recurring/_components/recurring-payments-navigation.tsx @@ -21,10 +21,10 @@ export function RecurringPaymentsNavigation() { - View Payouts + View Payouts - Create New + Create New From 38e093a948da01d2eea51b7b3eec8b373d78945e Mon Sep 17 00:00:00 2001 From: Bassgeta Date: Mon, 27 Oct 2025 14:27:44 +0100 Subject: [PATCH 19/22] fix: header and selection bug --- .../dashboard/subscriptions/_components/subscriptions.tsx | 1 + src/app/(dashboard)/invoices/_components/invoices-received.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/(dashboard)/dashboard/subscriptions/_components/subscriptions.tsx b/src/app/(dashboard)/dashboard/subscriptions/_components/subscriptions.tsx index 10cd50d7..d8274d07 100644 --- a/src/app/(dashboard)/dashboard/subscriptions/_components/subscriptions.tsx +++ b/src/app/(dashboard)/dashboard/subscriptions/_components/subscriptions.tsx @@ -53,6 +53,7 @@ const SubscriptionTableColumns = () => ( Chain Recipient Payment History + Actions ); diff --git a/src/app/(dashboard)/invoices/_components/invoices-received.tsx b/src/app/(dashboard)/invoices/_components/invoices-received.tsx index 8fef78a2..a908015b 100644 --- a/src/app/(dashboard)/invoices/_components/invoices-received.tsx +++ b/src/app/(dashboard)/invoices/_components/invoices-received.tsx @@ -106,7 +106,7 @@ export const InvoicesReceived = ({ setLastSelectedNetwork(invoiceNetwork); } else { setSelectedInvoices(selectedInvoices.filter((i) => i.id !== invoice.id)); - if (selectedInvoices.length === 1) { + if (selectedInvoices.length === 0) { setLastSelectedNetwork(null); } } From 9f7fa4a7fda08d2c02faa67141594e38c51f9d13 Mon Sep 17 00:00:00 2001 From: Bassgeta Date: Wed, 29 Oct 2025 09:21:35 +0100 Subject: [PATCH 20/22] feat: realign after code review --- .../_components/compliance-form.tsx | 28 +++++++++---------- .../_components/currency-combobox.tsx | 15 ++++++---- .../_components/playground-form.tsx | 5 +--- src/lib/helpers.ts | 6 ---- src/server/context.ts | 4 --- 5 files changed, 24 insertions(+), 34 deletions(-) diff --git a/src/app/(dashboard)/crypto-to-fiat/_components/compliance-form.tsx b/src/app/(dashboard)/crypto-to-fiat/_components/compliance-form.tsx index 7e611d19..c3b5c6c5 100644 --- a/src/app/(dashboard)/crypto-to-fiat/_components/compliance-form.tsx +++ b/src/app/(dashboard)/crypto-to-fiat/_components/compliance-form.tsx @@ -98,14 +98,15 @@ export function ComplianceForm({ user }: { user: User }) { ); useEffect(() => { - if (isComplianceSuccess) { + const complianceData = complianceApiData?.data; + if (isComplianceSuccess && complianceData) { setComplianceData({ - agreementUrl: (complianceApiData.data.agreementUrl as string) ?? null, - kycUrl: (complianceApiData.data.kycUrl as string) ?? null, + agreementUrl: (complianceData.agreementUrl as string) ?? null, + kycUrl: (complianceData.kycUrl as string) ?? null, status: { - agreementStatus: complianceApiData.data.agreementStatus as StatusType, - kycStatus: complianceApiData.data.kycStatus as StatusType, - isCompliant: complianceApiData.data.isCompliant, + agreementStatus: complianceData.agreementStatus as StatusType, + kycStatus: complianceData.kycStatus as StatusType, + isCompliant: complianceData.isCompliant, }, }); } @@ -216,14 +217,7 @@ export function ComplianceForm({ user }: { user: User }) { }, [handleAgreementUpdate, complianceData?.agreementUrl, TRUSTED_ORIGINS]); async function onSubmit(values: ComplianceFormValues) { - try { - await submitComplianceMutation.mutateAsync(values); - toast.success("Compliance information submitted successfully!"); - } catch (error) { - toast.error( - `Failed to submit compliance information${error instanceof Error ? `. Error: ${error.message}` : ". Please try again."}`, - ); - } + await submitComplianceMutation.mutateAsync(values); } return ( @@ -294,7 +288,11 @@ export function ComplianceForm({ user }: { user: User }) { className="w-full" onClick={() => { if (complianceData?.kycUrl) { - window.open(complianceData.kycUrl, "_blank"); + window.open( + complianceData.kycUrl, + "_blank", + "noopener,noreferrer", + ); } }} disabled={!complianceData?.kycUrl} diff --git a/src/app/(dashboard)/ecommerce/widget-playground/_components/currency-combobox.tsx b/src/app/(dashboard)/ecommerce/widget-playground/_components/currency-combobox.tsx index b446d860..d17bd0da 100644 --- a/src/app/(dashboard)/ecommerce/widget-playground/_components/currency-combobox.tsx +++ b/src/app/(dashboard)/ecommerce/widget-playground/_components/currency-combobox.tsx @@ -65,9 +65,10 @@ export function CurrencyCombobox({ + ))}
@@ -91,7 +92,11 @@ export function CurrencyCombobox({ - + diff --git a/src/app/(dashboard)/ecommerce/widget-playground/_components/playground-form.tsx b/src/app/(dashboard)/ecommerce/widget-playground/_components/playground-form.tsx index 1b5ee851..ba5d4044 100644 --- a/src/app/(dashboard)/ecommerce/widget-playground/_components/playground-form.tsx +++ b/src/app/(dashboard)/ecommerce/widget-playground/_components/playground-form.tsx @@ -25,10 +25,7 @@ export const Playground = () => { walletConnectProjectId: undefined, rnApiClientId: "YOUR_CLIENT_ID_HERE", supportedCurrencies: [], - feeInfo: { - feePercentage: "0", - feeAddress: "", - }, + feeInfo: undefined, }, uiConfig: { showRequestScanUrl: true, diff --git a/src/lib/helpers.ts b/src/lib/helpers.ts index bce7f46a..d73693b1 100644 --- a/src/lib/helpers.ts +++ b/src/lib/helpers.ts @@ -1,11 +1,5 @@ import { TRPCClientError } from "@trpc/client"; import { TRPCError } from "@trpc/server"; -import { type ClassValue, clsx } from "clsx"; -import { twMerge } from "tailwind-merge"; - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} /** * Removes undefined and null values from an object diff --git a/src/server/context.ts b/src/server/context.ts index 2e2f1dd4..02247ee1 100644 --- a/src/server/context.ts +++ b/src/server/context.ts @@ -1,4 +1,3 @@ -import type * as trpcNext from "@trpc/server/adapters/next"; import { getCurrentSession } from "./auth"; import { db } from "./db"; @@ -15,9 +14,6 @@ export async function createContext() { } export type Context = { - headers?: Headers; - req?: trpcNext.CreateNextContextOptions["req"]; - res?: trpcNext.CreateNextContextOptions["res"]; session: Awaited>["session"]; user: Awaited>["user"]; db: typeof db; From 0c197a3f68e26ef60418e840b276e2a58766068e Mon Sep 17 00:00:00 2001 From: Bassgeta Date: Wed, 29 Oct 2025 10:24:30 +0100 Subject: [PATCH 21/22] fix: code review align #2 --- .../_components/compliance-form.tsx | 4 +++- .../_components/playground-form.tsx | 4 ++-- src/lib/helpers.ts | 15 ++++++++++++++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/app/(dashboard)/crypto-to-fiat/_components/compliance-form.tsx b/src/app/(dashboard)/crypto-to-fiat/_components/compliance-form.tsx index c3b5c6c5..32896357 100644 --- a/src/app/(dashboard)/crypto-to-fiat/_components/compliance-form.tsx +++ b/src/app/(dashboard)/crypto-to-fiat/_components/compliance-form.tsx @@ -91,7 +91,9 @@ export function ComplianceForm({ user }: { user: User }) { // Only fetch if we have a user email enabled: !!user?.email, // Use the configurable constant for polling interval - refetchInterval: COMPLIANCE_STATUS_POLLING_INTERVAL, + refetchInterval: complianceData?.status?.isCompliant + ? false + : COMPLIANCE_STATUS_POLLING_INTERVAL, // Also refetch when the window regains focus refetchOnWindowFocus: true, }, diff --git a/src/app/(dashboard)/ecommerce/widget-playground/_components/playground-form.tsx b/src/app/(dashboard)/ecommerce/widget-playground/_components/playground-form.tsx index ba5d4044..f8101ef4 100644 --- a/src/app/(dashboard)/ecommerce/widget-playground/_components/playground-form.tsx +++ b/src/app/(dashboard)/ecommerce/widget-playground/_components/playground-form.tsx @@ -123,8 +123,8 @@ export const Playground = () => { ? paymentConfig.supportedCurrencies : undefined, feeInfo: - paymentConfig.feeInfo?.feeAddress || - paymentConfig.feeInfo?.feePercentage !== "0" + paymentConfig.feeInfo?.feeAddress && + paymentConfig.feeInfo?.feePercentage ? paymentConfig.feeInfo : undefined, }; diff --git a/src/lib/helpers.ts b/src/lib/helpers.ts index d73693b1..3520c482 100644 --- a/src/lib/helpers.ts +++ b/src/lib/helpers.ts @@ -16,8 +16,17 @@ export function filterDefinedValues>( export function truncateEmail(email: string, maxLength = 20): string { if (email.length <= maxLength) return email; + + if (!email.includes("@")) return email; + const [user, domain] = email.split("@"); - const keep = maxLength - domain.length - 4; + + if (maxLength <= domain.length + 4) { + return email; // Fall back to original email + } + + const keep = Math.max(1, maxLength - domain.length - 4); + return `${user.slice(0, keep)}...@${domain}`; } @@ -60,6 +69,10 @@ export async function retry( onRetry, } = options; + if (retries < 1) { + throw new Error("retries must be at least 1"); + } + let lastError: unknown; for (let attempt = 1; attempt <= retries; attempt++) { From 542f915bceed30d64c805dacca4a998ecf700368 Mon Sep 17 00:00:00 2001 From: Bassgeta Date: Wed, 29 Oct 2025 10:43:15 +0100 Subject: [PATCH 22/22] fix: code review align #3 --- .../_components/compliance-form.tsx | 9 ++------- .../_components/playground-form.tsx | 20 ++++++++++++++----- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/app/(dashboard)/crypto-to-fiat/_components/compliance-form.tsx b/src/app/(dashboard)/crypto-to-fiat/_components/compliance-form.tsx index 32896357..64310c61 100644 --- a/src/app/(dashboard)/crypto-to-fiat/_components/compliance-form.tsx +++ b/src/app/(dashboard)/crypto-to-fiat/_components/compliance-form.tsx @@ -31,7 +31,6 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { Skeleton } from "@/components/ui/skeleton"; import { COMPLIANCE_COUNTRIES } from "@/lib/constants/compliance"; import { BeneficiaryType, @@ -240,12 +239,8 @@ export function ComplianceForm({ user }: { user: User }) { )} - {isLoadingStatus && !complianceData ? ( -
- -
- ) : complianceData?.status.kycStatus !== "not_started" || - complianceData?.status.agreementStatus !== "not_started" ? ( + {complianceData?.status.kycStatus !== "not_started" || + complianceData?.status.agreementStatus !== "not_started" ? (
{ : undefined, }; + const buyer = formValues.receiptInfo.buyerInfo || {}; + const hasDirectFields = [ + buyer.email, + buyer.firstName, + buyer.lastName, + buyer.businessName, + buyer.phone, + ].some((v) => v); + const hasAddress = + buyer.address && Object.values(buyer.address).some((v) => v); + const cleanedreceiptInfo = { ...formValues.receiptInfo, - buyerInfo: Object.values(formValues.receiptInfo.buyerInfo || {}).some( - (val) => val, - ) - ? formValues.receiptInfo.buyerInfo - : undefined, + buyerInfo: + hasDirectFields || hasAddress + ? formValues.receiptInfo.buyerInfo + : undefined, }; return `