Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions web-v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,20 @@
"add-icon": "tsx ./scripts/add-icon.ts"
},
"dependencies": {
"lwk_web": "file:../lwk_wasm/pkg_web",
"@fontsource-variable/inter": "^5.2.8",
"@heroui/react": "^3.0.4",
"@tanstack/react-query": "^5.100.10",
"axios": "^1.7.0",
"lwk_web": "file:../lwk_wasm/pkg_web",
"react": "^19.2.6",
"react-dom": "^19.2.6",
"react-router-dom": "^7.15.0",
"zod": "^3.25.76"
},
"devDependencies": {
"@eslint/js": "^9.39.4",
"@tanstack/react-query-devtools": "^5.100.10",
"@tailwindcss/postcss": "^4.2.0",
"@tanstack/react-query-devtools": "^5.100.10",
"@types/node": "^24.12.4",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
Expand Down
11 changes: 11 additions & 0 deletions web-v2/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 7 additions & 6 deletions web-v2/scripts/add-icon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,18 +67,19 @@ async function getIconOptions() {
}

async function createIconComponent({ name, content }: { name: string; content: string }) {
const fileName = name.toLowerCase().replace(/ /g, '-') + '-icon'
const filePath = `./src/components/icons/${fileName}.tsx`
const componentName =
name
.toLowerCase()
.replace(/ /g, '-')
.replace(/(^\w|-\w)/g, c => c.toUpperCase())
.replace(/-/g, '') + 'Icon'
const filePath = `./src/components/icons/${componentName}.tsx`
Comment on lines +70 to +76
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can component name actually contain spaces? Looking at existing icons, it seems we always use PascalCase names. If that's true, do we need all this normalization logic?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And add one-line comment or name regEx describing the component case


// Check if the file already exists
if (fs.existsSync(filePath)) {
console.error(chalk.red(`File ${filePath} already exists. Please choose a different name.`))
process.exit(1)
}

// Use PascalCase for the component name
const componentName = fileName.replace(/(^\w|-\w)/g, c => c.toUpperCase()).replace(/-/g, '')

const fileContent = `
import type { SVGProps } from "react"

Expand Down
5 changes: 5 additions & 0 deletions web-v2/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AppProviders } from '@/providers/AppProviders'
import ErrorBoundary from './components/ErrorBoundary'
import BorrowPage from './pages/Borrow'
import DashboardPage from './pages/Dashboard'
import DesignSystemPage from './pages/DesignSystem'
import SupplyPage from './pages/Supply'

const router = createBrowserRouter([
Expand All @@ -27,6 +28,10 @@ const router = createBrowserRouter([
path: RoutePath.Supply,
element: <SupplyPage />,
},
{
path: RoutePath.DesignSystem,
element: <DesignSystemPage />,
},
],
},
])
Expand Down
33 changes: 17 additions & 16 deletions web-v2/src/components/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,31 @@ import { Link, Outlet } from 'react-router-dom'
import { env } from '@/constants/env'
import { RoutePath } from '@/constants/routes'

const NAV = [
{ to: RoutePath.Dashboard, label: 'Dashboard' },
{ to: RoutePath.Borrow, label: 'Borrow' },
{ to: RoutePath.Supply, label: 'Supply' },
{ to: RoutePath.DesignSystem, label: 'System' },
]

export default function AppLayout() {
return (
<main className='min-h-screen'>
<main className='bg-background text-foreground min-h-screen'>
<div className='mx-auto flex w-full max-w-5xl flex-col gap-8 px-4 py-8'>
<header className='flex justify-between items-center'>
<h1 className='text-4xl font-black uppercase'>Lending</h1>
<header className='flex items-center justify-between'>
<h1 className='text-h2'>Lending</h1>
<nav className='flex flex-wrap items-center gap-6 text-sm font-medium'>
<Link className='text-accent hover:underline' to={RoutePath.Dashboard}>
Dashboard
</Link>
<Link className='text-accent hover:underline' to={RoutePath.Borrow}>
Borrow
</Link>
<Link className='text-accent hover:underline' to={RoutePath.Supply}>
Supply
</Link>
{NAV.map(({ to, label }) => (
<Link key={to} className='text-accent hover:underline' to={to}>
{label}
</Link>
))}
</nav>
</header>

<section className='rounded-2xl border bg-white p-6 shadow-sm'>
<Outlet />
</section>
<Outlet />

<footer className='text-sm'>
<footer className='text-muted text-xs'>
<p>Network: {env.VITE_NETWORK}</p>
<p>API URL: {env.VITE_API_URL}</p>
<p>Esplora Base URL: {env.VITE_ESPLORA_BASE_URL}</p>
Expand Down
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And recheck others

File renamed without changes.
28 changes: 28 additions & 0 deletions web-v2/src/components/ui/UiButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Button, type ButtonProps, Spinner } from '@heroui/react'
import type { ReactNode } from 'react'

export interface UiButtonProps extends Omit<ButtonProps, 'children'> {
isLoading?: boolean
loadingText?: ReactNode
startContent?: ReactNode
endContent?: ReactNode
children?: ReactNode
}

export function UiButton({
isLoading,
loadingText,
isDisabled,
startContent,
endContent,
children,
...props
}: UiButtonProps) {
return (
<Button isDisabled={isDisabled || isLoading} {...props}>
{isLoading ? <Spinner size='sm' color='current' aria-hidden /> : startContent}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{isLoading ? (loadingText ?? children) : children}
{!isLoading && endContent}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

startContent and endContent can be passed as children?

</Button>
)
}
11 changes: 11 additions & 0 deletions web-v2/src/components/ui/UiCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Card, type CardProps } from '@heroui/react'

export type UiCardProps = CardProps

export const UiCard = Object.assign((props: UiCardProps) => <Card {...props} />, {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to override Card if no changes to the component needed, we can use Card component from Hero UI directly

Header: Card.Header,
Title: Card.Title,
Description: Card.Description,
Content: Card.Content,
Footer: Card.Footer,
})
50 changes: 50 additions & 0 deletions web-v2/src/components/ui/UiInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {
Description,
FieldError,
Input,
InputGroup,
Label,
TextField,
type TextFieldProps,
} from '@heroui/react'
import type { ReactNode } from 'react'

export interface UiInputProps extends Omit<TextFieldProps, 'children'> {
label?: ReactNode
placeholder?: string
description?: ReactNode
errorMessage?: ReactNode
startContent?: ReactNode
endContent?: ReactNode
}

export function UiInput({
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's name it UitextField to respect Hero UI original name

label,
placeholder,
description,
errorMessage,
isInvalid,
startContent,
endContent,
...props
}: UiInputProps) {
const invalid = isInvalid ?? Boolean(errorMessage)
const hasGroup = Boolean(startContent || endContent)

return (
<TextField isInvalid={invalid} {...props}>
{label && <Label>{label}</Label>}
{hasGroup ? (
<InputGroup>
{startContent && <InputGroup.Prefix>{startContent}</InputGroup.Prefix>}
<InputGroup.Input placeholder={placeholder} />
{endContent && <InputGroup.Suffix>{endContent}</InputGroup.Suffix>}
</InputGroup>
) : (
<Input placeholder={placeholder} />
)}
{description && !invalid && <Description>{description}</Description>}
{invalid && errorMessage && <FieldError>{errorMessage}</FieldError>}
</TextField>
)
}
50 changes: 50 additions & 0 deletions web-v2/src/components/ui/UiModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Modal, type ModalContainerProps } from '@heroui/react'
import type { ReactNode } from 'react'

export interface UiModalProps {
isOpen?: boolean
defaultOpen?: boolean
onOpenChange?: (isOpen: boolean) => void
title?: ReactNode
children?: ReactNode
footer?: ReactNode
trigger?: ReactNode
size?: ModalContainerProps['size']
placement?: ModalContainerProps['placement']
isDismissable?: boolean
showCloseButton?: boolean
}
Comment on lines +4 to +16
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use ModalProps from Hero UI


export function UiModal({
isOpen,
defaultOpen,
onOpenChange,
title,
children,
footer,
trigger,
size,
placement,
isDismissable = true,
showCloseButton = true,
}: UiModalProps) {
return (
<Modal.Root isOpen={isOpen} defaultOpen={defaultOpen} onOpenChange={onOpenChange}>
{trigger ? <Modal.Trigger>{trigger}</Modal.Trigger> : null}
<Modal.Backdrop isDismissable={isDismissable}>
<Modal.Container size={size} placement={placement}>
<Modal.Dialog>
{title || showCloseButton ? (
<Modal.Header>
{title ? <Modal.Heading>{title}</Modal.Heading> : null}
{showCloseButton ? <Modal.CloseTrigger /> : null}
</Modal.Header>
) : null}
<Modal.Body>{children}</Modal.Body>
{footer ? <Modal.Footer>{footer}</Modal.Footer> : null}
</Modal.Dialog>
</Modal.Container>
</Modal.Backdrop>
</Modal.Root>
)
}
Loading