@qhristen/paygrid 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qhristen/paygrid",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Self-hosted Web3 payments infrastructure for Solana",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,49 +0,0 @@
1
- 'use client';
2
- import React, { useActionState } from 'react';
3
- import { login } from './actions';
4
- import { Loader2 } from 'lucide-react';
5
- export function LoginScreen() {
6
- const [state, formAction, isPending] = useActionState(login, null);
7
- return (<div className="min-h-screen flex items-center justify-center bg-[#050505] text-white overflow-hidden relative">
8
- <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-indigo-900/20 via-[#050505] to-[#050505] z-0 pointer-events-none"/>
9
-
10
- <div className="w-full max-w-md p-8 rounded-2xl border border-white/10 bg-[#111]/80 backdrop-blur-xl shadow-2xl z-10">
11
- <div className="mb-8 text-center">
12
- <div className="mx-auto w-12 h-12 bg-indigo-500 rounded-xl flex items-center justify-center mb-4 shadow-lg shadow-indigo-500/20">
13
- <span className="text-xl font-bold text-white">P</span>
14
- </div>
15
- <h1 className="text-2xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-white to-gray-400">Welcome Back</h1>
16
- <p className="text-gray-500 text-sm mt-2">Sign in to your PayGrid dashboard</p>
17
- </div>
18
-
19
- <form action={formAction} className="space-y-4">
20
- <div className="space-y-2">
21
- <label htmlFor="email" className="text-sm font-medium text-gray-300">Email</label>
22
- <input id="email" name="email" type="email" required placeholder="admin@paygrid.com" className="w-full bg-black/50 border border-white/10 rounded-lg px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500/50 transition-all placeholder:text-gray-600 hover:border-white/20"/>
23
- </div>
24
-
25
- <div className="space-y-2">
26
- <label htmlFor="password" className="text-sm font-medium text-gray-300">Password</label>
27
- <input id="password" name="password" type="password" required placeholder="••••••••" className="w-full bg-black/50 border border-white/10 rounded-lg px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500/50 transition-all placeholder:text-gray-600 hover:border-white/20"/>
28
- </div>
29
-
30
- {state?.error && (<div className="p-3 rounded-lg bg-red-500/10 border border-red-500/20 text-red-400 text-sm text-center">
31
- {state.error}
32
- </div>)}
33
-
34
- <button type="submit" disabled={isPending} className="w-full bg-indigo-600 hover:bg-indigo-500 text-white font-medium py-3 rounded-lg transition-all shadow-lg shadow-indigo-600/20 flex items-center justify-center gap-2 mt-6 disabled:opacity-50 disabled:cursor-not-allowed">
35
- {isPending ? (<>
36
- <Loader2 className="w-4 h-4 animate-spin"/>
37
- Signing in...
38
- </>) : ("Sign In")}
39
- </button>
40
-
41
- <div className="mt-6 text-center">
42
- <p className="text-xs text-gray-600">
43
- Secure admin access only
44
- </p>
45
- </div>
46
- </form>
47
- </div>
48
- </div>);
49
- }
@@ -1,235 +0,0 @@
1
- "use client";
2
- import { initWASM, isWASMSupported, ShadowWireClient, } from "@radr/shadowwire";
3
- import { useEffect, useState } from "react";
4
- import { SUPPORTED_TOKENS } from "../config";
5
- import { PaymentMethod } from "../types";
6
- export function CheckoutModal({ amount: propsAmount, method: propsMethod, tokenSymbol: propsTokenSymbol, sender: propsSender, onPaymentResponse, }) {
7
- const [isCheckoutOpen, setIsCheckoutOpen] = useState(false);
8
- const [step, setStep] = useState("config");
9
- const [selectedToken, setSelectedToken] = useState(propsTokenSymbol);
10
- const [selectedMethod, setSelectedMethod] = useState(propsMethod === "manual-transfer"
11
- ? PaymentMethod.MANUAL_TRANSFER
12
- : PaymentMethod.WALLET_SIGNING);
13
- const [client] = useState(() => new ShadowWireClient());
14
- const [amount, setAmount] = useState(propsAmount);
15
- const [activeIntent, setActiveIntent] = useState(null);
16
- const [isLoading, setIsLoading] = useState(false);
17
- const [error, setError] = useState(null);
18
- const apiKey = process.env.NEXT_PUBLIC_PAYGRID_API_SECRET;
19
- const [wasmInitialized, setWasmInitialized] = useState(false);
20
- const [balance, setBalance] = useState(null);
21
- useEffect(() => {
22
- async function init() {
23
- if (!isWASMSupported()) {
24
- setError("WebAssembly not supported");
25
- return;
26
- }
27
- try {
28
- await initWASM("/wasm/settler_wasm_bg.wasm");
29
- setWasmInitialized(true);
30
- await loadBalance();
31
- }
32
- catch (err) {
33
- setError("Initialization failed: " + err.message);
34
- }
35
- }
36
- init();
37
- }, []);
38
- const loadBalance = async () => {
39
- try {
40
- const data = await client.getBalance(propsSender, selectedToken);
41
- setBalance(data.available / 1e9);
42
- }
43
- catch (err) {
44
- console.error("Balance load failed:", err);
45
- }
46
- };
47
- const handleStartPayment = async () => {
48
- try {
49
- setIsLoading(true);
50
- setError(null);
51
- const headers = {};
52
- if (apiKey)
53
- headers["x-api-key"] = apiKey;
54
- const response = await fetch("/api/paygrid/payment-intents", {
55
- method: "POST",
56
- headers: {
57
- ...headers,
58
- "Content-Type": "application/json",
59
- },
60
- body: JSON.stringify({
61
- amount,
62
- method: propsMethod,
63
- tokenSymbol: selectedToken,
64
- sender: propsSender,
65
- }),
66
- });
67
- // setStep("paying");
68
- if (!response.ok) {
69
- throw new Error(`Payment intent creation failed: ${response.statusText}`);
70
- }
71
- const data = (await response.json());
72
- if (onPaymentResponse) {
73
- onPaymentResponse(data);
74
- }
75
- setActiveIntent(data);
76
- setStep("paying");
77
- }
78
- catch (err) {
79
- const errorMessage = err instanceof Error ? err.message : "An error occurred";
80
- setError(errorMessage);
81
- console.error("Payment error:", err);
82
- }
83
- finally {
84
- setIsLoading(false);
85
- }
86
- };
87
- return (<div>
88
- <button onClick={() => setIsCheckoutOpen(true)} className="bg-white/5 hover:bg-white/10 cursor-pointer border border-white/10 text-white px-8 py-4 rounded-2xl font-bold text-lg transition-all">
89
- Pay now
90
- </button>
91
-
92
- {isCheckoutOpen && (<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
93
- <div className="absolute inset-0 bg-black/80 backdrop-blur-sm" onClick={() => setIsCheckoutOpen(false)}></div>
94
-
95
- <div className="relative w-full max-w-md bg-[#111] border border-white/10 rounded-3xl overflow-hidden shadow-2xl">
96
- <div className="p-6">
97
- <div className="flex justify-between items-center mb-6">
98
- <div className="flex items-center gap-2">
99
- <div className="w-6 h-6 bg-indigo-600 rounded flex items-center justify-center text-[10px] font-bold">
100
- P
101
- </div>
102
- <span className="font-bold">PayGrid checkout</span>
103
- </div>
104
- <button onClick={() => setIsCheckoutOpen(false)} className="text-gray-500 cursor-pointer hover:text-white">
105
- &times;
106
- </button>
107
- </div>
108
-
109
- {step === "config" && (<div className="space-y-6">
110
- <div>
111
- <label className="text-xs text-gray-500 font-bold uppercase block mb-2">
112
- Select Token
113
- </label>
114
- <div className="grid grid-cols-3 gap-2">
115
- {SUPPORTED_TOKENS.map((token) => (<button key={token.symbol} disabled={token.disabled} onClick={() => setSelectedToken(token.symbol)} className={`p-3 rounded-xl cursor-pointer border transition-all flex flex-col items-center gap-1 ${selectedToken === token.symbol ? "border-indigo-500 bg-indigo-500/10" : "border-white/5 bg-white/5 hover:border-white/20"}`}>
116
- <div className="w-6 h-6 rounded-full" style={{ backgroundColor: token.color }}></div>
117
- <span className="text-sm font-medium">
118
- {token.symbol}
119
- </span>
120
- </button>))}
121
- </div>
122
- </div>
123
-
124
- <div>
125
- <label className="text-xs text-gray-500 font-bold uppercase block mb-2">
126
- Payment Method
127
- </label>
128
- <div className="space-y-2">
129
- <button onClick={() => setSelectedMethod(PaymentMethod.WALLET_SIGNING)} className={`w-full p-4 cursor-pointer rounded-xl border text-left flex items-center gap-3 transition-all ${selectedMethod === PaymentMethod.WALLET_SIGNING ? "border-indigo-500 bg-indigo-500/10" : "border-white/5 bg-white/5"}`}>
130
- <div className="p-2 bg-white/5 rounded-lg">⚡</div>
131
- <div>
132
- <p className="text-sm font-semibold">
133
- Wallet Signing
134
- </p>
135
- <p className="text-xs text-gray-500">
136
- Sign with Phantom, Solflare or Backpack
137
- </p>
138
- </div>
139
- </button>
140
- <button onClick={() => setSelectedMethod(PaymentMethod.MANUAL_TRANSFER)} disabled className={`w-full p-4 cursor-pointer rounded-xl border text-left flex items-center gap-3 transition-all ${selectedMethod === PaymentMethod.MANUAL_TRANSFER ? "border-indigo-500 bg-indigo-500/10" : "border-white/5 bg-white/5"}`}>
141
- <div className="p-2 bg-white/5 rounded-lg">📋</div>
142
- <div>
143
- <p className="text-sm font-semibold">
144
- Manual Transfer
145
- </p>
146
- <p className="text-xs text-gray-500">
147
- Send tokens to a unique address
148
- </p>
149
- </div>
150
- </button>
151
- </div>
152
- </div>
153
-
154
- <div className="pt-4 border-t border-white/10">
155
- <div className="flex justify-between items-end mb-4">
156
- <span className="text-gray-400 text-sm">Total Due</span>
157
- <div className="text-right">
158
- <span className="text-2xl font-bold">
159
- {amount} {selectedToken}
160
- </span>
161
- <p className="text-xs text-gray-500">{`~${amount} ${selectedToken}`}</p>
162
- </div>
163
- </div>
164
- {error && (<div className="bg-red-500/10 border border-red-500/20 p-3 rounded-lg mb-4">
165
- <p className="text-xs text-red-400">{error}</p>
166
- </div>)}
167
- <button onClick={handleStartPayment} disabled={isLoading} className="w-full bg-white text-black py-4 rounded-2xl font-bold hover:bg-gray-200 transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
168
- {isLoading ? "Processing..." : "Confirm & Pay"}
169
- </button>
170
- </div>
171
- </div>)}
172
-
173
- {step === "paying" && (<div className="text-center py-8">
174
- <div className="relative w-24 h-24 mx-auto mb-6">
175
- <div className="absolute inset-0 border-4 border-indigo-500/20 rounded-full"></div>
176
- <div className="absolute inset-0 border-4 border-indigo-500 border-t-transparent rounded-full animate-spin"></div>
177
- <div className="absolute inset-0 flex items-center justify-center font-bold text-lg">
178
- {selectedToken}
179
- </div>
180
- </div>
181
- <h3 className="text-xl font-bold mb-2">
182
- Awaiting Transaction
183
- </h3>
184
- <p className="text-gray-500 text-sm mb-6 max-w-[280px] mx-auto">
185
- Please complete the transaction in your wallet. We are
186
- monitoring the Solana network.
187
- </p>
188
- <div className="bg-white/5 border border-white/10 p-4 rounded-2xl font-mono text-xs text-left">
189
- <div className="flex justify-between mb-1">
190
- <span className="text-gray-500">Network</span>
191
- <span>Solana Mainnet</span>
192
- </div>
193
- <div className="flex justify-between mb-1">
194
- <span className="text-gray-500">Status</span>
195
- <span className="text-amber-400">Monitoring...</span>
196
- </div>
197
- <div className="flex justify-between">
198
- <span className="text-gray-500">Intent ID</span>
199
- <span>{activeIntent?.id}</span>
200
- </div>
201
- </div>
202
- </div>)}
203
-
204
- {step === "success" && (<div className="text-center py-8">
205
- <div className="w-20 h-20 bg-emerald-500/20 text-emerald-500 rounded-full mx-auto flex items-center justify-center text-4xl mb-6">
206
-
207
- </div>
208
- <h3 className="text-2xl font-bold mb-2">Payment Settled!</h3>
209
- <p className="text-gray-400 text-sm mb-6">
210
- Your transaction has been confirmed on the blockchain.
211
- </p>
212
-
213
- {/* <div className="space-y-3 text-left">
214
- <div className="bg-white/5 p-4 rounded-2xl border border-white/5">
215
- <p className="text-[10px] text-gray-500 uppercase font-bold mb-1">
216
- Transaction Hash
217
- </p>
218
- <p className="text-xs font-mono text-indigo-400 truncate">
219
- {activeIntent?.transactionSignature}
220
- </p>
221
- </div>
222
- </div> */}
223
- </div>)}
224
- </div>
225
-
226
- <div className="bg-white/5 p-4 text-center">
227
- <p className="text-[10px] text-gray-500 font-medium">
228
- Powering the decentralized privacy economy with PayGrid
229
- </p>
230
- </div>
231
- </div>
232
- </div>)}
233
- </div>);
234
- }
235
- export default CheckoutModal;
@@ -1,146 +0,0 @@
1
- 'use client';
2
- import React, { useState, useEffect } from 'react';
3
- const ApiKeysSection = ({ apiKey, baseUrl }) => {
4
- const [keys, setKeys] = useState([]);
5
- const [isLoading, setIsLoading] = useState(false);
6
- const [newKeyData, setNewKeyData] = useState(null);
7
- const fetchKeys = async () => {
8
- setIsLoading(true);
9
- try {
10
- const headers = {};
11
- if (apiKey)
12
- headers['x-api-key'] = apiKey;
13
- const res = await fetch(`${baseUrl}/api-keys`, { headers });
14
- if (res.ok) {
15
- const data = await res.json();
16
- setKeys(data);
17
- }
18
- }
19
- catch (error) {
20
- console.error('Failed to fetch API keys:', error);
21
- }
22
- finally {
23
- setIsLoading(false);
24
- }
25
- };
26
- const createKey = async () => {
27
- const name = prompt('Enter a name for the new API key:');
28
- if (!name)
29
- return;
30
- setIsLoading(true);
31
- try {
32
- const headers = {
33
- 'Content-Type': 'application/json',
34
- };
35
- if (apiKey)
36
- headers['x-api-key'] = apiKey;
37
- const res = await fetch(`${baseUrl}/api-keys`, {
38
- method: 'POST',
39
- headers,
40
- body: JSON.stringify({ name }),
41
- });
42
- if (res.ok) {
43
- const result = await res.json();
44
- setNewKeyData({ key: result.key, name: result.apiKey.name });
45
- fetchKeys();
46
- }
47
- }
48
- catch (error) {
49
- console.error('Failed to create API key:', error);
50
- }
51
- finally {
52
- setIsLoading(false);
53
- }
54
- };
55
- const revokeKey = async (id, name) => {
56
- if (!confirm(`Are you sure you want to revoke the key "${name}"? This action cannot be undone.`))
57
- return;
58
- setIsLoading(true);
59
- try {
60
- const headers = {};
61
- if (apiKey)
62
- headers['x-api-key'] = apiKey;
63
- const res = await fetch(`${baseUrl}/api-keys/${id}`, {
64
- method: 'DELETE',
65
- headers
66
- });
67
- if (res.ok) {
68
- fetchKeys();
69
- }
70
- }
71
- catch (error) {
72
- console.error('Failed to revoke API key:', error);
73
- }
74
- finally {
75
- setIsLoading(false);
76
- }
77
- };
78
- useEffect(() => {
79
- fetchKeys();
80
- }, [apiKey, baseUrl]);
81
- return (<div className="space-y-6">
82
- <div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
83
- <div>
84
- <h3 className="text-xl font-bold">API Access Tokens</h3>
85
- <p className="text-gray-500 text-sm">Generate keys to integrate PayGrid into your applications.</p>
86
- </div>
87
- <button onClick={createKey} disabled={isLoading} className="w-full md:w-auto bg-indigo-600 hover:bg-indigo-500 text-white px-4 py-2 rounded-xl text-sm font-semibold transition-colors disabled:opacity-50">
88
- {isLoading ? 'Creating...' : '+ Create New Key'}
89
- </button>
90
- </div>
91
-
92
- {newKeyData && (<div className="bg-emerald-500/10 border border-emerald-500/20 p-6 rounded-2xl relative">
93
- <button onClick={() => setNewKeyData(null)} className="absolute top-4 right-4 text-gray-400 hover:text-white">
94
-
95
- </button>
96
- <h4 className="text-emerald-400 font-semibold mb-2">New API Key Created!</h4>
97
- <p className="text-sm text-gray-400 mb-4">Make sure to copy your API key now. You won't be able to see it again.</p>
98
- <div className="flex items-center gap-2">
99
- <pre className="bg-black/50 p-4 rounded-xl font-mono text-sm text-emerald-300 flex-1 overflow-x-auto">
100
- {newKeyData.key}
101
- </pre>
102
- <button onClick={() => navigator.clipboard.writeText(newKeyData.key)} className="bg-white/5 hover:bg-white/10 p-4 rounded-xl transition-colors" title="Copy to clipboard">
103
- 📋
104
- </button>
105
- </div>
106
- </div>)}
107
-
108
- <div className="grid gap-4">
109
- {isLoading && keys.length === 0 ? (<div className="text-center py-12 text-gray-500">Loading keys...</div>) : keys.length === 0 ? (<div className="text-center py-12 text-gray-500 font-medium bg-[#111] border border-white/10 rounded-2xl">
110
- No API keys found. Create one to get started.
111
- </div>) : (keys.map(key => (<div key={key.id} className="bg-[#111] border border-white/10 p-4 md:p-6 rounded-2xl flex flex-col md:flex-row md:justify-between md:items-center gap-4">
112
- <div className="flex items-center gap-4">
113
- <div className="w-10 h-10 bg-white/5 rounded-full flex-shrink-0 flex items-center justify-center">
114
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#888" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 2-2 2.23"/><path d="M7 22 22.77 6.23a2 2 0 0 0 0-2.83l-.77-.77a2 2 0 0 0-2.83 0L3.41 18.41A2 2 0 0 0 3 19.83V22h2.17a2 2 0 0 0 1.42-.59Z"/><path d="m15 5 4 4"/></svg>
115
- </div>
116
- <div className="min-w-0">
117
- <h4 className="font-semibold truncate">{key.name}</h4>
118
- <p className="text-xs font-mono text-gray-500 mt-1">{key.keyHint}••••••••••••••••</p>
119
- </div>
120
- </div>
121
- <div className="flex items-center justify-between md:justify-end gap-6 border-t md:border-t-0 border-white/5 pt-4 md:pt-0">
122
- <div className="text-left md:text-right">
123
- <p className="text-[10px] text-gray-500 uppercase font-bold">Created</p>
124
- <p className="text-sm">{new Date(key.createdAt).toLocaleDateString()}</p>
125
- </div>
126
- <button onClick={() => revokeKey(key.id, key.name)} className="text-red-400 hover:text-red-300 text-xs font-semibold p-2">
127
- Revoke
128
- </button>
129
- </div>
130
- </div>)))}
131
- </div>
132
-
133
- <div className="bg-indigo-500/5 border border-indigo-500/20 p-6 rounded-2xl">
134
- <h4 className="text-indigo-400 font-semibold mb-2">Integration Guide</h4>
135
- <p className="text-sm text-gray-400 mb-4">Initialize the SDK in your Next.js application using your API secret key.</p>
136
- <pre className="bg-black/50 p-4 rounded-xl font-mono text-xs text-indigo-300 overflow-x-auto">
137
- {`const paygrid = initPayGrid({
138
- apiKey: process.env.PAYGRID_API_KEY,
139
- rpcUrl: 'https://api.devnet.solana.com',
140
- network: 'devnet'
141
- });`}
142
- </pre>
143
- </div>
144
- </div>);
145
- };
146
- export default ApiKeysSection;
@@ -1,13 +0,0 @@
1
- import React from 'react';
2
- export const SUPPORTED_TOKENS = [
3
- { symbol: 'SOL', mint: '11111111111111111111111111111111', color: '#14F195' },
4
- { symbol: 'USDC', mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', color: '#2775CA' },
5
- { symbol: 'BONK', mint: 'DezXAZ8z7PnrnRJjz3wXBoRgixJ6WoPBw5DRF6S49t38', color: '#FFA500' }
6
- ];
7
- export const Icons = {
8
- Dashboard: () => (<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect width="7" height="9" x="3" y="3" rx="1"/><rect width="7" height="5" x="14" y="3" rx="1"/><rect width="7" height="9" x="14" y="12" rx="1"/><rect width="7" height="5" x="3" y="16" rx="1"/></svg>),
9
- Payments: () => (<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect width="20" height="14" x="2" y="5" rx="2"/><line x1="2" x2="22" y1="10" y2="10"/></svg>),
10
- Key: () => (<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m21 2-2 2.23"/><path d="M7 22 22.77 6.23a2 2 0 0 0 0-2.83l-.77-.77a2 2 0 0 0-2.83 0L3.41 18.41A2 2 0 0 0 3 19.83V22h2.17a2 2 0 0 0 1.42-.59Z"/><path d="m15 5 4 4"/></svg>),
11
- Search: () => (<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>),
12
- External: () => (<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/></svg>)
13
- };