Skip to content

Commit a4547a6

Browse files
authored
Implemented all logged:admin privy, client connect privy, expert tool privy and lib auth privy (#682)
1 parent e6e648e commit a4547a6

7 files changed

Lines changed: 151 additions & 19 deletions

File tree

dashboard/app/dashboard/layout.tsx

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,37 @@
11
"use client";
22
import { useEffect } from "react";
33
import { useRouter } from "next/navigation";
4-
import { useAuth } from "@/lib/auth-context";
4+
import { usePrivy } from "@privy-io/react-auth";
55
import Sidebar from "@/components/Sidebar";
66
import SearchBar from "@/components/SearchBar";
77

88
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
9-
const { token } = useAuth();
9+
const { authenticated, login } = usePrivy();
1010
const router = useRouter();
1111

1212
useEffect(() => {
13-
if (token === null && typeof window !== "undefined" && !localStorage.getItem("token")) {
14-
router.replace("/login");
13+
if (!authenticated) {
14+
router.replace("/");
1515
}
16-
}, [token, router]);
16+
}, [authenticated, router]);
17+
18+
if (!authenticated) {
19+
return (
20+
<div className="flex min-h-screen items-center justify-center bg-slate-50">
21+
<div className="bg-white border border-slate-200 rounded-2xl shadow-sm p-8 w-full max-w-sm text-center">
22+
<span className="text-3xl"></span>
23+
<h1 className="text-xl font-bold text-slate-900 mt-2">Zaps Merchant</h1>
24+
<p className="text-sm text-slate-500 mt-2">Sign in with Privy to access the dashboard</p>
25+
<button
26+
onClick={() => login()}
27+
className="mt-4 w-full bg-indigo-600 text-white py-2.5 rounded-lg text-sm font-medium hover:bg-indigo-700 disabled:opacity-50 transition-colors"
28+
>
29+
Sign in with Privy
30+
</button>
31+
</div>
32+
</div>
33+
);
34+
}
1735

1836
return (
1937
<div className="flex min-h-screen">

dashboard/app/dashboard/page.tsx

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

3+
import { useCallback } from "react";
34
import StatCard from "@/components/StatCard";
45
import { api } from "@/lib/api";
56
import { usePolling } from "@/lib/use-polling";
@@ -13,6 +14,30 @@ function fmtUsdc(value: number): string {
1314
);
1415
}
1516

