Skip to content

Commit 8329a9c

Browse files
committed
feat: ui polishing update
1 parent 7279de7 commit 8329a9c

7 files changed

Lines changed: 173 additions & 42 deletions

File tree

backend/src/routes/webhooks.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const bulkRetrySchema = z.object({
4747
* 500:
4848
* description: Server error
4949
*/
50-
router.get("/webhook-logs", async (req, res, next) => {
50+
router.get("/webhook-logs", requireApiKeyAuth(), async (req, res, next) => {
5151
try {
5252
const { cursor, limit, status } = req.query;
5353
const result = await merchantService.getWebhookLogs(req.merchant.id, {
@@ -126,7 +126,7 @@ router.get("/notifications", requireApiKeyAuth(), async (req, res, next) => {
126126
}
127127
});
128128

129-
router.post("/webhooks/retry-bulk", async (req, res, next) => {
129+
router.post("/webhooks/retry-bulk", requireApiKeyAuth(), async (req, res, next) => {
130130
try {
131131
const body = bulkRetrySchema.parse(req.body || {});
132132
const result = await queueBulkWebhookRetries({

frontend/public/sw.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/src/app/(authenticated)/layout.tsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import NotificationCenter from "@/components/NotificationCenter";
88
import PaymentToastListener from "@/components/PaymentToastListener";
99
import Sidebar from "@/components/Sidebar";
1010
import SupportOverlay from "@/components/SupportOverlay";
11+
import UserNav from "@/components/UserNav";
1112
import { useHydrateMerchantStore } from "@/lib/merchant-store";
1213
import { motion } from "framer-motion";
1314

@@ -30,28 +31,31 @@ export default function AuthenticatedLayout({
3031
<PaymentToastListener />
3132

3233
<main className="min-w-0 flex-1 flex flex-col overflow-hidden">
33-
<header className="shrink-0 flex h-16 items-center border-b border-[#E8E8E8] bg-white px-8 z-30">
34+
<header className="shrink-0 flex h-20 items-center border-b border-[#F5F5F5] bg-white px-8 z-30">
3435
<div className="flex w-full items-center justify-between">
35-
<div className="flex items-center gap-4">
36+
<div className="flex items-center gap-6">
3637
<button
3738
type="button"
3839
onClick={() => setMobileSidebarOpen(true)}
39-
className="inline-flex h-10 w-10 items-center justify-center rounded-lg border border-[#E8E8E8] bg-white text-[#6B6B6B] transition-all hover:bg-[#F5F5F5] hover:text-[#0A0A0A] lg:hidden"
40+
className="inline-flex h-10 w-10 items-center justify-center rounded-xl border border-[#E8E8E8] bg-white text-[#6B6B6B] transition-all hover:border-[#0A0A0A] hover:bg-[#F9F9F9] hover:text-[#0A0A0A] lg:hidden"
4041
aria-label="Open navigation menu"
4142
aria-controls="dashboard-sidebar-mobile"
4243
aria-expanded={mobileSidebarOpen}
4344
>
4445
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
45-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 7h16M4 12h16M4 17h16" />
46+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.8} d="M4 7h16M4 12h16M4 17h16" />
4647
</svg>
4748
</button>
4849
<Breadcrumbs />
4950
</div>
50-
<div className="flex items-center gap-6">
51-
<NotificationCenter />
52-
<LocaleSwitcher className="text-[#0A0A0A] border-none bg-transparent" />
53-
<div className="h-8 w-px bg-[#E8E8E8]" />
54-
<div className="h-8 w-8 rounded-full bg-[#F5F5F5] border border-[#E8E8E8]" />
51+
52+
<div className="flex items-center gap-4">
53+
<div className="flex items-center gap-2 mr-2">
54+
<NotificationCenter />
55+
<LocaleSwitcher className="text-[#0A0A0A] border-none bg-transparent hover:bg-[#F5F5F5] rounded-lg transition-colors p-1" />
56+
</div>
57+
<div className="h-8 w-px bg-[#E8E8E8] mx-2" />
58+
<UserNav />
5559
</div>
5660
</div>
5761
</header>

frontend/src/app/(public)/page.tsx

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"use client";
22

3-
import GuestGuard from "@/components/GuestGuard";
43
import SystemStatus from "@/components/SystemStatus";
54
import Link from "next/link";
65
import { useState } from "react";
@@ -510,18 +509,16 @@ function Footer() {
510509

511510
export default function Home() {
512511
return (
513-
<GuestGuard>
514-
<main className="relative min-h-screen bg-white overflow-x-hidden">
515-
<HeroSection />
516-
<IntegrationModesSection />
517-
<PricingSection />
518-
<FeaturesSection />
519-
<HowItWorksSection />
520-
<CodeSnippetSection />
521-
<PayWithLinkDemo />
522-
<CTASection />
523-
<Footer />
524-
</main>
525-
</GuestGuard>
512+
<main className="relative min-h-screen bg-white overflow-x-hidden">
513+
<HeroSection />
514+
<IntegrationModesSection />
515+
<PricingSection />
516+
<FeaturesSection />
517+
<HowItWorksSection />
518+
<CodeSnippetSection />
519+
<PayWithLinkDemo />
520+
<CTASection />
521+
<Footer />
522+
</main>
526523
);
527524
}

frontend/src/components/MerchantProfileCard.tsx

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,27 @@ export default function MerchantProfileCard() {
107107

108108
{/* Menu Items */}
109109
<div className="flex flex-col gap-1.5">
110+
<Link
111+
href="/dashboard"
112+
onClick={() => setShowDropdown(false)}
113+
className="flex items-center gap-3 rounded-xl px-3 py-2.5 text-[11px] font-bold uppercase tracking-widest text-[#0A0A0A] bg-[#F5F5F5] transition-all hover:bg-[#E8E8E8]"
114+
>
115+
<svg
116+
className="h-4 w-4"
117+
fill="none"
118+
stroke="currentColor"
119+
viewBox="0 0 24 24"
120+
>
121+
<path
122+
strokeLinecap="round"
123+
strokeLinejoin="round"
124+
strokeWidth={2}
125+
d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"
126+
/>
127+
</svg>
128+
Dashboard
129+
</Link>
130+
110131
<Link
111132
href="/settings"
112133
onClick={() => setShowDropdown(false)}
@@ -176,19 +197,7 @@ export default function MerchantProfileCard() {
176197
Logout Account
177198
</button>
178199
</div>
179-
180-
{/* Network Info */}
181-
<div className="mt-5 rounded-xl border border-[#E8E8E8] bg-[#F9F9F9] p-4">
182-
<p className="text-[9px] font-bold uppercase tracking-widest text-[#6B6B6B]">
183-
Partner Since
184-
</p>
185-
<p className="mt-1 text-xs font-bold text-[#0A0A0A]">
186-
{merchant?.created_at
187-
? new Date(merchant.created_at).toLocaleDateString()
188-
: "N/A"}
189-
</p>
190-
</div>
191-
</div>
200+
</div>
192201
</>
193202
)}
194203
</div>
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
"use client";
2+
3+
import { useState, useRef, useEffect } from "react";
4+
import Link from "next/link";
5+
import {
6+
useMerchantMetadata,
7+
useMerchantLogout
8+
} from "@/lib/merchant-store";
9+
import { useRouter } from "next/navigation";
10+
11+
export default function UserNav() {
12+
const [open, setOpen] = useState(false);
13+
const merchant = useMerchantMetadata();
14+
const logout = useMerchantLogout();
15+
const router = useRouter();
16+
const menuRef = useRef<HTMLDivElement>(null);
17+
18+
useEffect(() => {
19+
const handleClickOutside = (event: MouseEvent) => {
20+
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
21+
setOpen(false);
22+
}
23+
};
24+
document.addEventListener("mousedown", handleClickOutside);
25+
return () => document.removeEventListener("mousedown", handleClickOutside);
26+
}, []);
27+
28+
const handleLogout = () => {
29+
logout();
30+
router.push("/login");
31+
};
32+
33+
if (!merchant) return null;
34+
35+
const initials = merchant.business_name
36+
? merchant.business_name.split(" ").map(n => n[0]).join("").toUpperCase().slice(0, 2)
37+
: merchant.email[0].toUpperCase();
38+
39+
return (
40+
<div className="relative" ref={menuRef}>
41+
<button
42+
onClick={() => setOpen(!open)}
43+
className="flex items-center gap-3 rounded-full border border-[#E8E8E8] bg-white p-1 pr-4 transition-all hover:border-[#0A0A0A] hover:shadow-sm active:scale-[0.98]"
44+
>
45+
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-[#0A0A0A] text-[10px] font-bold text-white uppercase tracking-tighter">
46+
{initials}
47+
</div>
48+
<div className="hidden flex-col items-start text-left sm:flex">
49+
<span className="text-[10px] font-bold uppercase tracking-widest text-[#0A0A0A]">
50+
{merchant.business_name}
51+
</span>
52+
<span className="text-[9px] font-medium text-[#6B6B6B]">
53+
{merchant.email}
54+
</span>
55+
</div>
56+
</button>
57+
58+
{open && (
59+
<div className="absolute right-0 mt-3 w-56 origin-top-right rounded-2xl border border-[#E8E8E8] bg-white p-2 shadow-2xl animate-in fade-in zoom-in-95 duration-200 z-50">
60+
<div className="px-3 py-2 border-b border-[#F5F5F5] mb-1 sm:hidden">
61+
<p className="text-[10px] font-bold uppercase tracking-widest text-[#0A0A0A] truncate">{merchant.business_name}</p>
62+
<p className="text-[9px] font-medium text-[#6B6B6B] truncate">{merchant.email}</p>
63+
</div>
64+
65+
<Link
66+
href="/dashboard"
67+
onClick={() => setOpen(false)}
68+
className="flex items-center gap-3 rounded-xl px-3 py-2.5 text-[10px] font-bold uppercase tracking-widest text-[#0A0A0A] bg-[#F5F5F5] transition-all hover:bg-[#E8E8E8]"
69+
>
70+
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
71+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.8} d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
72+
</svg>
73+
Dashboard
74+
</Link>
75+
76+
<Link
77+
href="/settings"
78+
onClick={() => setOpen(false)}
79+
className="flex items-center gap-3 rounded-xl px-3 py-2.5 text-[10px] font-bold uppercase tracking-widest text-[#6B6B6B] transition-colors hover:bg-[#F9F9F9] hover:text-[#0A0A0A]"
80+
>
81+
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
82+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.8} d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
83+
</svg>
84+
Account Settings
85+
</Link>
86+
87+
<Link
88+
href="/support"
89+
onClick={() => setOpen(false)}
90+
className="flex items-center gap-3 rounded-xl px-3 py-2.5 text-[10px] font-bold uppercase tracking-widest text-[#6B6B6B] transition-colors hover:bg-[#F9F9F9] hover:text-[#0A0A0A]"
91+
>
92+
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
93+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.8} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
94+
</svg>
95+
Support Center
96+
</Link>
97+
98+
<div className="my-1 h-px bg-[#F5F5F5]" />
99+
100+
<button
101+
onClick={handleLogout}
102+
className="flex w-full items-center gap-3 rounded-xl px-3 py-2.5 text-[10px] font-bold uppercase tracking-widest text-red-600 transition-colors hover:bg-red-50"
103+
>
104+
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
105+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.8} d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
106+
</svg>
107+
Sign Out
108+
</button>
109+
</div>
110+
)}
111+
</div>
112+
);
113+
}

frontend/src/lib/merchant-store.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,11 @@ export const useMerchantStore = create<MerchantStore>((set) => ({
108108

109109
hydrate: () => {
110110
const { token, session } = readInitialToken();
111-
const apiKey =
112-
typeof window === "undefined" ? null : localStorage.getItem(API_KEY_KEY);
113111
const merchant = readInitialMerchant();
112+
const apiKey =
113+
(typeof window === "undefined" ? null : localStorage.getItem(API_KEY_KEY)) ||
114+
merchant?.api_key ||
115+
null;
114116

115117
// Dev Bypass: Provide mock data if enabled and no session exists
116118
const isBypass = typeof window !== "undefined" &&
@@ -181,11 +183,17 @@ export const useMerchantStore = create<MerchantStore>((set) => ({
181183
if (typeof window !== "undefined") {
182184
if (merchant) {
183185
localStorage.setItem(MERCHANT_KEY, JSON.stringify(merchant));
186+
if (merchant.api_key) {
187+
localStorage.setItem(API_KEY_KEY, merchant.api_key);
188+
}
184189
} else {
185190
localStorage.removeItem(MERCHANT_KEY);
186191
}
187192
}
188-
set({ merchant });
193+
set((state) => ({
194+
merchant,
195+
apiKey: merchant?.api_key || state.apiKey
196+
}));
189197
},
190198

191199
addTrustedAddress: (address) => {

0 commit comments

Comments
 (0)