Compare commits

...

2 Commits

Author SHA1 Message Date
945bd5d357 Matrix console create product 2025-01-09 23:37:24 +08:00
daba03c2e8 Basic console layout 2025-01-09 22:33:52 +08:00
11 changed files with 331 additions and 17 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -18,6 +18,7 @@
"@mui/material-nextjs": "^6.3.1",
"@mui/x-charts": "^7.23.2",
"@tailwindcss/typography": "^0.5.16",
"@toolpad/core": "^0.11.0",
"@vercel/speed-insights": "^1.1.0",
"animate.css": "^4.1.1",
"axios": "^1.7.9",
@ -51,6 +52,8 @@
"@eslint/eslintrc": "^3.2.0"
},
"trustedDependencies": [
"@vercel/speed-insights"
"@vercel/speed-insights",
"esbuild",
"sharp"
]
}

View File

@ -47,7 +47,7 @@ export function CapAppBar() {
<CapDrawer width={drawerWidth} open={open} onClose={() => setOpen(false)} />
<AppBarScroll elevation={0}>
<AppBar position="sticky" elevation={0} color="transparent" className="backdrop-blur-md">
<AppBar position="sticky" elevation={0} color="transparent" className="backdrop-blur-md z-10">
<Toolbar>
<IconButton
size="large"

View File

@ -16,10 +16,11 @@ import Image from 'next/image'
import ExploreIcon from '@mui/icons-material/Explore'
import PhotoLibraryIcon from '@mui/icons-material/PhotoLibrary'
import AppsIcon from '@mui/icons-material/Apps'
import NextLink from 'next/link'
import { useRouter } from 'next/router'
interface NavLink {
export interface NavLink {
title: string
icon?: JSX.Element
href: string
@ -39,6 +40,11 @@ export function CapDrawer({ width, open, onClose }: { width: number; open: boole
icon: <PhotoLibraryIcon />,
href: '/attachments',
},
{
title: 'Matrix',
icon: <AppsIcon />,
href: '/matrix',
},
]
const additionalLinks: NavLink[] = [
@ -46,6 +52,10 @@ export function CapDrawer({ width, open, onClose }: { width: number; open: boole
title: 'Terms & Conditions',
href: '/terms',
},
{
title: 'SN Console',
href: '/console',
},
]
return (
@ -87,7 +97,7 @@ export function CapDrawer({ width, open, onClose }: { width: number; open: boole
))}
</List>
<Divider />
<Box sx={{ display: 'flex', flexWrap: 'wrap', px: 2, py: 1.5 }}>
<Box sx={{ display: 'flex', flexWrap: 'wrap', px: 2, py: 1.5, gap: 1 }}>
{additionalLinks.map((l) => (
<NextLink passHref href={l.href} key={l.href}>
<Link variant="body2" color={'textSecondary'} fontSize={13}>

View File

@ -0,0 +1,63 @@
import { checkAuthenticatedClient, redirectToLogin } from '@/services/auth'
import { JSX, useEffect } from 'react'
import { DashboardLayout, Navigation } from '@toolpad/core'
import { Box, Stack, Typography } from '@mui/material'
import NextLink from 'next/link'
import HomeIcon from '@mui/icons-material/Home'
import AppsIcon from '@mui/icons-material/Apps'
export function ConsoleLayout({ children }: { children: JSX.Element }) {
useEffect(() => {
if (!checkAuthenticatedClient()) redirectToLogin()
}, [])
const navigation: Navigation = [
{
segment: '',
title: 'Home',
icon: <HomeIcon />,
},
{
segment: 'console/matrix',
title: 'Matrix',
icon: <AppsIcon />,
},
]
return (
<DashboardLayout
navigation={navigation}
branding={{
homeUrl: '/console',
}}
slots={{
appTitle(_) {
return (
<Stack direction="row" alignItems="center" spacing={2}>
<NextLink passHref href="/console">
<Typography variant="h6">Solar Network Console</Typography>
</NextLink>
</Stack>
)
},
toolbarActions(_) {
return <Box />
},
}}
sidebarExpandedWidth={300}
defaultSidebarCollapsed
>
{children}
</DashboardLayout>
)
}
export function getConsoleStaticProps(original: any) {
if (original.props.title) {
original.props.title = 'Console | ' + original.props.title
}
original.props.showAppBar = false
return original
}

View File

@ -4,6 +4,7 @@ import { Box, createTheme, CssBaseline, ThemeProvider } from '@mui/material'
import { Roboto } from 'next/font/google'
import { CapAppBar } from '@/components/CapAppBar'
import { PagesProgressBar as ProgressBar } from 'next-nprogress-bar'
import { AppProvider } from '@toolpad/core/nextjs'
import { useUserStore } from '@/services/user'
import { useEffect } from 'react'
import Head from 'next/head'
@ -56,20 +57,22 @@ export default function App({ Component, pageProps }: AppProps) {
<link rel="apple-touch-icon" href="/apple-icon.png" type="image/png" />
</Head>
<ThemeProvider theme={siteTheme}>
<CssBaseline />
<ProgressBar
height="4px"
color={siteTheme.palette.primary.main}
options={{ showSpinner: false }}
shallowRouting
/>
<AppProvider>
<ThemeProvider theme={siteTheme}>
<CssBaseline />
<ProgressBar
height="4px"
color={siteTheme.palette.primary.main}
options={{ showSpinner: false }}
shallowRouting
/>
<CapAppBar />
<Box sx={{ minHeight: 'calc(100vh - 64px)' }}>
<Component {...pageProps} />
</Box>
</ThemeProvider>
{(pageProps.showAppBar ?? true) && <CapAppBar />}
<Box sx={{ minHeight: 'calc(100vh - 64px)' }}>
<Component {...pageProps} />
</Box>
</ThemeProvider>
</AppProvider>
</>
)
}

View File

@ -0,0 +1,51 @@
import { ConsoleLayout, getConsoleStaticProps } from '@/components/layouts/ConsoleLayout'
import { Typography, Container, Box, Grid2 as Grid, Card, CardContent, CardActionArea } from '@mui/material'
import NextLink from 'next/link'
import DynamicFormIcon from '@mui/icons-material/DynamicForm'
import AppsIcon from '@mui/icons-material/Apps'
export function getStaticProps() {
return getConsoleStaticProps({
props: {
title: 'Welcome',
},
})
}
export default function ConsoleLanding() {
return (
<ConsoleLayout>
<Container sx={{ py: 16, display: 'flex', flexDirection: 'column', gap: 8 }}>
<Box>
<DynamicFormIcon sx={{ fontSize: 64, mb: 2 }} />
<Typography variant="subtitle2">Welcome to the</Typography>
<Typography variant="h3" component="h1">
Console
</Typography>
<Typography variant="subtitle1">of the Solar Network</Typography>
</Box>
<Grid container columns={{ xs: 2, sm: 2, md: 3, lg: 4 }} spacing={4}>
<Grid size={1}>
<NextLink passHref href="/console/matrix">
<CardActionArea>
<Card sx={{ width: '100%' }}>
<CardContent>
<AppsIcon sx={{ fontSize: 32, mb: 1.5 }} />
<Typography variant="h5" gutterBottom>
Matrix
</Typography>
<Typography variant="body1">
Publish and versioning your application with Matrix Marketplace.
</Typography>
</CardContent>
</Card>
</CardActionArea>
</NextLink>
</Grid>
</Grid>
</Container>
</ConsoleLayout>
)
}

View File

@ -0,0 +1,66 @@
import { ConsoleLayout, getConsoleStaticProps } from '@/components/layouts/ConsoleLayout'
import { MaProduct } from '@/services/matrix/product'
import { sni } from '@/services/network'
import { Typography, Container, Box, Button, Grid2 as Grid, Card, CardActionArea, CardContent } from '@mui/material'
import NextLink from 'next/link'
import { useEffect, useState } from 'react'
export async function getStaticProps() {
return getConsoleStaticProps({
props: {
title: 'Matrix Marketplace',
},
})
}
export default function MatrixMarketplace() {
const [products, setProducts] = useState<MaProduct[]>([])
async function fetchProducts() {
const { data: resp } = await sni.get<{ data: MaProduct[] }>('/cgi/ma/products/created', {
params: {
take: 10,
},
})
setProducts(resp.data)
}
useEffect(() => {
fetchProducts()
}, [])
return (
<ConsoleLayout>
<Container sx={{ py: 16, display: 'flex', flexDirection: 'column', gap: 4 }}>
<Typography variant="h3" component="h1">
Matrix Marketplace
</Typography>
<Grid container columns={{ xs: 2, sm: 2, md: 3, lg: 4 }} spacing={4}>
{products.map((p) => (
<Grid size={1} key={p.id}>
<NextLink passHref href="/console/matrix">
<CardActionArea>
<Card sx={{ width: '100%' }}>
<CardContent>
<Typography variant="h5" gutterBottom>
{p.name}
</Typography>
<Typography variant="body1">{p.description}</Typography>
</CardContent>
</Card>
</CardActionArea>
</NextLink>
</Grid>
))}
</Grid>
<Box>
<NextLink passHref href="/console/matrix/products/new">
<Button variant="contained">Create a product</Button>
</NextLink>
</Box>
</Container>
</ConsoleLayout>
)
}

View File

@ -0,0 +1,82 @@
import { ConsoleLayout, getConsoleStaticProps } from '@/components/layouts/ConsoleLayout'
import { Typography, Container, Box, Button, TextField, Collapse, Alert } from '@mui/material'
import { useForm } from 'react-hook-form'
import { useState } from 'react'
import { useRouter } from 'next/router'
import NextLink from 'next/link'
import { sni } from '@/services/network'
import ErrorIcon from '@mui/icons-material/Error'
export async function getStaticProps() {
return getConsoleStaticProps({
props: {
title: 'Matrix',
},
})
}
interface MatrixProductNewForm {
name: string
alias: string
description: string
introduction: string
}
export default function ProductNew() {
const { handleSubmit, register } = useForm<MatrixProductNewForm>()
const router = useRouter()
const [error, setError] = useState<string | null>(null)
const [busy, setBusy] = useState<boolean>(false)
async function onSubmit(data: any) {
try {
setBusy(true)
await sni.post('/cgi/ma/products', data)
router.push('/console/matrix')
} catch (err: any) {
setError(err.toString())
} finally {
setBusy(false)
}
}
return (
<ConsoleLayout>
<Container sx={{ py: 16, display: 'flex', flexDirection: 'column', gap: 4 }}>
<Typography variant="h3" component="h1">
Create a product
</Typography>
<form onSubmit={handleSubmit(onSubmit)}>
<Box display="flex" flexDirection="column" maxWidth="sm" gap={2.5}>
<Collapse in={!!error} sx={{ width: '100%' }}>
<Alert icon={<ErrorIcon fontSize="inherit" />} severity="error">
{error}
</Alert>
</Collapse>
<TextField label="Name" {...register('name')} />
<TextField label="Alias" {...register('alias')} />
<TextField minRows={3} maxRows={3} multiline label="Description" {...register('description')} />
<TextField minRows={5} multiline label="Introduction" {...register('introduction')} />
<Box sx={{ mt: 5 }} display="flex" gap={2}>
<Button variant="contained" type="submit" disabled={busy}>
Create
</Button>
<NextLink passHref href="/console/matrix">
<Button disabled={busy}>Cancel</Button>
</NextLink>
</Box>
</Box>
</form>
</Container>
</ConsoleLayout>
)
}

View File

@ -0,0 +1,11 @@
import { Typography, Container } from '@mui/material'
export default function MatrixMarketplace() {
return (
<Container sx={{ py: 24, display: 'flex', flexDirection: 'column', gap: 32 }}>
<Typography variant="h3" component="h1">
Matrix Marketplace
</Typography>
</Container>
)
}

View File

@ -0,0 +1,25 @@
export interface MaProduct {
id: number
created_at: Date
updated_at: Date
deleted_at?: Date
icon: string
name: string
alias: string
description: string
previews: string[]
tags: string[]
meta: MaProductMeta
releases: null
account_id: number
}
export interface MaProductMeta {
id: number
created_at: Date
updated_at: Date
deleted_at?: Date
introduction: string
attachments: string[]
product_id: number
}