Skip to content

Commit 3ef621a

Browse files
Chain Management (#272)
* Add chain delete * Add export chain * Add chain rename * Add handle create * update chain select --------- Co-authored-by: Jameson Grieve <37882431+JamesonRGrieve@users.noreply.github.com>
1 parent 1d52e73 commit 3ef621a

File tree

2 files changed

+194
-66
lines changed

2 files changed

+194
-66
lines changed

src/app/settings/chains/page.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,13 @@ import ChainSteps from '@/interface/Settings/chain/ChainSteps';
1010
import { SidebarContent } from '@/components/appwrapper/src/SidebarContentManager';
1111

1212
export default function ChainPage() {
13-
const [showCreateDialog, setShowCreateDialog] = useState(false);
1413
const searchParams = useSearchParams();
1514
const { data: chainData, error } = useChain(searchParams.get('chain') ?? undefined);
1615

1716
return (
1817
<SidebarPage title='Chains'>
1918
<SidebarContent title='Chains'>
20-
<ChainPanel showCreateDialog={showCreateDialog} setShowCreateDialog={setShowCreateDialog} />
21-
<ChainDialog open={showCreateDialog} setOpen={setShowCreateDialog} />
19+
<ChainPanel />
2220
</SidebarContent>
2321
{chainData && (
2422
<div className='mt-4'>

src/interface/Settings/chain/ChainPanel.tsx

Lines changed: 193 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,125 @@ import { useChain } from '../../hooks/useChain';
88
import { useInteractiveConfig } from '@/interactive/InteractiveConfigContext';
99
import { SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar';
1010
import { Input } from '@/components/ui/input';
11+
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
12+
import { Button } from '@/components/ui/button';
13+
import { LuDownload } from 'react-icons/lu';
14+
import { Label } from '@/components/ui/label';
1115

12-
export default function ChainPanel({
13-
showCreateDialog,
14-
setShowCreateDialog,
15-
}: {
16-
showCreateDialog: boolean;
17-
setShowCreateDialog: (show: boolean) => void;
18-
}) {
16+
export default function ChainPanel() {
17+
return (
18+
<div>
19+
<SidebarGroup>
20+
<SidebarGroupLabel>Select Chain</SidebarGroupLabel>
21+
<SidebarMenuButton className='group-data-[state=expanded]:hidden'>
22+
<ArrowBigLeft />
23+
</SidebarMenuButton>
24+
<div className='w-full group-data-[collapsible=icon]:hidden'>
25+
<ChainSelector />
26+
</div>
27+
</SidebarGroup>
28+
<SidebarGroup>
29+
<SidebarGroupLabel>Chain Functions</SidebarGroupLabel>
30+
<SidebarMenu>
31+
<ChainCreate />
32+
<ChainRename />
33+
<ChainExport />
34+
<ChainDelete />
35+
</SidebarMenu>
36+
</SidebarGroup>
37+
</div>
38+
);
39+
}
40+
41+
export function ChainCreate() {
42+
const [showCreateDialog, setShowCreateDialog] = useState(false);
43+
const context = useInteractiveConfig();
44+
const router = useRouter();
45+
const [newChainName, setNewChainName] = useState('');
46+
47+
const handleNewChain = async () => {
48+
await context.sdk.addChain(newChainName);
49+
router.push(`/settings/chains?chain=${newChainName}`);
50+
setShowCreateDialog(false);
51+
};
52+
53+
const handleChainImport = async (event: React.ChangeEvent<HTMLInputElement>) => {
54+
const files = Array.from(event.target.files ?? []);
55+
for (const file of files) {
56+
const fileContent = await file.text();
57+
if (newChainName === '') {
58+
const filename = file.name.replace('.json', '');
59+
setNewChainName(filename);
60+
}
61+
const steps = JSON.parse(fileContent);
62+
await context.sdk.addChain(newChainName);
63+
await context.sdk.importChain(newChainName, steps);
64+
router.push(`/chains?chain=${newChainName}`);
65+
}
66+
setShowCreateDialog(false);
67+
};
68+
69+
return (
70+
<>
71+
<SidebarMenuItem>
72+
<SidebarMenuButton side='left' tooltip='Create Chain' onClick={() => setShowCreateDialog(true)}>
73+
<Plus />
74+
<span>Create Chain</span>
75+
</SidebarMenuButton>
76+
</SidebarMenuItem>
77+
<Dialog open={showCreateDialog} onOpenChange={setShowCreateDialog}>
78+
<DialogContent>
79+
<DialogHeader>
80+
<DialogTitle>Create New Chain</DialogTitle>
81+
</DialogHeader>
82+
<div className='grid gap-4 py-4'>
83+
<div className='grid grid-cols-4 items-center gap-4'>
84+
<Label htmlFor='chain-name' className='text-right'>
85+
Chain Name
86+
</Label>
87+
<Input
88+
id='chain-name'
89+
value={newChainName}
90+
onChange={(e) => setNewChainName(e.target.value)}
91+
className='col-span-3'
92+
/>
93+
</div>
94+
<div className='grid grid-cols-4 items-center gap-4'>
95+
<Label htmlFor='import-chain' className='text-right'>
96+
Import Chain
97+
</Label>
98+
<Input id='import-chain' type='file' onChange={handleChainImport} className='col-span-3' />
99+
</div>
100+
</div>
101+
<DialogFooter>
102+
<Button variant='outline' onClick={() => setShowCreateDialog(false)}>
103+
Cancel
104+
</Button>
105+
<Button onClick={handleNewChain}>Create Chain</Button>
106+
</DialogFooter>
107+
</DialogContent>
108+
</Dialog>
109+
</>
110+
);
111+
}
112+
113+
export function ChainRename() {
114+
const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false);
19115
const [renaming, setRenaming] = useState(false);
20116
const [newName, setNewName] = useState('');
21117
const context = useInteractiveConfig();
22118
const router = useRouter();
23119
const pathname = usePathname();
24120
const searchParams = useSearchParams();
25121

26-
const { data: chainData, error } = useChain(searchParams.get('chain') ?? undefined);
122+
const { data: chainData } = useChain(searchParams.get('chain') ?? undefined);
27123

28124
useEffect(() => {
29125
if (renaming) {
30126
setNewName(searchParams.get('chain') ?? '');
31127
}
32128
}, [renaming]);
33129

34-
const handleDelete = async () => {
35-
await context.sdk.deleteChain(searchParams.get('chain') ?? '');
36-
router.push(pathname);
37-
};
38-
39130
const handleRename = async () => {
40131
if ((newName && newName !== searchParams.get('chain')) ?? '') {
41132
await context.sdk.renameChain(searchParams.get('chain') ?? '', newName);
@@ -46,6 +137,45 @@ export default function ChainPanel({
46137
}
47138
};
48139

140+
return (
141+
<>
142+
<SidebarMenuItem>
143+
<SidebarMenuButton
144+
side='left'
145+
tooltip='Rename Chain'
146+
onClick={() => setIsRenameDialogOpen(true)}
147+
disabled={!chainData}
148+
>
149+
<Pencil className='size-4' />
150+
<span>Rename Chain</span>
151+
</SidebarMenuButton>
152+
</SidebarMenuItem>
153+
<Dialog open={isRenameDialogOpen} onOpenChange={setIsRenameDialogOpen}>
154+
<DialogContent>
155+
<DialogHeader>
156+
<DialogTitle>Rename Chain</DialogTitle>
157+
</DialogHeader>
158+
<div className='grid gap-4 py-4'>
159+
<Input value={newName} onChange={(e) => setNewName(e.target.value)} placeholder='Enter new name' />
160+
</div>
161+
<DialogFooter>
162+
<Button variant='outline' onClick={() => setIsRenameDialogOpen(false)}>
163+
Cancel
164+
</Button>
165+
<Button onClick={handleRename}>Rename</Button>
166+
</DialogFooter>
167+
</DialogContent>
168+
</Dialog>
169+
</>
170+
);
171+
}
172+
173+
export function ChainExport() {
174+
const context = useInteractiveConfig();
175+
const searchParams = useSearchParams();
176+
177+
const { data: chainData } = useChain(searchParams.get('chain') ?? undefined);
178+
49179
const handleExportChain = async () => {
50180
const chainData = await context.sdk.getChain(searchParams.get('chain') ?? '');
51181
const element = document.createElement('a');
@@ -58,56 +188,56 @@ export default function ChainPanel({
58188
};
59189

60190
return (
61-
<div className='space-y-4'>
62-
<SidebarGroup>
63-
<SidebarGroupLabel>Select Chain</SidebarGroupLabel>
64-
<SidebarMenuButton className='group-data-[state=expanded]:hidden'>
65-
<ArrowBigLeft />
191+
<SidebarMenuItem>
192+
<SidebarMenuButton side='left' tooltip='Export Chain' onClick={handleExportChain} disabled={!chainData}>
193+
<LuDownload className='size-4' />
194+
<span>Export Chain</span>
195+
</SidebarMenuButton>
196+
</SidebarMenuItem>
197+
);
198+
}
199+
200+
export function ChainDelete() {
201+
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
202+
const context = useInteractiveConfig();
203+
const router = useRouter();
204+
const pathname = usePathname();
205+
const searchParams = useSearchParams();
206+
const { data: chainData } = useChain(searchParams.get('chain') ?? undefined);
207+
208+
const handleDelete = async () => {
209+
await context.sdk.deleteChain(searchParams.get('chain') ?? '');
210+
router.push(pathname);
211+
setIsDeleteDialogOpen(false);
212+
};
213+
214+
return (
215+
<>
216+
<SidebarMenuItem>
217+
<SidebarMenuButton
218+
side='left'
219+
tooltip='Delete Chain'
220+
onClick={() => setIsDeleteDialogOpen(true)}
221+
disabled={!chainData}
222+
>
223+
<Trash2 />
224+
<span>Delete Chain</span>
66225
</SidebarMenuButton>
67-
<div className='w-full group-data-[collapsible=icon]:hidden'>
68-
{renaming ? (
69-
<Input value={newName} onChange={(e) => setNewName(e.target.value)} className='w-full' />
70-
) : (
71-
<ChainSelector />
72-
)}
73-
</div>
74-
<SidebarGroupLabel>Chain Functions</SidebarGroupLabel>
75-
<SidebarMenu>
76-
{[
77-
{
78-
title: 'Create Chain',
79-
icon: Plus,
80-
func: () => setShowCreateDialog(true),
81-
disabled: renaming || showCreateDialog,
82-
},
83-
{
84-
title: renaming ? 'Save Name' : 'Rename Chain',
85-
icon: renaming ? Check : Pencil,
86-
func: renaming ? handleRename : () => setRenaming(true),
87-
disabled: !chainData || (renaming && (!newName || newName === searchParams.get('chain'))),
88-
},
89-
{
90-
title: 'Export Chain',
91-
icon: Download,
92-
func: handleExportChain,
93-
disabled: !chainData || renaming,
94-
},
95-
{
96-
title: 'Delete Chain',
97-
icon: Trash2,
98-
func: handleDelete,
99-
disabled: !chainData || renaming,
100-
},
101-
].map((item) => (
102-
<SidebarMenuItem key={item.title}>
103-
<SidebarMenuButton side='left' tooltip={item.title} onClick={item.func} disabled={item.disabled}>
104-
{item.icon && <item.icon />}
105-
<span>{item.title}</span>
106-
</SidebarMenuButton>
107-
</SidebarMenuItem>
108-
))}
109-
</SidebarMenu>
110-
</SidebarGroup>
111-
</div>
226+
</SidebarMenuItem>
227+
<Dialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
228+
<DialogContent>
229+
<DialogHeader>
230+
<DialogTitle>Delete Chain</DialogTitle>
231+
</DialogHeader>
232+
<DialogDescription>Are you sure you want to delete this chain? This action cannot be undone.</DialogDescription>
233+
<DialogFooter>
234+
<Button variant='outline' onClick={() => setIsDeleteDialogOpen(false)}>
235+
Cancel
236+
</Button>
237+
<Button onClick={handleDelete}>Delete</Button>
238+
</DialogFooter>
239+
</DialogContent>
240+
</Dialog>
241+
</>
112242
);
113243
}

0 commit comments

Comments
 (0)