Skip to content

Commit 866446d

Browse files
committed
feat: implement account management layout with tabs and stepper for deposit/withdraw actions
1 parent 87c539e commit 866446d

File tree

8 files changed

+395
-4
lines changed

8 files changed

+395
-4
lines changed

src/components/Stepper.tsx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { cn } from '@/lib/utils';
2+
import { Check } from 'lucide-react';
3+
import React from 'react';
4+
5+
type StepperProps = {
6+
classname: string;
7+
currentStep: number;
8+
steps: string[];
9+
};
10+
11+
export const Stepper: React.FC<StepperProps> = ({
12+
classname,
13+
currentStep,
14+
steps,
15+
}) => {
16+
const stepsNb = steps.length;
17+
18+
return (
19+
<div
20+
className={cn(classname, 'grid gap-y-4')}
21+
style={{
22+
gridTemplateColumns: `repeat(${stepsNb}, minmax(0, 1fr))`,
23+
maxWidth: stepsNb * 350,
24+
}}
25+
aria-label="Progress indicator"
26+
>
27+
{steps.map((step, index) => {
28+
const isActive = currentStep >= index;
29+
const isCompleted = currentStep > index;
30+
31+
return (
32+
<div key={step} className="flex w-full flex-col items-center">
33+
<div className="relative w-full">
34+
{index < stepsNb - 1 && (
35+
<span
36+
className={cn(
37+
'absolute top-1/2 right-0 h-px w-1/2 translate-x-1/2 -translate-y-1/2 rounded-full',
38+
isCompleted ? 'bg-white' : 'bg-grey-400'
39+
)}
40+
/>
41+
)}
42+
<div
43+
className={cn(
44+
'mx-auto flex size-8 items-center justify-center rounded-full',
45+
isActive ? 'bg-white text-black' : 'bg-grey-700',
46+
isCompleted && 'bg-primary text-black'
47+
)}
48+
aria-label={`Step ${index + 1}`}
49+
>
50+
{isCompleted ? (
51+
<Check size="16" strokeWidth="2.5" />
52+
) : (
53+
index + 1
54+
)}
55+
</div>
56+
</div>
57+
<span
58+
className={cn(
59+
'mt-2 text-center',
60+
isActive ? 'text-white' : 'text-grey-500'
61+
)}
62+
>
63+
{step}
64+
</span>
65+
</div>
66+
);
67+
})}
68+
</div>
69+
);
70+
};

src/components/ui/button.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ const buttonVariants = cva(
2424
'from-primary text-primary hover:before:bg-muted relative z-0 overflow-hidden bg-gradient-to-b to-primary before:absolute before:inset-px before:-z-10 before:rounded-[29px] before:bg-[#322B1E] before:duration-300',
2525
},
2626
size: {
27-
default: 'h-9 px-4 py-2 has-[>svg]:px-3 rounded-full',
27+
xs: 'h-6 rounded-full px-2 text-xs has-[>svg]:px-1.5 gap-1',
2828
sm: 'h-8 rounded-full gap-1.5 px-3 has-[>svg]:px-2.5',
29+
default: 'h-9 px-4 py-2 has-[>svg]:px-3 rounded-full',
2930
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
3031
icon: 'size-9 rounded-md',
3132
},

src/index.css

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,12 +197,15 @@
197197
--muted: var(--color-grey-700);
198198
--muted-foreground: var(--color-grey-300);
199199

200+
/* --intermediary: var(--color-grey-400);
201+
--intermediary-foreground: var(--color-grey-600); */
202+
200203
--accent: var(--color-grey-700);
201204
--accent-foreground: var(--color-white);
202205

203206
--destructive: var(--color-red-500);
204207
--border: var(--color-white);
205-
--input: var(--color-grey-700);
208+
--input: var(--color-grey-400);
206209
--ring: var(--color-primary);
207210

208211
--chart-1: oklch(0.488 0.243 264.376);