17+
function maskEmail(email: string): string {
18+
const [local, domain] = email.split("@");
19+
if (!domain) return "***";
20+
const masked = local.length > 1 ? local[0] + "***" : "*";
21+
return `${masked}@${domain}`;
22+
}
23+
24+
function maskPhone(phone: string): string {
25+
if (phone.length <= 4) return "***";
26+
return phone.slice(0, 3) + "***" + phone.slice(-2);
27+
}
28+
29+
function downloadCSV(rows: string[], filename: string): void {
30+
const blob = new Blob([rows.join("\n")], { type: "text/csv;charset=utf-8;" });
31+
const url = URL.createObjectURL(blob);
32+
const link = document.createElement("a");
33+
link.href = url;
34+
link.download = filename;
35+
document.body.appendChild(link);
36+
link.click();
37+
document.body.removeChild(link);
38+
URL.revokeObjectURL(url);
39+
}
40+
1641
export default function OverviewPage() {
1742
const { data: feedData, loading: feedLoading, error: feedError } = usePolling(
1843
() => api.socialFeed(),
@@ -37,16 +62,50 @@ export default function OverviewPage() {
3762
const yieldDistributed = yieldData?.total_yield_distributed ?? 0;
3863
const apy = yieldData?.apy ?? 0;
3964

65+
const handleExportUsers = useCallback(async () => {
66+
try {
67+
const links = await api.identityLinks();
68+
const headers = ["User ID", "Privy DID", "Stellar Address", "Display Name", "Email", "Phone", "Status", "Linked At"];
69+
const csvRows = [headers.join(",")];
70+
for (const link of links.links) {
71+
const email = link.email ? maskEmail(link.email) : "";
72+
const phone = link.display_name ? maskPhone(link.display_name) : "";
73+
csvRows.push([
74+
link.user_id,
75+
link.privy_did,
76+
link.stellar_address,
77+
link.display_name ?? "",
78+
email,
79+
phone,
80+
link.status,
81+
link.linked_at,
82+
].map((v) => `"${v}"`).join(","));
83+
}
84+
downloadCSV(csvRows, `privy-users-export-${new Date().toISOString().slice(0, 10)}.csv`);
85+
} catch {
86+
// silently fail
87+
}
88+
}, []);
89+
4090
return (
4191
<div>
42-
{/* Social Overview */}
43-
<div className="mb-6">
44-
<h1 className="text-2xl font-bold text-slate-900">Social Overview</h1>
45-
<p className="mt-1 text-sm text-slate-500">
46-
Live engagement across recent payment feeds.
47-
</p>
92+
<div className="flex items-center justify-between mb-6">
93+
<div>
94+
<h1 className="text-2xl font-bold text-slate-900">Social Overview</h1>
95+
<p className="mt-1 text-sm text-slate-500">
96+
Live engagement across recent payment feeds.
97+
</p>
98+
</div>
99+
<button
100+
onClick={handleExportUsers}
101+
className="inline-flex items-center gap-2 rounded-lg border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 transition-colors"
102+
>
103+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" x2="12" y1="15" y2="3"/></svg>
104+
Export Users (CSV)
105+
</button>
48106
</div>
49107

108+
{/* Social Overview */}
50109
{feedError && (
51110
<div className="mb-4 rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700">
52111
{feedError} — showing the most recently loaded values

dashboard/app/layout.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Metadata } from "next";
22
import "./globals.css";
3-
import { AuthProvider } from "@/lib/auth-context";
3+
import { PrivyProvider } from "@privy-io/react-auth";
44

55
export const metadata: Metadata = {
66
title: "Zaps Merchant Dashboard",
@@ -11,7 +11,15 @@ export default function RootLayout({ children }: { children: React.ReactNode })
1111
return (
1212
<html lang="en">
1313
<body>
14-
<AuthProvider>{children}</AuthProvider>
14+
<PrivyProvider
15+
appId={process.env.NEXT_PUBLIC_PRIVY_APP_ID || ""}
16+
config={{
17+
loginMethods: ["google", "apple", "email"],
18+
appearance: { theme: "light" },
19+
}}
20+
>
21+
{children}
22+
</PrivyProvider>
1523
</body>
1624
</html>
1725
);

dashboard/app/page.tsx

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,41 @@
1-
import { redirect } from "next/navigation";
1+
"use client";
2+
import { useRouter } from "next/navigation";
3+
import { usePrivy } from "@privy-io/react-auth";
4+
import { useEffect } from "react";
25

3-
export default function Home() {
4-
redirect("/dashboard");
6+
export default function LoginPage() {
7+
const { authenticated, login, logout } = usePrivy();
8+
const router = useRouter();
9+
10+
useEffect(() => {
11+
if (authenticated) {
12+
router.replace("/dashboard");
13+
}
14+
}, [authenticated, router]);
15+
16+
return (
17+
<div className="min-h-screen flex items-center justify-center bg-slate-50">
18+
<div className="bg-white border border-slate-200 rounded-2xl shadow-sm p-8 w-full max-w-sm">
19+
<div className="text-center mb-6">
20+
<span className="text-3xl"></span>
21+
<h1 className="text-xl font-bold text-slate-900 mt-2">Zaps Merchant</h1>
22+
<p className="text-sm text-slate-500">Sign in with Privy to continue</p>
23+
</div>
24+
<button
25+
onClick={() => login()}
26+
className="w-full bg-indigo-600 text-white py-2.5 rounded-lg text-sm font-medium hover:bg-indigo-700 disabled:opacity-50 transition-colors"
27+
>
28+
Sign in with Privy
29+
</button>
30+
{authenticated && (
31+
<button
32+
onClick={() => { logout(); router.replace("/"); }}
33+
className="mt-3 w-full border border-slate-300 text-slate-700 py-2.5 rounded-lg text-sm font-medium hover:bg-slate-50 transition-colors"
34+
>
35+
Sign out
36+
</button>
37+
)}
38+
</div>
39+
</div>
40+
);
541
}

dashboard/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"e2e:report": "playwright show-report"
1313
},
1414
"dependencies": {
15+
"@privy-io/react-auth": "^1.14.0",
1516
"@stellar/freighter-api": "6.0.1",
1617
"@stellar/stellar-sdk": "^15.1.0",
1718
"@types/qrcode": "^1.5.6",

mobileapp/app/_layout.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import React from "react";
44
import { Stack, useRouter, usePathname } from "expo-router";
55
import { StatusBar } from "expo-status-bar";
66
import { View } from "react-native";
7+
import { PrivyProvider } from "@privy-io/expo-sdk";
78
import { COLORS } from "../src/constants/colors";
89
import { useFonts } from "expo-font";
910
import { Anton_400Regular } from "@expo-google-fonts/anton";
@@ -177,8 +178,16 @@ export default function Layout() {
177178
}
178179

179180
return (
180-
<ErrorBoundary>
181-
<LayoutContent />
182-
</ErrorBoundary>
181+
<PrivyProvider
182+
appId={process.env.EXPO_PUBLIC_PRIVY_APP_ID || ""}
183+
config={{
184+
loginMethods: ["google", "apple", "email"],
185+
appearance: { theme: "light" },
186+
}}
187+
>
188+
<ErrorBoundary>
189+
<LayoutContent />
190+
</ErrorBoundary>
191+
</PrivyProvider>
183192
);
184193
}

mobileapp/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"@expo-google-fonts/outfit": "^0.4.3",
1919
"@expo/metro-runtime": "~6.1.2",
2020
"@expo/vector-icons": "^15.0.3",
21+
"@privy-io/expo-sdk": "^1.14.0",
2122
"@react-native-async-storage/async-storage": "^2.0.0",
2223
"@react-native-community/netinfo": "^11.3.1",
2324
"@react-navigation/native": "^7.2.5",

0 commit comments

Comments
 (0)