Skip to content

Commit

Permalink
SPV-1010/WebhookView (#1033)
Browse files Browse the repository at this point in the history
Co-authored-by: chris-4chain <[email protected]>
  • Loading branch information
Nazarii-4chain and chris-4chain authored Oct 1, 2024
1 parent 9ab87a6 commit 3e68057
Show file tree
Hide file tree
Showing 23 changed files with 356 additions and 61 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"dependencies": {
"@4chain-ag/react-configuration": "^1.0.4",
"@bsv/sdk": "^1.0.37",
"@bsv/spv-wallet-js-client": "^1.0.0-beta.20",
"@bsv/spv-wallet-js-client": "^1.0.0-beta.21",
"@estruyf/github-actions-reporter": "^1.9.2",
"@heroicons/react": "^2.1.3",
"@hookform/resolvers": "^3.9.0",
Expand Down
8 changes: 7 additions & 1 deletion src/components/AccessKeysTabContent/AccessKeysTabContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
DataTable,
NoRecordsText,
RevokeKeyDialog,
ViewDialog,
} from '@/components';
import { AccessKeyExtended } from '@/interfaces';

Expand All @@ -26,7 +27,12 @@ export const AccessKeysTabContent = ({ accessKeys, hasRevokeKeyDialog }: AccessK
<DataTable
columns={accessKeysColumns}
data={accessKeys}
renderItem={(row) => hasRevokeKeyDialog && <RevokeKeyDialog row={row} />}
renderItem={(row) => (
<>
<ViewDialog row={row} />
{hasRevokeKeyDialog && <RevokeKeyDialog row={row} />}
</>
)}
/>
) : (
<NoRecordsText message="No Access Keys to show." />
Expand Down
20 changes: 12 additions & 8 deletions src/components/ContactsTabContent/ContactsTabContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ContactStatus,
DataTable,
NoRecordsText,
ViewDialog,
} from '@/components';
import { ContactExtended } from '@/interfaces/contacts.ts';

Expand All @@ -29,16 +30,19 @@ export const ContactsTabContent = ({ contacts }: ContactsTabContentProps) => {
<DataTable
columns={contactsColumns}
data={contacts}
renderInlineItem={(row) =>
row.getValue('status') === ContactStatus.Awaiting ? (
<div className="grid grid-cols-2 items-center w-fit gap-4 ">
<ContactAcceptDialog row={row} />
<ContactRejectDialog row={row} />
</div>
) : null
}
renderInlineItem={(row) => (
<>
{row.getValue('status') === ContactStatus.Awaiting ? (
<div className="grid grid-cols-2 items-center w-fit gap-4 ">
<ContactAcceptDialog row={row} />
<ContactRejectDialog row={row} />
</div>
) : null}
</>
)}
renderItem={(row) => (
<>
<ViewDialog row={row} />
<ContactEditDialog row={row} />
<ContactDeleteDialog row={row} />
</>
Expand Down
31 changes: 16 additions & 15 deletions src/components/DataTable/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
TableHead,
TableHeader,
TableRow,
ViewDialog,
} from '@/components';
import { AccessKey, Contact, Destination, PaymailAddress, Tx, XPub } from '@bsv/spv-wallet-js-client';
import {
Expand Down Expand Up @@ -79,20 +78,22 @@ export function DataTable<TData, TValue>({
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
))}
<TableCell>{renderInlineItem ? renderInlineItem(row) : null}</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<EllipsisVertical className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<ViewDialog row={row as Row<RowType>} />
{renderItem ? renderItem(row) : null}
</DropdownMenuContent>
</DropdownMenu>
</TableCell>

{renderItem && (
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<EllipsisVertical className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
{renderItem ? renderItem(row) : null}
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
)}
</TableRow>
))
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
DestinationEditDialog,
destinationsColumns,
NoRecordsText,
ViewDialog,
} from '@/components';
import { DestinationExtended } from '@/interfaces/destination.ts';