src/modules/SearcherBar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export function SearcherBar({ className }: { className?: string }) {
1313
<div className="relative w-full">
1414
<Input
1515
className={cn(
16-
'bg-input border-secondary w-full rounded-2xl py-5.5 pl-12 sm:py-6.5',
16+
'bg-muted border-secondary w-full rounded-2xl py-5.5 pl-12 sm:py-6.5',
1717
isConnected && 'sm:pr-32'
1818
)}
1919
placeholder="Search address or id or transaction"
@@ -25,7 +25,7 @@ export function SearcherBar({ className }: { className?: string }) {
2525
{isConnected && (
2626
<Button
2727
variant="outline"
28-
className="bg-input hover:bg-secondary absolute top-1/2 right-4 hidden -translate-y-1/2 sm:flex"
28+
className="bg-muted hover:bg-secondary absolute top-1/2 right-4 hidden -translate-y-1/2 sm:flex"
2929
asChild
3030
>
3131
<ChainLink to={`/address/${userAddress}`}>My activity</ChainLink>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { ChainLink } from '@/components/ChainLink';
2+
import {
3+
Breadcrumb,
4+
BreadcrumbItem,
5+
BreadcrumbLink,
6+
BreadcrumbList,
7+
BreadcrumbPage,
8+
BreadcrumbSeparator,
9+
} from '@/components/ui/breadcrumb';
10+
11+
export function AccountBreadcrumbs() {
12+
return (
13+
<Breadcrumb>
14+
<BreadcrumbList>
15+
<BreadcrumbItem>
16+
<BreadcrumbLink asChild>
17+
<ChainLink to="/">Homepage</ChainLink>
18+
</BreadcrumbLink>
19+
</BreadcrumbItem>
20+
<BreadcrumbSeparator />
21+
<BreadcrumbItem>
22+
<BreadcrumbPage>Account</BreadcrumbPage>
23+
</BreadcrumbItem>
24+
</BreadcrumbList>
25+
</Breadcrumb>
26+
);
27+
}

src/modules/account/getTabs.tsx

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { SUPPORTED_CHAINS } from '@/config';
2+
import { DefaultError } from '@tanstack/query-core';
3+
import { UseMutationResult } from '@tanstack/react-query';
4+
import { LoaderCircle, CheckCircle, ExternalLink } from 'lucide-react';
5+
import { Button } from '@/components/ui/button';
6+
import { Input } from '@/components/ui/input';
7+
8+
interface TabsProps {
9+
deposit: UseMutationResult<void, DefaultError, void>;
10+
withdraw: UseMutationResult<void, DefaultError, void>;
11+
chainId: number;
12+
}
13+
14+
export function getTabs({ deposit, withdraw, chainId }: TabsProps) {
15+
return [
16+
{
17+
title: 'DEPOSIT xRLC',
18+
longTitle: 'Deposit xRLC to your Account',
19+
desc: 'Top up your iExec account with your wallet to use iExec platform',
20+
steps: [
21+
{
22+
title: 'Choose the amount to deposit',
23+
content: (
24+
<form
25+
className="flex justify-center gap-6"
26+
onSubmit={(e) => {
27+
e.preventDefault();
28+
deposit.mutate();
29+
}}
30+
>
31+
<div className="relative">
32+
<Input className="w-full max-w-80" max={10} />
33+
<Button
34+
type="button"
35+
size="xs"
36+
variant="outline"
37+
className="absolute inset-y-1.5 right-1"
38+
>
39+
Max
40+
</Button>
41+
</div>
42+
<Button type="submit">Deposit</Button>
43+
</form>
44+
),
45+
},
46+
{
47+
title: 'Sign deposit tx',
48+
content: (
49+
<span className="mx-auto flex w-full justify-center gap-2">
50+
<LoaderCircle className="text-primary animate-spin" />
51+
Awaiting transaction signature
52+
</span>
53+
),
54+
},
55+
{
56+
title: 'xRLC deposit',
57+
content: (
58+
<span className="mx-auto flex w-full justify-center gap-2">
59+
Deposit successful
60+
<CheckCircle className="text-primary" />
61+
</span>
62+
),
63+
},
64+
],
65+
},
66+
{
67+
title: 'WITHDRAW xRLC',
68+
longTitle: 'Withdraw xRLC to your Wallet',
69+
desc: 'Withdraw your profits from your iExec Account',
70+
steps: [
71+
{
72+
title: 'Choose the amount to withdraw',
73+
content: (
74+
<form
75+
className="flex justify-center gap-6"
76+
onSubmit={(e) => {
77+
e.preventDefault();
78+
withdraw.mutate();
79+
}}
80+
>
81+
<div className="relative">
82+
<Input className="w-full max-w-80" max={10} />
83+
<Button
84+
type="button"
85+
size="xs"
86+
variant="outline"
87+
className="absolute inset-y-1.5 right-1"
88+
>
89+
Max
90+
</Button>
91+
</div>
92+
<Button type="submit">Withdraw</Button>
93+
</form>
94+
),
95+
},
96+
{
97+
title: 'Sign withdraw tx',
98+
content: (
99+
<span className="mx-auto flex w-full justify-center gap-2">
100+
<LoaderCircle className="text-primary animate-spin" />
101+
Awaiting transaction signature
102+
</span>
103+
),
104+
},
105+
{
106+
title: 'xRLC withdrawn',
107+
content: (
108+
<span className="mx-auto flex w-full justify-center gap-2">
109+
Withdraw successful
110+
<CheckCircle className="text-primary" />
111+
</span>
112+
),
113+
},
114+
],
115+
},
116+
{
117+
title: 'BRIDGE xRLC/RLC',
118+
longTitle: 'Bridge your xRLC/RLC between chains',
119+
desc: 'Move your xRLC/RLC in your wallet between iExec Sidechain and Ethereum Mainnet with our bridge. ',
120+
content: (
121+
<Button asChild>
122+
<a
123+
href="https://bridge-bellecour.iex.ec/"
124+
rel="noreferrer"
125+
target="_blank"
126+
>
127+
Bridge <ExternalLink />
128+
</a>
129+
</Button>
130+
),
131+
},
132+
].filter((tab, index) => {
133+
if (index === 2 && chainId !== SUPPORTED_CHAINS[0].id) return false;
134+
return true;
135+
});
136+
}

src/routeTree.gen.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { Route as ChainSlugLayoutTasksImport } from './routes/$chainSlug/_layout
2121
import { Route as ChainSlugLayoutDealsImport } from './routes/$chainSlug/_layout/deals'
2222
import { Route as ChainSlugLayoutDatasetsImport } from './routes/$chainSlug/_layout/datasets'
2323
import { Route as ChainSlugLayoutAppsImport } from './routes/$chainSlug/_layout/apps'
24+
import { Route as ChainSlugLayoutAccountImport } from './routes/$chainSlug/_layout/account'
2425
import { Route as ChainSlugLayoutWorkerpoolWorkerpoolAddressImport } from './routes/$chainSlug/_layout/workerpool/$workerpoolAddress'
2526
import { Route as ChainSlugLayoutTxTxAddressImport } from './routes/$chainSlug/_layout/tx/$txAddress'
2627
import { Route as ChainSlugLayoutTaskTaskAddressImport } from './routes/$chainSlug/_layout/task/$taskAddress'
@@ -90,6 +91,12 @@ const ChainSlugLayoutAppsRoute = ChainSlugLayoutAppsImport.update({
9091
getParentRoute: () => ChainSlugLayoutRoute,
9192
} as any)
9293

94+
const ChainSlugLayoutAccountRoute = ChainSlugLayoutAccountImport.update({
95+
id: '/account',
96+
path: '/account',
97+
getParentRoute: () => ChainSlugLayoutRoute,
98+
} as any)
99+
93100
const ChainSlugLayoutWorkerpoolWorkerpoolAddressRoute =
94101
ChainSlugLayoutWorkerpoolWorkerpoolAddressImport.update({
95102
id: '/workerpool/$workerpoolAddress',
@@ -165,6 +172,13 @@ declare module '@tanstack/react-router' {
165172
preLoaderRoute: typeof ChainSlugLayoutImport
166173
parentRoute: typeof ChainSlugRoute
167174
}
175+
'/$chainSlug/_layout/account': {
176+
id: '/$chainSlug/_layout/account'
177+
path: '/account'
178+
fullPath: '/$chainSlug/account'
179+
preLoaderRoute: typeof ChainSlugLayoutAccountImport
180+
parentRoute: typeof ChainSlugLayoutImport
181+
}
168182
'/$chainSlug/_layout/apps': {
169183
id: '/$chainSlug/_layout/apps'
170184
path: '/apps'
@@ -262,6 +276,7 @@ declare module '@tanstack/react-router' {
262276
// Create and export the route tree
263277

264278
interface ChainSlugLayoutRouteChildren {
279+
ChainSlugLayoutAccountRoute: typeof ChainSlugLayoutAccountRoute
265280
ChainSlugLayoutAppsRoute: typeof ChainSlugLayoutAppsRoute
266281
ChainSlugLayoutDatasetsRoute: typeof ChainSlugLayoutDatasetsRoute
267282
ChainSlugLayoutDealsRoute: typeof ChainSlugLayoutDealsRoute
@@ -278,6 +293,7 @@ interface ChainSlugLayoutRouteChildren {
278293
}
279294

280295
const ChainSlugLayoutRouteChildren: ChainSlugLayoutRouteChildren = {
296+
ChainSlugLayoutAccountRoute: ChainSlugLayoutAccountRoute,
281297
ChainSlugLayoutAppsRoute: ChainSlugLayoutAppsRoute,
282298
ChainSlugLayoutDatasetsRoute: ChainSlugLayoutDatasetsRoute,
283299
ChainSlugLayoutDealsRoute: ChainSlugLayoutDealsRoute,
@@ -315,6 +331,7 @@ const ChainSlugRouteWithChildren = ChainSlugRoute._addFileChildren(
315331
export interface FileRoutesByFullPath {
316332
'/': typeof IndexRoute
317333
'/$chainSlug': typeof ChainSlugLayoutRouteWithChildren
334+
'/$chainSlug/account': typeof ChainSlugLayoutAccountRoute
318335
'/$chainSlug/apps': typeof ChainSlugLayoutAppsRoute
319336
'/$chainSlug/datasets': typeof ChainSlugLayoutDatasetsRoute
320337
'/$chainSlug/deals': typeof ChainSlugLayoutDealsRoute
@@ -333,6 +350,7 @@ export interface FileRoutesByFullPath {
333350
export interface FileRoutesByTo {
334351
'/': typeof IndexRoute
335352
'/$chainSlug': typeof ChainSlugLayoutIndexRoute
353+
'/$chainSlug/account': typeof ChainSlugLayoutAccountRoute
336354
'/$chainSlug/apps': typeof ChainSlugLayoutAppsRoute
337355
'/$chainSlug/datasets': typeof ChainSlugLayoutDatasetsRoute
338356
'/$chainSlug/deals': typeof ChainSlugLayoutDealsRoute
@@ -352,6 +370,7 @@ export interface FileRoutesById {
352370
'/': typeof IndexRoute
353371
'/$chainSlug': typeof ChainSlugRouteWithChildren
354372
'/$chainSlug/_layout': typeof ChainSlugLayoutRouteWithChildren
373+
'/$chainSlug/_layout/account': typeof ChainSlugLayoutAccountRoute
355374
'/$chainSlug/_layout/apps': typeof ChainSlugLayoutAppsRoute
356375
'/$chainSlug/_layout/datasets': typeof ChainSlugLayoutDatasetsRoute
357376
'/$chainSlug/_layout/deals': typeof ChainSlugLayoutDealsRoute
@@ -372,6 +391,7 @@ export interface FileRouteTypes {
372391
fullPaths:
373392
| '/'
374393
| '/$chainSlug'
394+
| '/$chainSlug/account'
375395
| '/$chainSlug/apps'
376396
| '/$chainSlug/datasets'
377397
| '/$chainSlug/deals'
@@ -389,6 +409,7 @@ export interface FileRouteTypes {
389409
to:
390410
| '/'
391411
| '/$chainSlug'
412+
| '/$chainSlug/account'
392413
| '/$chainSlug/apps'
393414
| '/$chainSlug/datasets'
394415
| '/$chainSlug/deals'
@@ -406,6 +427,7 @@ export interface FileRouteTypes {
406427
| '/'
407428
| '/$chainSlug'
408429
| '/$chainSlug/_layout'
430+
| '/$chainSlug/_layout/account'
409431
| '/$chainSlug/_layout/apps'
410432
| '/$chainSlug/_layout/datasets'
411433
| '/$chainSlug/_layout/deals'
@@ -459,6 +481,7 @@ export const routeTree = rootRoute
459481
"filePath": "$chainSlug/_layout.tsx",
460482
"parent": "/$chainSlug",
461483
"children": [
484+
"/$chainSlug/_layout/account",
462485
"/$chainSlug/_layout/apps",
463486
"/$chainSlug/_layout/datasets",
464487
"/$chainSlug/_layout/deals",
@@ -474,6 +497,10 @@ export const routeTree = rootRoute
474497
"/$chainSlug/_layout/workerpool/$workerpoolAddress"
475498
]
476499
},
500+
"/$chainSlug/_layout/account": {
501+
"filePath": "$chainSlug/_layout/account.tsx",
502+
"parent": "/$chainSlug/_layout"
503+
},
477504
"/$chainSlug/_layout/apps": {
478505
"filePath": "$chainSlug/_layout/apps.tsx",
479506
"parent": "/$chainSlug/_layout"

0 commit comments

Comments
 (0)