@djangocfg/layouts 1.0.3 → 1.0.5
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 +5 -5
- package/src/layouts/AppLayout/layouts/AuthLayout/AuthHelp.tsx +2 -2
- package/src/layouts/AppLayout/layouts/AuthLayout/OTPForm.tsx +6 -6
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardSidebar.tsx +1 -1
- package/src/layouts/AppLayout/layouts/PublicLayout/components/DesktopUserMenu.tsx +6 -6
- package/src/layouts/AppLayout/layouts/PublicLayout/components/Footer.tsx +1 -1
- package/src/layouts/AppLayout/layouts/PublicLayout/components/MobileMenu.tsx +43 -133
- package/src/layouts/AppLayout/layouts/PublicLayout/components/MobileMenuUserCard.tsx +150 -0
- package/src/layouts/AppLayout/layouts/PublicLayout/components/Navigation.tsx +2 -2
- package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +47 -65
- package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +121 -144
- package/src/layouts/PaymentsLayout/components/PaymentDetailsDialog.tsx +103 -48
- package/src/layouts/PaymentsLayout/components/index.ts +1 -4
- package/src/layouts/PaymentsLayout/events.ts +23 -84
- package/src/layouts/PaymentsLayout/index.ts +7 -11
- package/src/layouts/PaymentsLayout/types.ts +3 -16
- package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +45 -16
- package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +18 -14
- package/src/layouts/PaymentsLayout/views/overview/components/index.ts +0 -2
- package/src/layouts/PaymentsLayout/views/overview/index.tsx +3 -6
- package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +51 -31
- package/src/layouts/PaymentsLayout/views/payments/components/index.ts +0 -1
- package/src/layouts/PaymentsLayout/views/payments/index.tsx +1 -2
- package/src/layouts/PaymentsLayout/views/transactions/components/TransactionsList.tsx +273 -0
- package/src/layouts/PaymentsLayout/views/transactions/components/index.ts +1 -0
- package/src/layouts/PaymentsLayout/views/transactions/index.tsx +5 -17
- package/src/layouts/SupportLayout/hooks/useInfiniteMessages.ts +2 -3
- package/src/layouts/SupportLayout/hooks/useInfiniteTickets.ts +2 -3
- package/src/snippets/Chat/components/SessionList.tsx +1 -1
- package/src/snippets/Chat/hooks/useInfiniteSessions.ts +2 -2
- package/src/snippets/VideoPlayer/VideoPlayer.tsx +1 -1
- package/src/layouts/PaymentsLayout/README.md +0 -133
- package/src/layouts/PaymentsLayout/components/CreateApiKeyDialog.tsx +0 -172
- package/src/layouts/PaymentsLayout/components/DeleteApiKeyDialog.tsx +0 -100
- package/src/layouts/PaymentsLayout/context/RootPaymentsContext.tsx +0 -134
- package/src/layouts/PaymentsLayout/views/apikeys/components/ApiKeyMetrics.tsx +0 -109
- package/src/layouts/PaymentsLayout/views/apikeys/components/ApiKeysList.tsx +0 -194
- package/src/layouts/PaymentsLayout/views/apikeys/components/index.ts +0 -3
- package/src/layouts/PaymentsLayout/views/apikeys/index.tsx +0 -19
- package/src/layouts/PaymentsLayout/views/overview/components/MetricsCards.tsx +0 -103
- package/src/layouts/PaymentsLayout/views/tariffs/index.tsx +0 -29
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
# Payments Layout
|
|
2
|
-
|
|
3
|
-
Modern payments layout with tabbed interface for managing payments, balances, API keys, and subscriptions.
|
|
4
|
-
|
|
5
|
-
## Structure
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
PaymentsLayout/
|
|
9
|
-
├── PaymentsLayout.tsx # Main layout with tabs and providers
|
|
10
|
-
├── events.ts # Event-based dialog communication
|
|
11
|
-
├── types.ts # TypeScript types
|
|
12
|
-
├── index.ts # Public exports
|
|
13
|
-
└── views/ # Tab views
|
|
14
|
-
├── overview/ # Dashboard with metrics and balance
|
|
15
|
-
│ ├── components/
|
|
16
|
-
│ │ ├── MetricsCards.tsx
|
|
17
|
-
│ │ ├── BalanceCard.tsx
|
|
18
|
-
│ │ ├── RecentPayments.tsx
|
|
19
|
-
│ │ └── index.ts
|
|
20
|
-
│ └── index.tsx
|
|
21
|
-
├── payments/ # Payment transactions list
|
|
22
|
-
│ ├── components/
|
|
23
|
-
│ │ ├── PaymentsList.tsx
|
|
24
|
-
│ │ └── index.ts
|
|
25
|
-
│ └── index.tsx
|
|
26
|
-
├── transactions/ # Transaction history (placeholder)
|
|
27
|
-
│ └── index.tsx
|
|
28
|
-
├── apikeys/ # API keys management
|
|
29
|
-
│ ├── components/
|
|
30
|
-
│ │ ├── ApiKeysList.tsx
|
|
31
|
-
│ │ ├── ApiKeyMetrics.tsx
|
|
32
|
-
│ │ └── index.ts
|
|
33
|
-
│ └── index.tsx
|
|
34
|
-
└── tariffs/ # Tariff plans (placeholder)
|
|
35
|
-
└── index.tsx
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
## Usage
|
|
39
|
-
|
|
40
|
-
```tsx
|
|
41
|
-
import { PaymentsLayout } from '@djangocfg/layouts';
|
|
42
|
-
|
|
43
|
-
function PaymentsPage() {
|
|
44
|
-
return <PaymentsLayout />;
|
|
45
|
-
}
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Features
|
|
49
|
-
|
|
50
|
-
### Tabs
|
|
51
|
-
- **Overview** - Dashboard with key metrics, balance card, and recent payments
|
|
52
|
-
- **Payments** - Full payment history with search and filters
|
|
53
|
-
- **Transactions** - Balance transaction history (coming soon)
|
|
54
|
-
- **API Keys** - Create, manage, and monitor API keys
|
|
55
|
-
- **Tariffs** - Subscription plans and pricing (coming soon)
|
|
56
|
-
|
|
57
|
-
### Contexts
|
|
58
|
-
The layout automatically provides all necessary contexts:
|
|
59
|
-
- `OverviewProvider` - Dashboard metrics and overview data
|
|
60
|
-
- `PaymentsProvider` - Payment CRUD operations
|
|
61
|
-
- `BalancesProvider` - User balance management
|
|
62
|
-
- `ApiKeysProvider` - API key management
|
|
63
|
-
|
|
64
|
-
### Event System
|
|
65
|
-
Uses event-based communication for dialogs:
|
|
66
|
-
- `openCreateApiKeyDialog()` - Open create API key dialog
|
|
67
|
-
- `openEditApiKeyDialog(id)` - Open edit API key dialog
|
|
68
|
-
- `openDeleteApiKeyDialog(id)` - Open delete confirmation dialog
|
|
69
|
-
- `openCreatePaymentDialog()` - Open create payment dialog
|
|
70
|
-
- `openPaymentDetailsDialog(id)` - Open payment details dialog
|
|
71
|
-
- `openCancelPaymentDialog(id)` - Open cancel payment dialog
|
|
72
|
-
- `closePaymentsDialog()` - Close any active dialog
|
|
73
|
-
|
|
74
|
-
## Components
|
|
75
|
-
|
|
76
|
-
### Overview View
|
|
77
|
-
- **MetricsCards** - Display key metrics (balance, payments, API keys)
|
|
78
|
-
- **BalanceCard** - Show current balance with quick actions
|
|
79
|
-
- **RecentPayments** - List of recent payment transactions
|
|
80
|
-
|
|
81
|
-
### Payments View
|
|
82
|
-
- **PaymentsList** - Paginated payment list with filters and search
|
|
83
|
-
|
|
84
|
-
### API Keys View
|
|
85
|
-
- **ApiKeyMetrics** - API key usage statistics
|
|
86
|
-
- **ApiKeysList** - List of API keys with management actions
|
|
87
|
-
|
|
88
|
-
## Responsive Design
|
|
89
|
-
- Mobile-first responsive layout
|
|
90
|
-
- Adaptive tabs (icons only on mobile, text on desktop)
|
|
91
|
-
- Responsive grid layouts for cards and metrics
|
|
92
|
-
|
|
93
|
-
## Architecture
|
|
94
|
-
|
|
95
|
-
### Decomposed Contexts
|
|
96
|
-
Uses specialized contexts from `@djangocfg/api/cfg/contexts`:
|
|
97
|
-
- `PaymentsContext` - Payment operations
|
|
98
|
-
- `BalancesContext` - Balance queries
|
|
99
|
-
- `ApiKeysContext` - API key CRUD
|
|
100
|
-
- `OverviewContext` - Dashboard data
|
|
101
|
-
|
|
102
|
-
### View-based Structure
|
|
103
|
-
Each tab is a separate view with:
|
|
104
|
-
- Dedicated components folder
|
|
105
|
-
- Dedicated hooks folder (for future custom hooks)
|
|
106
|
-
- Clean separation of concerns
|
|
107
|
-
|
|
108
|
-
### Event-driven Dialogs
|
|
109
|
-
Dialogs are controlled via events, not props:
|
|
110
|
-
- Loose coupling between components
|
|
111
|
-
- Easy to trigger from anywhere
|
|
112
|
-
- Centralized dialog management
|
|
113
|
-
|
|
114
|
-
## Migration from Old Layout
|
|
115
|
-
|
|
116
|
-
The old `PaymentsLayout_old` has been replaced with this new structure. Key improvements:
|
|
117
|
-
|
|
118
|
-
1. **Better Organization** - Views are logically separated
|
|
119
|
-
2. **Decomposed Contexts** - Each context has single responsibility
|
|
120
|
-
3. **Event System** - Better dialog management
|
|
121
|
-
4. **Scalability** - Easy to add new tabs and features
|
|
122
|
-
5. **Type Safety** - Full TypeScript support
|
|
123
|
-
|
|
124
|
-
## Future Enhancements
|
|
125
|
-
|
|
126
|
-
- [ ] Complete Transactions view with filtering
|
|
127
|
-
- [ ] Complete Tariffs view with plan comparison
|
|
128
|
-
- [ ] Add payment creation dialogs
|
|
129
|
-
- [ ] Add API key creation/edit dialogs
|
|
130
|
-
- [ ] Add real-time payment status updates
|
|
131
|
-
- [ ] Add charts and analytics
|
|
132
|
-
- [ ] Add export functionality
|
|
133
|
-
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Create API Key Dialog
|
|
3
|
-
* Dialog for creating new API keys
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
'use client';
|
|
7
|
-
|
|
8
|
-
import React, { useState, useEffect } from 'react';
|
|
9
|
-
import {
|
|
10
|
-
Dialog,
|
|
11
|
-
DialogContent,
|
|
12
|
-
DialogDescription,
|
|
13
|
-
DialogFooter,
|
|
14
|
-
DialogHeader,
|
|
15
|
-
DialogTitle,
|
|
16
|
-
Form,
|
|
17
|
-
FormControl,
|
|
18
|
-
FormDescription,
|
|
19
|
-
FormField,
|
|
20
|
-
FormItem,
|
|
21
|
-
FormLabel,
|
|
22
|
-
FormMessage,
|
|
23
|
-
Input,
|
|
24
|
-
Button,
|
|
25
|
-
useEventListener,
|
|
26
|
-
} from '@djangocfg/ui';
|
|
27
|
-
import { Plus, RefreshCw } from 'lucide-react';
|
|
28
|
-
import { useForm } from 'react-hook-form';
|
|
29
|
-
import { zodResolver } from '@hookform/resolvers/zod';
|
|
30
|
-
import { useApiKeysContext } from '@djangocfg/api/cfg/contexts';
|
|
31
|
-
import { Schemas } from '@djangocfg/api/cfg/generated';
|
|
32
|
-
import { paymentsLogger } from '../../../utils/logger';
|
|
33
|
-
|
|
34
|
-
const { APIKeyCreateRequestSchema } = Schemas;
|
|
35
|
-
type APIKeyCreateRequest = Schemas.APIKeyCreateRequest;
|
|
36
|
-
import { PAYMENTS_DIALOG_EVENTS, closePaymentsDialog } from '../events';
|
|
37
|
-
|
|
38
|
-
export const CreateApiKeyDialog: React.FC = () => {
|
|
39
|
-
const [open, setOpen] = useState(false);
|
|
40
|
-
const [initialData, setInitialData] = useState<any>();
|
|
41
|
-
const { createApiKey } = useApiKeysContext();
|
|
42
|
-
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
43
|
-
|
|
44
|
-
const form = useForm<APIKeyCreateRequest>({
|
|
45
|
-
resolver: zodResolver(APIKeyCreateRequestSchema),
|
|
46
|
-
defaultValues: {
|
|
47
|
-
name: '',
|
|
48
|
-
expires_in_days: null,
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// Event listeners
|
|
53
|
-
useEventListener(PAYMENTS_DIALOG_EVENTS.OPEN_CREATE_APIKEY_DIALOG, (event: any) => {
|
|
54
|
-
setInitialData(event.payload?.initialKeyData);
|
|
55
|
-
setOpen(true);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
useEventListener(PAYMENTS_DIALOG_EVENTS.CLOSE_PAYMENTS_DIALOG, () => {
|
|
59
|
-
setOpen(false);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// Reset form when initial data changes
|
|
63
|
-
useEffect(() => {
|
|
64
|
-
if (initialData) {
|
|
65
|
-
form.reset({
|
|
66
|
-
name: initialData.name || '',
|
|
67
|
-
expires_in_days: initialData.expires_in_days || undefined,
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
}, [initialData, form]);
|
|
71
|
-
|
|
72
|
-
const handleClose = () => {
|
|
73
|
-
setOpen(false);
|
|
74
|
-
form.reset();
|
|
75
|
-
setInitialData(undefined);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const handleSubmit = async (data: APIKeyCreateRequest) => {
|
|
79
|
-
try {
|
|
80
|
-
setIsSubmitting(true);
|
|
81
|
-
await createApiKey(data);
|
|
82
|
-
handleClose();
|
|
83
|
-
closePaymentsDialog();
|
|
84
|
-
} catch (error) {
|
|
85
|
-
paymentsLogger.error('Failed to create API key:', error);
|
|
86
|
-
} finally {
|
|
87
|
-
setIsSubmitting(false);
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
return (
|
|
92
|
-
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && handleClose()}>
|
|
93
|
-
<DialogContent className="sm:max-w-md">
|
|
94
|
-
<DialogHeader>
|
|
95
|
-
<DialogTitle>Create API Key</DialogTitle>
|
|
96
|
-
<DialogDescription>
|
|
97
|
-
Create a new API key for secure access to the platform.
|
|
98
|
-
</DialogDescription>
|
|
99
|
-
</DialogHeader>
|
|
100
|
-
|
|
101
|
-
<Form {...form}>
|
|
102
|
-
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4">
|
|
103
|
-
<FormField
|
|
104
|
-
control={form.control}
|
|
105
|
-
name="name"
|
|
106
|
-
render={({ field }) => (
|
|
107
|
-
<FormItem>
|
|
108
|
-
<FormLabel>Name</FormLabel>
|
|
109
|
-
<FormControl>
|
|
110
|
-
<Input placeholder="My API Key" {...field} />
|
|
111
|
-
</FormControl>
|
|
112
|
-
<FormDescription>
|
|
113
|
-
A descriptive name for your API key.
|
|
114
|
-
</FormDescription>
|
|
115
|
-
<FormMessage />
|
|
116
|
-
</FormItem>
|
|
117
|
-
)}
|
|
118
|
-
/>
|
|
119
|
-
|
|
120
|
-
<FormField
|
|
121
|
-
control={form.control}
|
|
122
|
-
name="expires_in_days"
|
|
123
|
-
render={({ field }) => (
|
|
124
|
-
<FormItem>
|
|
125
|
-
<FormLabel>Expiration (Days)</FormLabel>
|
|
126
|
-
<FormControl>
|
|
127
|
-
<Input
|
|
128
|
-
type="number"
|
|
129
|
-
placeholder="30"
|
|
130
|
-
min="1"
|
|
131
|
-
max="365"
|
|
132
|
-
{...field}
|
|
133
|
-
onChange={(e) => {
|
|
134
|
-
const value = e.target.value;
|
|
135
|
-
field.onChange(value ? parseInt(value, 10) : null);
|
|
136
|
-
}}
|
|
137
|
-
value={field.value || ''}
|
|
138
|
-
/>
|
|
139
|
-
</FormControl>
|
|
140
|
-
<FormDescription>
|
|
141
|
-
Number of days until expiration. Leave empty for no expiration.
|
|
142
|
-
</FormDescription>
|
|
143
|
-
<FormMessage />
|
|
144
|
-
</FormItem>
|
|
145
|
-
)}
|
|
146
|
-
/>
|
|
147
|
-
|
|
148
|
-
<DialogFooter>
|
|
149
|
-
<Button type="button" variant="outline" onClick={handleClose}>
|
|
150
|
-
Cancel
|
|
151
|
-
</Button>
|
|
152
|
-
<Button type="submit" disabled={isSubmitting || !form.formState.isValid}>
|
|
153
|
-
{isSubmitting ? (
|
|
154
|
-
<>
|
|
155
|
-
<RefreshCw className="h-4 w-4 mr-2 animate-spin" />
|
|
156
|
-
Creating...
|
|
157
|
-
</>
|
|
158
|
-
) : (
|
|
159
|
-
<>
|
|
160
|
-
<Plus className="h-4 w-4 mr-2" />
|
|
161
|
-
Create Key
|
|
162
|
-
</>
|
|
163
|
-
)}
|
|
164
|
-
</Button>
|
|
165
|
-
</DialogFooter>
|
|
166
|
-
</form>
|
|
167
|
-
</Form>
|
|
168
|
-
</DialogContent>
|
|
169
|
-
</Dialog>
|
|
170
|
-
);
|
|
171
|
-
};
|
|
172
|
-
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Delete API Key Dialog
|
|
3
|
-
* Confirmation dialog for deleting API keys
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
'use client';
|
|
7
|
-
|
|
8
|
-
import React, { useState } from 'react';
|
|
9
|
-
import {
|
|
10
|
-
Dialog,
|
|
11
|
-
DialogContent,
|
|
12
|
-
DialogDescription,
|
|
13
|
-
DialogFooter,
|
|
14
|
-
DialogHeader,
|
|
15
|
-
DialogTitle,
|
|
16
|
-
Button,
|
|
17
|
-
useEventListener,
|
|
18
|
-
} from '@djangocfg/ui';
|
|
19
|
-
import { Trash2, RefreshCw } from 'lucide-react';
|
|
20
|
-
import { useApiKeysContext } from '@djangocfg/api/cfg/contexts';
|
|
21
|
-
import { paymentsLogger } from '../../../utils/logger';
|
|
22
|
-
import { PAYMENTS_DIALOG_EVENTS, closePaymentsDialog } from '../events';
|
|
23
|
-
|
|
24
|
-
export const DeleteApiKeyDialog: React.FC = () => {
|
|
25
|
-
const [open, setOpen] = useState(false);
|
|
26
|
-
const [keyId, setKeyId] = useState<string>('');
|
|
27
|
-
const [isDeleting, setIsDeleting] = useState(false);
|
|
28
|
-
const { deleteApiKey } = useApiKeysContext();
|
|
29
|
-
|
|
30
|
-
useEventListener(PAYMENTS_DIALOG_EVENTS.OPEN_DELETE_APIKEY_DIALOG, (payload: any) => {
|
|
31
|
-
const id = payload?.keyId || '';
|
|
32
|
-
setKeyId(id);
|
|
33
|
-
setOpen(true);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
useEventListener(PAYMENTS_DIALOG_EVENTS.CLOSE_PAYMENTS_DIALOG, () => {
|
|
37
|
-
setOpen(false);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
const handleClose = () => {
|
|
41
|
-
setOpen(false);
|
|
42
|
-
setKeyId('');
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const handleDelete = async () => {
|
|
46
|
-
if (!keyId) return;
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
setIsDeleting(true);
|
|
50
|
-
await deleteApiKey(keyId);
|
|
51
|
-
// Add a small delay to ensure the list refreshes before closing
|
|
52
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
53
|
-
handleClose();
|
|
54
|
-
closePaymentsDialog();
|
|
55
|
-
} catch (error) {
|
|
56
|
-
paymentsLogger.error('Failed to delete API key:', error);
|
|
57
|
-
} finally {
|
|
58
|
-
setIsDeleting(false);
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && handleClose()}>
|
|
64
|
-
<DialogContent className="sm:max-w-md">
|
|
65
|
-
<DialogHeader>
|
|
66
|
-
<DialogTitle>Delete API Key</DialogTitle>
|
|
67
|
-
<DialogDescription>
|
|
68
|
-
Are you sure you want to delete this API key? This action cannot be undone and will
|
|
69
|
-
immediately revoke access for any applications using this key.
|
|
70
|
-
</DialogDescription>
|
|
71
|
-
</DialogHeader>
|
|
72
|
-
|
|
73
|
-
<DialogFooter>
|
|
74
|
-
<Button type="button" variant="outline" onClick={handleClose} disabled={isDeleting}>
|
|
75
|
-
Cancel
|
|
76
|
-
</Button>
|
|
77
|
-
<Button
|
|
78
|
-
type="button"
|
|
79
|
-
variant="destructive"
|
|
80
|
-
onClick={handleDelete}
|
|
81
|
-
disabled={isDeleting}
|
|
82
|
-
>
|
|
83
|
-
{isDeleting ? (
|
|
84
|
-
<>
|
|
85
|
-
<RefreshCw className="h-4 w-4 mr-2 animate-spin" />
|
|
86
|
-
Deleting...
|
|
87
|
-
</>
|
|
88
|
-
) : (
|
|
89
|
-
<>
|
|
90
|
-
<Trash2 className="h-4 w-4 mr-2" />
|
|
91
|
-
Delete Key
|
|
92
|
-
</>
|
|
93
|
-
)}
|
|
94
|
-
</Button>
|
|
95
|
-
</DialogFooter>
|
|
96
|
-
</DialogContent>
|
|
97
|
-
</Dialog>
|
|
98
|
-
);
|
|
99
|
-
};
|
|
100
|
-
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React, { createContext, useContext, useEffect, type ReactNode } from 'react';
|
|
4
|
-
import {
|
|
5
|
-
api,
|
|
6
|
-
usePaymentsCurrenciesList,
|
|
7
|
-
usePaymentsProviderCurrenciesList,
|
|
8
|
-
usePaymentsNetworksList,
|
|
9
|
-
} from '@djangocfg/api/cfg';
|
|
10
|
-
import type { API } from '@djangocfg/api/cfg';
|
|
11
|
-
import type {
|
|
12
|
-
Currency,
|
|
13
|
-
PaginatedCurrencyListList,
|
|
14
|
-
ProviderCurrency,
|
|
15
|
-
PaginatedProviderCurrencyList,
|
|
16
|
-
Network,
|
|
17
|
-
PaginatedNetworkList,
|
|
18
|
-
} from '@djangocfg/api/cfg';
|
|
19
|
-
|
|
20
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
21
|
-
// Context Type
|
|
22
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
23
|
-
|
|
24
|
-
export interface RootPaymentsContextValue {
|
|
25
|
-
// Currencies
|
|
26
|
-
currencies: PaginatedCurrencyListList | undefined;
|
|
27
|
-
isLoadingCurrencies: boolean;
|
|
28
|
-
currenciesError: Error | undefined;
|
|
29
|
-
refreshCurrencies: () => Promise<void>;
|
|
30
|
-
|
|
31
|
-
// Provider Currencies
|
|
32
|
-
providerCurrencies: PaginatedProviderCurrencyList | undefined;
|
|
33
|
-
isLoadingProviderCurrencies: boolean;
|
|
34
|
-
providerCurrenciesError: Error | undefined;
|
|
35
|
-
refreshProviderCurrencies: () => Promise<void>;
|
|
36
|
-
|
|
37
|
-
// Networks
|
|
38
|
-
networks: PaginatedNetworkList | undefined;
|
|
39
|
-
isLoadingNetworks: boolean;
|
|
40
|
-
networksError: Error | undefined;
|
|
41
|
-
refreshNetworks: () => Promise<void>;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
45
|
-
// Context
|
|
46
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
47
|
-
|
|
48
|
-
const RootPaymentsContext = createContext<RootPaymentsContextValue | undefined>(undefined);
|
|
49
|
-
|
|
50
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
51
|
-
// Provider
|
|
52
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
53
|
-
|
|
54
|
-
export function RootPaymentsProvider({ children }: { children: ReactNode }) {
|
|
55
|
-
// List all currencies
|
|
56
|
-
const {
|
|
57
|
-
data: currencies,
|
|
58
|
-
error: currenciesError,
|
|
59
|
-
isLoading: isLoadingCurrencies,
|
|
60
|
-
mutate: mutateCurrencies,
|
|
61
|
-
} = usePaymentsCurrenciesList({}, api as unknown as API);
|
|
62
|
-
|
|
63
|
-
// List all provider currencies
|
|
64
|
-
const {
|
|
65
|
-
data: providerCurrencies,
|
|
66
|
-
error: providerCurrenciesError,
|
|
67
|
-
isLoading: isLoadingProviderCurrencies,
|
|
68
|
-
mutate: mutateProviderCurrencies,
|
|
69
|
-
} = usePaymentsProviderCurrenciesList({}, api as unknown as API);
|
|
70
|
-
|
|
71
|
-
// List all networks
|
|
72
|
-
const {
|
|
73
|
-
data: networks,
|
|
74
|
-
error: networksError,
|
|
75
|
-
isLoading: isLoadingNetworks,
|
|
76
|
-
mutate: mutateNetworks,
|
|
77
|
-
} = usePaymentsNetworksList({}, api as unknown as API);
|
|
78
|
-
|
|
79
|
-
const refreshCurrencies = async () => {
|
|
80
|
-
await mutateCurrencies();
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const refreshProviderCurrencies = async () => {
|
|
84
|
-
await mutateProviderCurrencies();
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const refreshNetworks = async () => {
|
|
88
|
-
await mutateNetworks();
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const value: RootPaymentsContextValue = {
|
|
92
|
-
currencies,
|
|
93
|
-
isLoadingCurrencies,
|
|
94
|
-
currenciesError,
|
|
95
|
-
refreshCurrencies,
|
|
96
|
-
providerCurrencies,
|
|
97
|
-
isLoadingProviderCurrencies,
|
|
98
|
-
providerCurrenciesError,
|
|
99
|
-
refreshProviderCurrencies,
|
|
100
|
-
networks,
|
|
101
|
-
isLoadingNetworks,
|
|
102
|
-
networksError,
|
|
103
|
-
refreshNetworks,
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
return (
|
|
107
|
-
<RootPaymentsContext.Provider value={value}>{children}</RootPaymentsContext.Provider>
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
112
|
-
// Hook
|
|
113
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
114
|
-
|
|
115
|
-
export function useRootPaymentsContext(): RootPaymentsContextValue {
|
|
116
|
-
const context = useContext(RootPaymentsContext);
|
|
117
|
-
if (!context) {
|
|
118
|
-
throw new Error('useRootPaymentsContext must be used within RootPaymentsProvider');
|
|
119
|
-
}
|
|
120
|
-
return context;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
124
|
-
// Re-export types
|
|
125
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
126
|
-
|
|
127
|
-
export type {
|
|
128
|
-
Currency,
|
|
129
|
-
PaginatedCurrencyListList,
|
|
130
|
-
ProviderCurrency,
|
|
131
|
-
PaginatedProviderCurrencyList,
|
|
132
|
-
Network,
|
|
133
|
-
PaginatedNetworkList,
|
|
134
|
-
};
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* API Key Metrics Component
|
|
3
|
-
* Display API key usage statistics
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
'use client';
|
|
7
|
-
|
|
8
|
-
import React from 'react';
|
|
9
|
-
import {
|
|
10
|
-
Card,
|
|
11
|
-
CardContent,
|
|
12
|
-
CardHeader,
|
|
13
|
-
CardTitle,
|
|
14
|
-
Skeleton,
|
|
15
|
-
} from '@djangocfg/ui';
|
|
16
|
-
import { Key, CheckCircle2, XCircle, Activity } from 'lucide-react';
|
|
17
|
-
import { useApiKeysContext } from '@djangocfg/api/cfg/contexts';
|
|
18
|
-
|
|
19
|
-
export const ApiKeyMetrics: React.FC = () => {
|
|
20
|
-
const { apiKeys, isLoadingApiKeys } = useApiKeysContext();
|
|
21
|
-
|
|
22
|
-
const keysList = apiKeys?.results || [];
|
|
23
|
-
|
|
24
|
-
const metrics = React.useMemo(() => {
|
|
25
|
-
const total = keysList.length;
|
|
26
|
-
const active = keysList.filter(key => key.is_active).length;
|
|
27
|
-
const inactive = total - active;
|
|
28
|
-
const recentlyUsed = keysList.filter(key => {
|
|
29
|
-
if (!key.last_used_at) return false;
|
|
30
|
-
const lastUsed = new Date(key.last_used_at);
|
|
31
|
-
const dayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
|
32
|
-
return lastUsed > dayAgo;
|
|
33
|
-
}).length;
|
|
34
|
-
|
|
35
|
-
return { total, active, inactive, recentlyUsed };
|
|
36
|
-
}, [keysList]);
|
|
37
|
-
|
|
38
|
-
if (isLoadingApiKeys) {
|
|
39
|
-
return (
|
|
40
|
-
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
|
41
|
-
{Array.from({ length: 4 }).map((_, i) => (
|
|
42
|
-
<Card key={i}>
|
|
43
|
-
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
44
|
-
<Skeleton className="h-4 w-24" />
|
|
45
|
-
<Skeleton className="h-4 w-4 rounded-full" />
|
|
46
|
-
</CardHeader>
|
|
47
|
-
<CardContent>
|
|
48
|
-
<Skeleton className="h-7 w-12 mb-1" />
|
|
49
|
-
<Skeleton className="h-3 w-32" />
|
|
50
|
-
</CardContent>
|
|
51
|
-
</Card>
|
|
52
|
-
))}
|
|
53
|
-
</div>
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const metricsData = [
|
|
58
|
-
{
|
|
59
|
-
title: 'Total Keys',
|
|
60
|
-
value: metrics.total.toString(),
|
|
61
|
-
description: 'All API keys',
|
|
62
|
-
icon: Key,
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
title: 'Active Keys',
|
|
66
|
-
value: metrics.active.toString(),
|
|
67
|
-
description: 'Currently active',
|
|
68
|
-
icon: CheckCircle2,
|
|
69
|
-
iconColor: 'text-green-600',
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
title: 'Inactive Keys',
|
|
73
|
-
value: metrics.inactive.toString(),
|
|
74
|
-
description: 'Not in use',
|
|
75
|
-
icon: XCircle,
|
|
76
|
-
iconColor: 'text-muted-foreground',
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
title: 'Recently Used',
|
|
80
|
-
value: metrics.recentlyUsed.toString(),
|
|
81
|
-
description: 'Last 24 hours',
|
|
82
|
-
icon: Activity,
|
|
83
|
-
iconColor: 'text-blue-600',
|
|
84
|
-
},
|
|
85
|
-
];
|
|
86
|
-
|
|
87
|
-
return (
|
|
88
|
-
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
|
89
|
-
{metricsData.map((metric, index) => {
|
|
90
|
-
const Icon = metric.icon;
|
|
91
|
-
return (
|
|
92
|
-
<Card key={index}>
|
|
93
|
-
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
94
|
-
<CardTitle className="text-sm font-medium">{metric.title}</CardTitle>
|
|
95
|
-
<Icon className={`h-4 w-4 ${metric.iconColor || 'text-muted-foreground'}`} />
|
|
96
|
-
</CardHeader>
|
|
97
|
-
<CardContent>
|
|
98
|
-
<div className="text-2xl font-bold">{metric.value}</div>
|
|
99
|
-
<p className="text-xs text-muted-foreground mt-1">
|
|
100
|
-
{metric.description}
|
|
101
|
-
</p>
|
|
102
|
-
</CardContent>
|
|
103
|
-
</Card>
|
|
104
|
-
);
|
|
105
|
-
})}
|
|
106
|
-
</div>
|
|
107
|
-
);
|
|
108
|
-
};
|
|
109
|
-
|