Expand All @@ -26,7 +27,12 @@ export const DestinationsTabContent = ({ destinations, hasDestinationEditDialog
<DataTable
columns={destinationsColumns}
data={destinations}
renderItem={(row) => hasDestinationEditDialog && <DestinationEditDialog row={row} />}
renderItem={(row) => (
<>
<ViewDialog row={row} />
{hasDestinationEditDialog && <DestinationEditDialog row={row} />}
</>
)}
/>
) : (
<NoRecordsText message="No Destinations to show." />
Expand Down
10 changes: 7 additions & 3 deletions src/components/PaymailsTabContent/PaymailsTabContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
NoRecordsText,
paymailColumns,
PaymailDeleteDialog,
ViewDialog,
} from '@/components';
import { PaymailExtended } from '@/interfaces/paymail.ts';

Expand All @@ -26,9 +27,12 @@ export const PaymailsTabContent = ({ paymails, hasPaymailDeleteDialog }: Paymail
<DataTable
columns={paymailColumns}
data={paymails}
renderItem={(row) =>
hasPaymailDeleteDialog && row.original.status !== 'deleted' && <PaymailDeleteDialog row={row} />
}
renderItem={(row) => (
<>
<ViewDialog row={row} />
{hasPaymailDeleteDialog && row.original.status !== 'deleted' && <PaymailDeleteDialog row={row} />}
</>
)}
/>
) : (
<NoRecordsText message="No Paymails to show." />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Card, CardContent, CardHeader, CardTitle, DataTable, TransactionEditDialog } from '@/components';
import { Card, CardContent, CardHeader, CardTitle, DataTable, TransactionEditDialog, ViewDialog } from '@/components';
import { columns } from '@/components/TransactionsColumns/columns.tsx';
import { Tx } from '@bsv/spv-wallet-js-client';
import React from 'react';
Expand Down Expand Up @@ -26,7 +26,12 @@ export const TransactionsTabContent = ({
<DataTable
columns={columns}
data={transactions}
renderItem={(row) => hasTransactionEditDialog && <TransactionEditDialog row={row} />}
renderItem={(row) => (
<>
<ViewDialog row={row} />
{hasTransactionEditDialog && <TransactionEditDialog row={row} />}
</>
)}
/>
) : (
<div className="flex flex-col items-center gap-1 text-center">
Expand Down
73 changes: 73 additions & 0 deletions src/components/UnsubscribeWebhook/UnsubscribeWebhook.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {
Button,
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components';
import { useSpvWalletClient } from '@/contexts';
import { errorWrapper } from '@/utils';
import { Webhook } from '@bsv/spv-wallet-js-client';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { Row } from '@tanstack/react-table';
import { CircleMinus } from 'lucide-react';
import { useState } from 'react';
import { toast } from 'sonner';

interface UnsubscribeWebhookProps {
row: Row<Webhook>;
}

export const UnsubscribeWebhook = ({ row }: UnsubscribeWebhookProps) => {
const { spvWalletClient } = useSpvWalletClient();
const queryClient = useQueryClient();
const [isOpen, setIsOpen] = useState(false);

const handleIsOpenToggle = () => {
setIsOpen((prev) => !prev);
};

const mutation = useMutation({
mutationFn: async (url: string) => {
// At this point, spvWalletClient is defined; using non-null assertion.
return await spvWalletClient!.AdminDeleteWebhook(url);
},
onSuccess: () => queryClient.invalidateQueries(),
});
const onRemove = () => {
mutation.mutate(row.original.url, {
onSuccess: () => {
toast.success('Webhook unsubscribed');
},
onError: (error) => {
toast.error('Failed to unsubscribe webhook');
errorWrapper(error);
},
});
};
return (
<>
<Dialog open={isOpen} onOpenChange={handleIsOpenToggle}>
<DialogTrigger asChild className="w-full">
<Button variant="destructive" className="h-8 max-w-min">
<CircleMinus className="w-4 h-4 mr-2" /> Unsubscribe
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Unsubscribe webhook</DialogTitle>
</DialogHeader>
<DialogDescription>Are you sure you want to unsubscribe a webhook ?</DialogDescription>
<div className="grid grid-cols-2 gap-4">
<Button onClick={onRemove}>Unsubscribe</Button>
<Button variant="ghost" onClick={handleIsOpenToggle}>
Cancel
</Button>
</div>
</DialogContent>
</Dialog>
</>
);
};
1 change: 1 addition & 0 deletions src/components/UnsubscribeWebhook/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './UnsubscribeWebhook.tsx';
4 changes: 2 additions & 2 deletions src/components/ViewDialog/ViewDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ export const ViewDialog = ({ row }: ViewDialogProps) => {
</div>
);
}

return (
<div key={field} className="break-all">
<span className="text-gray-400">{field}:</span> {value as React.ReactNode}
<span className="text-gray-400">{field}:</span>{' '}
{typeof value === 'boolean' ? value.toString() : (value as React.ReactNode)}
</div>
);
});
Expand Down
51 changes: 51 additions & 0 deletions src/components/WebhooksColumns/WebhooksColumns.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Badge, Button } from '@/components/ui';
import { getSortDirection } from '@/utils';
import { Webhook } from '@bsv/spv-wallet-js-client';
import { Link } from '@tanstack/react-router';
import { ColumnDef } from '@tanstack/react-table';

import { ArrowUpDown } from 'lucide-react';

export interface WebhooksColumns extends Webhook {
status: string;
}

export const webhookColumns: ColumnDef<WebhooksColumns>[] = [
{
accessorKey: 'url',
header: ({ column }) => {
return (
<Link
search={(prev) => ({
...prev,
order_by_field: 'url',
sort_direction: getSortDirection(column),
})}
>
<Button variant="ghost" onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}>
URL
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
</Link>
);
},
},
{
accessorKey: 'status',
header: ({ column }) => {
return (
<Button variant="ghost" onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}>
Status
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
cell: ({ row }) => {
return row.getValue('status') === 'banned' ? (
<Badge variant="secondary">Banned</Badge>
) : (
<Badge variant="outline">Active</Badge>
);
},
},
];
34 changes: 34 additions & 0 deletions src/components/WebhooksTabContent/WebhooksTabContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Card, CardContent, CardHeader, CardTitle, DataTable, webhookColumns } from '@/components';
import { UnsubscribeWebhook } from '@/components/UnsubscribeWebhook/UnsubscribeWebhook.tsx';
import { WebhookExtended } from '@/interfaces/webhook.ts';

export interface WebhooksTabContentProps {
webhooks: WebhookExtended[];
}

export const WebhooksTabContent = ({ webhooks }: WebhooksTabContentProps) => {
return (
<Card>
<CardHeader>
<CardTitle>Webhooks</CardTitle>
</CardHeader>
<CardContent className="mb-2">
{webhooks.length > 0 ? (
<DataTable
columns={webhookColumns}
data={webhooks}
renderInlineItem={(row) => (
<>
<UnsubscribeWebhook row={row} />
</>
)}
/>
) : (
<div className="flex flex-col items-center gap-1 text-center">
<h3 className="text-2xl font-bold tracking-tight">You have no webhooks</h3>
</div>
)}
</CardContent>
</Card>
);
};
1 change: 1 addition & 0 deletions src/components/WebhooksTabContent/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './WebhooksTabContent.tsx';
13 changes: 11 additions & 2 deletions src/components/XpubsTabContent/XpubsTabContent.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { AddXpubDialog, Card, CardContent, CardHeader, CardTitle, DataTable, xPubsColumns } from '@/components';
import {
AddXpubDialog,
Card,
CardContent,
CardHeader,
CardTitle,
DataTable,
ViewDialog,
xPubsColumns,
} from '@/components';
import { XpubExtended } from '@/interfaces';

export interface XpubsTabContentProps {
Expand All @@ -13,7 +22,7 @@ export const XpubsTabContent = ({ xpubs }: XpubsTabContentProps) => {
</CardHeader>
<CardContent className="mb-2">
{xpubs.length > 0 ? (
<DataTable columns={xPubsColumns} data={xpubs} />
<DataTable columns={xPubsColumns} data={xpubs} renderItem={(row) => <ViewDialog row={row} />} />
) : (
<div className="flex flex-col items-center gap-1 text-center">
<h3 className="text-2xl font-bold tracking-tight">You have no xPubs</h3>
Expand Down
3 changes: 3 additions & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ export * from './XpubsSkeleton';
export * from './XpubsTabContent';
export * from './CustomErrorComponent';
export * from './LoadingSpinner';
export * from './WebhooksColumns/WebhooksColumns.tsx';
export * from './WebhooksTabContent';
export * from './UnsubscribeWebhook';
5 changes: 5 additions & 0 deletions src/interfaces/webhook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Webhook } from '@bsv/spv-wallet-js-client';

export interface WebhookExtended extends Webhook {
status: string;
}
Loading

0 comments on commit 3e68057

Please sign in to comment.