build ok,

This commit is contained in:
louiscklaw
2025-04-14 09:26:24 +08:00
commit 6c931c1fe8
770 changed files with 63959 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
import * as React from 'react';
import type { Metadata } from 'next';
import RouterLink from 'next/link';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Grid from '@mui/material/Unstable_Grid2';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { config } from '@/config';
import { paths } from '@/paths';
import { ChapterView } from '@/components/dashboard/academy/chapter-view';
import { CourseSummary } from '@/components/dashboard/academy/course-summary';
import type { Chapter, Course } from '@/components/dashboard/academy/types';
export const metadata = { title: `Course | Academy | ${config.site.name}` } satisfies Metadata;
const course = {
id: 'CRS-001',
title: 'React and Redux Tutorial',
description: 'Introductory course for design and framework basics',
duration: '78 hours',
progress: 50,
} satisfies Course;
const chapters = [
{ id: 'CHAP-001', number: 1, title: 'Introduction', description: 'Introducing the app and how it works', lesson: '' },
{
id: 'CHAP-002',
number: 2,
title: 'Installing required packages',
description: 'Installing the required packages for the project',
lesson: `Alias animi labque, deserunt distinctio eum excepturi fuga iure labore magni molestias mollitia natus, officia pofro quis sunt
temporibus veritatis voluptatem, voluptatum. Aut blanditiis esse et illum maxim, obcaecati possimus voluptate! Accusamus adipisci
amet aperiam, assumenda consequuntur fugiat inventore iusto magnam molestias natus necessitatibus, nulla pariatur.
Adipisci alias animi debitis eos et impedit maiores, modi nam nobis officia optio perspiciatis, rerum. Accusantium esse nostrum odit quis quo:
\`\`\`ts
const mongoose = require('mongoose'),
const uniqueValidator = require('mongoose-unique-validator'),
const bcrypt = require('bcrypt'),
const SALT_WORK_FACTOR = 10;
const Schema = mongoose.Schema;
const Email = new Schema({ address: { type: String,
lowercase: true,
required: [true, "can't be blank"],
match: [/\\S+@\\S+\\.\\S+/, 'is invalid'],
index: true,
},
// Change the default to true if you don't need to validate a new user's email address
validated: { type: Boolean, default: false },
});
\`\`\``,
},
{
id: 'CHAP-003',
number: 3,
title: 'Setting up the project',
description: "Structuring the project's folders and files",
lesson: '',
},
] satisfies Chapter[];
export default function Page(): React.JSX.Element {
const currentChapter = chapters[1];
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Grid container spacing={4}>
<Grid md={4} xs={12}>
<Stack spacing={3}>
<div>
<Link
color="text.primary"
component={RouterLink}
href={paths.dashboard.academy.browse}
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
variant="subtitle2"
>
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
Academy
</Link>
</div>
<CourseSummary chapters={chapters} course={course} currentChapterNumber={currentChapter.number} />
</Stack>
</Grid>
<Grid md={8} xs={12}>
<ChapterView chapter={currentChapter} />
</Grid>
</Grid>
</Box>
);
}

View File

@@ -0,0 +1,114 @@
import * as React from 'react';
import type { Metadata } from 'next';
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import { config } from '@/config';
import { CourseCard } from '@/components/dashboard/academy/course-card';
import { CoursesFilters } from '@/components/dashboard/academy/courses-filters';
import { DailyProgress } from '@/components/dashboard/academy/daily-progress';
import { Help } from '@/components/dashboard/academy/help';
import type { Course } from '@/components/dashboard/academy/types';
export const metadata = { title: `Browse | Academy | ${config.site.name}` } satisfies Metadata;
const courses = [
{
id: 'CRS-003',
title: 'React Crash Course: Beginner',
description: 'Introductory course for design and framework basics',
media: '/assets/course-3.png',
duration: '21 hours',
progress: 90,
},
{
id: 'CRS-002',
title: 'React and Express Tutorial',
description: 'Introductory course for design and framework basics',
media: '/assets/course-2.png',
duration: '14 hours',
progress: 52,
},
{
id: 'CRS-001',
title: 'React and Redux Tutorial',
description: 'Introductory course for design and framework basics',
media: '/assets/course-1.png',
duration: '78 hours',
progress: 23,
},
] satisfies Course[];
export default function Page(): React.JSX.Element {
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Box
sx={{
bgcolor: 'var(--mui-palette-neutral-900)',
borderRadius: '20px',
color: 'var(--mui-palette-common-white)',
overflow: 'hidden',
position: 'relative',
p: { xs: '32px 24px', md: '64px 56px', lg: '120px 80px' },
}}
>
<Box
alt="Pulse"
component="img"
src="/assets/pulse.svg"
sx={{
top: 0,
height: 'auto',
right: 0,
position: 'absolute',
width: '900px',
zIndex: 0,
transform: 'scaleX(-1)',
}}
/>
<Stack spacing={4} sx={{ position: 'relative', zIndex: 1 }}>
<Stack spacing={1}>
<Typography variant="h3">Find unparalleled knowledge</Typography>
<Typography>Learn from the top-tier creatives and leading experts in AI</Typography>
</Stack>
<CoursesFilters />
</Stack>
</Box>
<Stack spacing={1}>
<Typography variant="h6">Welcome back, Sofia</Typography>
<Typography color="text.secondary" variant="body2">
Nice progress so far, keep it up!
</Typography>
</Stack>
<Grid container spacing={4}>
<Grid md={8} xs={12}>
<DailyProgress timeCurrent={20} timeGoal={35} />
</Grid>
<Grid md={4} xs={12}>
<Help />
</Grid>
</Grid>
<Stack spacing={4}>
<Typography variant="h6">My courses</Typography>
<Grid container spacing={4}>
{courses.map((course) => (
<Grid key={course.id} md={4} xs={12}>
<CourseCard course={course} />
</Grid>
))}
</Grid>
</Stack>
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,125 @@
import * as React from 'react';
import type { Metadata } from 'next';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import { config } from '@/config';
import { ChannelSessionsVsBounce } from '@/components/dashboard/analytics/channel-sessions-vs-bounce-rate';
import { CountrySessionsVsBounce } from '@/components/dashboard/analytics/country-sessions-vs-bounce-rate';
import { Devices } from '@/components/dashboard/analytics/devices';
import { InboundOutbound } from '@/components/dashboard/analytics/inbound-outbound';
import { Insight } from '@/components/dashboard/analytics/insight';
import { Summary } from '@/components/dashboard/analytics/summary';
export const metadata = { title: `Analytics | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element {
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
<Box sx={{ flex: '1 1 auto' }}>
<Typography variant="h4">Analytics</Typography>
</Box>
<div>
<Button startIcon={<PlusIcon />} variant="contained">
Add metrics
</Button>
</div>
</Stack>
<Grid container spacing={4}>
<Grid xs={12}>
<Summary />
</Grid>
<Grid lg={6} xs={12}>
<CountrySessionsVsBounce
data={[
{ name: 'us', v1: 600, v2: 560 },
{ name: 'uk', v1: 540, v2: 500 },
{ name: 'ru', v1: 490, v2: 450 },
{ name: 'ca', v1: 440, v2: 380 },
{ name: 'de', v1: 320, v2: 280 },
]}
/>
</Grid>
<Grid lg={6} xs={12}>
<ChannelSessionsVsBounce
data={[
{ name: 'Organic', v1: 600, v2: 560 },
{ name: 'Direct', v1: 540, v2: 500 },
{ name: 'Paid Ads', v1: 490, v2: 450 },
{ name: 'Social', v1: 440, v2: 380 },
{ name: 'Email', v1: 320, v2: 280 },
]}
/>
</Grid>
<Grid lg={4} xs={12}>
<Devices
data={[
{ name: 'Desktop', value: 68.2, color: 'var(--mui-palette-primary-main)' },
{ name: 'Mobile', value: 22.8, color: 'var(--mui-palette-success-main)' },
{ name: 'Tablet', value: 10, color: 'var(--mui-palette-warning-main)' },
]}
/>
</Grid>
<Grid lg={4} xs={12}>
<InboundOutbound
inbound={{
color: 'var(--mui-palette-error-main)',
data: [
720, 705, 707, 691, 692, 640, 634, 630, 647, 640, 661, 670, 652, 638, 631, 620, 624, 636, 632, 631,
616, 601, 602, 580, 572, 571, 562, 540,
],
diff: 25,
trend: 'down',
value: 560,
}}
outbound={{
color: 'var(--mui-palette-success-main)',
data: [
1836, 1872, 1911, 1912, 1920, 1942, 1941, 1956, 1971, 1972, 1976, 1964, 1960, 1971, 1978, 1992, 2009,
2001, 1980, 1987, 1970, 1973, 1979, 2028, 2029, 2034, 2035, 2040,
],
diff: 10,
trend: 'up',
value: 2040,
}}
/>
</Grid>
<Grid lg={4} xs={12}>
<Insight
insights={[
{
id: 'INSIGHT-1',
title: '+15%',
description: 'forecasted increase in your traffic by the end of the current month',
},
{
id: 'INSIGHT-2',
title: '2.5%',
description: 'forecasted increase in your conversion rate by the end of the current month',
},
{
id: 'INSIGHT-3',
title: '3.5%',
description: 'forecasted increase in your revenue by the end of the current month',
},
]}
/>
</Grid>
</Grid>
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,38 @@
import * as React from 'react';
import type { Metadata } from 'next';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import { config } from '@/config';
export const metadata = { title: `Blank | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element {
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
<Box sx={{ flex: '1 1 auto' }}>
<Typography variant="h4">Blank</Typography>
</Box>
<div>
<Button startIcon={<PlusIcon />} variant="contained">
Action
</Button>
</div>
</Stack>
<Box sx={{ border: '1px dashed var(--mui-palette-divider)', height: '300px', p: '4px' }} />
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,158 @@
import * as React from 'react';
import type { Metadata } from 'next';
import RouterLink from 'next/link';
import Avatar from '@mui/material/Avatar';
import Box from '@mui/material/Box';
import Breadcrumbs from '@mui/material/Breadcrumbs';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import Chip from '@mui/material/Chip';
import Container from '@mui/material/Container';
import Divider from '@mui/material/Divider';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { config } from '@/config';
import { paths } from '@/paths';
import { dayjs } from '@/lib/dayjs';
import { BreadcrumbsSeparator } from '@/components/core/breadcrumbs-separator';
import { CommentAdd } from '@/components/dashboard/blog/comment-add';
import { CommentBox } from '@/components/dashboard/blog/comment-box';
import { Content } from '@/components/dashboard/blog/content';
import { Newsletter } from '@/components/dashboard/blog/newsletter';
import type { Comment } from '@/components/dashboard/blog/types';
export const metadata = { title: `Details | Blog | Dashboard | ${config.site.name}` } satisfies Metadata;
const content = `## Cras at molestie lacus. Phasellus feugiat leo quis sem iaculis, sed mattis nibh accumsan.
Phasellus ullamcorper ultrices ullamcorper. Nunc auctor porttitor ex, non consequat ipsum aliquam at. Duis dapibus dolor in nisi viverra, a elementum nulla viverra. Etiam feugiat turpis leo, nec finibus diam rhoncus ac. Sed at metus et orci consequat facilisis vel vel diam.
## Cras at molestie lacus. Phasellus feugiat leo quis sem iaculis, sed mattis nibh accumsan.
Etiam faucibus massa auctor gravida finibus.
Cras nulla magna, dapibus sit amet accumsan nec, ullamcorper sit amet dolor.
Donec leo nisi, porta et gravida nec, tincidunt ac velit. Aliquam in turpis a quam tempus dapibus. Morbi in tellus tempor, hendrerit mi vel, aliquet tellus. Quisque vel interdum ante. Nunc quis purus sem. Donec at risus lacinia ipsum cursus condimentum at ac dui. Nulla bibendum feugiat tellus ac tristique. Proin auctor, lectus et accumsan varius, justo odio vulputate neque, et efficitur augue leo id ex. Aliquam eget turpis nisl. Nam sapien massa, sollicitudin et vehicula a, fringilla vitae purus. Praesent a vestibulum felis.
\`\`\`javascript
// This is a comment
const x = () => {};
\`\`\`
Class aptent taciti sociosqu ad litora torquent \`const d = 3;\` per conubia nostra, per inceptos himenaeos. Morbi maximus metus eget nulla malesuada, sit amet luctus est fringilla. Aenean imperdiet rhoncus justo, ut pharetra lorem gravida placerat. Duis et enim lorem. Aliquam placerat elit est, vitae fermentum ipsum finibus sed. Donec dapibus magna non tortor commodo rhoncus. Suspendisse luctus tincidunt eros, aliquet pellentesque neque venenatis quis. Aliquam auctor felis nec orci ornare gravida. Fusce ac neque sit amet nibh scelerisque molestie. Nullam in lorem viverra, aliquam nunc vel, interdum orci. Fusce mattis est neque, et tincidunt justo blandit quis. Etiam tincidunt purus in libero semper, vitae placerat dui vehicula. Pellentesque sit amet imperdiet purus, quis lacinia eros.
Duis placerat turpis non metus dapibus sagittis. Vestibulum ex massa, tempus pulvinar varius at, placerat non justo. Ut tristique nisl sed porta pulvinar. Nunc ex nibh, tempor eget elit vel, fringilla ornare risus. Praesent vel lacus finibus, laoreet nulla quis, semper tellus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec volutpat quis dui ac varius. Suspendisse potenti. Maecenas sagittis lacus vitae ex rhoncus, eu fringilla urna luctus.
## Donec vel erat augue. Aenean ut nisl cursus nulla tempus ultricies vel eget lorem.
Suspendisse pharetra dolor in massa molestie, vel molestie nunc accumsan. Cras varius aliquet pellentesque. Curabitur ac mi fermentum nibh congue pharetra in eu nunc. Vivamus mattis urna a fringilla facilisis. Cras finibus nulla in nulla imperdiet pharetra. Morbi vel tortor turpis.`;
const comments = [
{
id: 'MSG-002',
content: 'Great article! Thanks for sharing.',
author: { name: 'Alcides Antonio', avatar: '/assets/avatar-10.png' },
createdAt: dayjs().subtract(2, 'hour').toDate(),
},
{
id: 'MSG-001',
createdAt: dayjs().subtract(8, 'hour').toDate(),
author: { name: 'Fran Perez', avatar: '/assets/avatar-5.png' },
content: 'I find this article very helpful. Do you have any other resources on this topic?',
},
] satisfies Comment[];
export default function Page(): React.JSX.Element | null {
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={8}>
<Stack spacing={1}>
<Typography variant="h4">Post</Typography>
<Breadcrumbs separator={<BreadcrumbsSeparator />}>
<Link color="text.primary" component={RouterLink} href={paths.dashboard.overview} variant="subtitle2">
Dashboard
</Link>
<Link color="text.primary" component={RouterLink} href={paths.dashboard.blog.list} variant="subtitle2">
Blog
</Link>
<Typography color="text.secondary" variant="subtitle2">
Details
</Typography>
</Breadcrumbs>
</Stack>
<Card
sx={{
alignItems: 'center',
borderRadius: 1,
boxShadow: 'var(--mui-shadows-16)',
display: 'flex',
justifyContent: 'space-between',
px: 3,
py: 2,
}}
>
<Typography variant="subtitle1">Hello, Sofia</Typography>
<Button component={RouterLink} href={paths.dashboard.blog.create} variant="contained">
Edit post
</Button>
</Card>
<Stack spacing={3}>
<div>
<Chip label="Programming" />
</div>
<Stack spacing={2}>
<Typography variant="h3">How to Create a Productivity Dashboard</Typography>
<Typography color="text.secondary" variant="subtitle1">
Learn how to create a productivity dashboard using Google Cloud and Supabase for your team.
</Typography>
</Stack>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center' }}>
<Avatar src="/assets/avatar-8.png" />
<div>
<Typography variant="subtitle2">
By Jie Yan {' '}
{dayjs().subtract(39, 'minute').subtract(7, 'hour').subtract(5, 'day').format('MMM D, YYYY')}
</Typography>
<Typography color="text.secondary" variant="body2">
5 min read
</Typography>
</div>
</Stack>
<Box
sx={{
backgroundImage: 'url(/assets/image-business-2.png)',
backgroundPosition: 'center',
backgroundSize: 'cover',
borderRadius: 1,
height: '380px',
}}
/>
<Container>
<Content content={content} />
</Container>
<Divider />
<Stack spacing={2}>
{comments.map((comment) => (
<CommentBox comment={comment} key={comment.id} />
))}
</Stack>
<Divider />
<CommentAdd />
</Stack>
<Newsletter />
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,79 @@
import * as React from 'react';
import type { Metadata } from 'next';
import RouterLink from 'next/link';
import Box from '@mui/material/Box';
import Breadcrumbs from '@mui/material/Breadcrumbs';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import IconButton from '@mui/material/IconButton';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { DotsThree as DotsThreeIcon } from '@phosphor-icons/react/dist/ssr/DotsThree';
import { config } from '@/config';
import { paths } from '@/paths';
import { BreadcrumbsSeparator } from '@/components/core/breadcrumbs-separator';
import { PostForm } from '@/components/dashboard/blog/post-form';
export const metadata = { title: `Create | Blog | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element {
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={8}>
<Stack spacing={1}>
<Typography variant="h4">Create a new post</Typography>
<Breadcrumbs separator={<BreadcrumbsSeparator />}>
<Link color="text.primary" component={RouterLink} href={paths.dashboard.overview} variant="subtitle2">
Dashboard
</Link>
<Link color="text.primary" component={RouterLink} href={paths.dashboard.blog.list} variant="subtitle2">
Blog
</Link>
<Typography color="text.secondary" variant="subtitle2">
Create
</Typography>
</Breadcrumbs>
</Stack>
<Card
sx={{
alignItems: 'center',
borderRadius: 1,
boxShadow: 'var(--mui-shadows-16)',
display: 'flex',
justifyContent: 'space-between',
px: 3,
py: 2,
}}
>
<Typography variant="subtitle1">Hello, Sofia</Typography>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', display: { xs: 'none', sm: 'flex' } }}>
<Button color="secondary" component={RouterLink} href={paths.dashboard.blog.list}>
Cancel
</Button>
<Button component={RouterLink} href={paths.dashboard.blog.details('1')} variant="contained">
Publish changes
</Button>
<IconButton>
<DotsThreeIcon weight="bold" />
</IconButton>
</Stack>
</Card>
<PostForm />
<Box sx={{ display: { sm: 'none' } }}>
<Button component={RouterLink} href={paths.dashboard.blog.details('1')} variant="contained">
Publish changes
</Button>
</Box>
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,141 @@
import * as React from 'react';
import type { Metadata } from 'next';
import RouterLink from 'next/link';
import Box from '@mui/material/Box';
import Breadcrumbs from '@mui/material/Breadcrumbs';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import Divider from '@mui/material/Divider';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { ArrowRight as ArrowRightIcon } from '@phosphor-icons/react/dist/ssr/ArrowRight';
import { config } from '@/config';
import { paths } from '@/paths';
import { dayjs } from '@/lib/dayjs';
import { BreadcrumbsSeparator } from '@/components/core/breadcrumbs-separator';
import { Newsletter } from '@/components/dashboard/blog/newsletter';
import { PostCard } from '@/components/dashboard/blog/post-card';
import type { Post } from '@/components/dashboard/blog/types';
export const metadata = { title: `List | Blog | Dashboard | ${config.site.name}` } satisfies Metadata;
const posts = [
{
id: 'POST-004',
title: 'Building a Design System from Scratch',
description: 'Learn how to build a design system from scratch using Figma and React that scales.',
cover: '/assets/image-business-2.png',
category: 'Programming',
author: { name: 'Iulia Albu', avatar: '/assets/avatar-6.png' },
readTime: '5 min',
publishedAt: dayjs().subtract(45, 'minute').toDate(),
},
{
id: 'POST-003',
title: 'Passive Income Ideas for Designers',
description: 'Explore the best passive income ideas for designers and how to get started.',
cover: '/assets/image-abstract-2.png',
category: 'Productivity',
author: { name: 'Omar Darobe', avatar: '/assets/avatar-11.png' },
readTime: '6 min',
publishedAt: dayjs().subtract(51, 'minute').subtract(6, 'hour').toDate(),
},
{
id: 'POST-002',
title: 'Five Ways to Improve Your Workspace',
description: 'Discover the five ways to improve your workspace and increase your productivity.',
cover: '/assets/image-minimal-2.png',
category: 'Entrepreneurs',
author: { name: 'Siegbert Gottfried', avatar: '/assets/avatar-2.png' },
readTime: '3 min',
publishedAt: dayjs().subtract(46, 'minute').subtract(16, 'hour').toDate(),
},
{
id: 'POST-001',
title: 'How to Create a Productivity Dashboard',
description: 'Learn how to create a productivity dashboard using Google Cloud and Supabase for your team.',
cover: '/assets/image-business-1.png',
category: 'Innovation',
author: { name: 'Jie Yan', avatar: '/assets/avatar-8.png' },
readTime: '1 min',
publishedAt: dayjs().subtract(39, 'minute').subtract(7, 'hour').subtract(5, 'day').toDate(),
},
] satisfies Post[];
export default function Page(): React.JSX.Element {
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={8}>
<Stack spacing={1}>
<Typography variant="h4">Posts</Typography>
<Breadcrumbs separator={<BreadcrumbsSeparator />}>
<Link color="text.primary" component={RouterLink} href={paths.dashboard.overview} variant="subtitle2">
Dashboard
</Link>
<Link color="text.primary" component={RouterLink} href={paths.dashboard.blog.list} variant="subtitle2">
Blog
</Link>
<Typography color="text.secondary" variant="subtitle2">
List
</Typography>
</Breadcrumbs>
</Stack>
<Card
sx={{
alignItems: 'center',
borderRadius: 1,
boxShadow: 'var(--mui-shadows-16)',
display: 'flex',
justifyContent: 'space-between',
px: 3,
py: 2,
}}
>
<Typography variant="subtitle1">Hello, Sofia</Typography>
<Button component={RouterLink} href={paths.dashboard.blog.create} variant="contained">
New post
</Button>
</Card>
<Stack spacing={4}>
<Stack spacing={2}>
<Typography variant="h4">Recent articles</Typography>
<div>
<Typography color="text.secondary" variant="body1">
Discover the latest news, tips and user research insights from Acme.
</Typography>
<Typography color="text.secondary" variant="body1">
You will learn about web infrastructure, design systems and devops APIs best practices.
</Typography>
</div>
</Stack>
<Divider />
<Grid container spacing={4}>
{posts.map((post) => (
<Grid key={post.title} md={6} xs={12}>
<PostCard post={post} />
</Grid>
))}
</Grid>
<Stack direction="row" spacing={1} sx={{ alignItems: 'center', justifyContent: 'center' }}>
<Button disabled startIcon={<ArrowLeftIcon />}>
Newer
</Button>
<Button endIcon={<ArrowRightIcon />}>Older</Button>
</Stack>
</Stack>
<Newsletter />
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,96 @@
import * as React from 'react';
import type { Metadata } from 'next';
import Box from '@mui/material/Box';
import { config } from '@/config';
import { dayjs } from '@/lib/dayjs';
import { CalendarProvider } from '@/components/dashboard/calendar/calendar-context';
import { CalendarView } from '@/components/dashboard/calendar/calendar-view';
import type { Event, ViewMode } from '@/components/dashboard/calendar/types';
export const metadata = { title: `Calendar | Dashboard | ${config.site.name}` } satisfies Metadata;
const events = [
{
id: 'EV-007',
title: 'Sign contract',
description: 'Discuss about the new partnership',
start: dayjs().subtract(6, 'day').set('hour', 17).set('minute', 30).toDate(),
end: dayjs().subtract(6, 'day').set('hour', 19).set('minute', 0).toDate(),
allDay: false,
},
{
id: 'EV-006',
title: 'Lunch meeting',
description: 'Meeting with the client',
start: dayjs().add(2, 'day').set('hour', 12).set('minute', 0).toDate(),
end: dayjs().add(2, 'day').set('hour', 15).set('minute', 30).toDate(),
allDay: false,
},
{
id: 'EV-005',
title: 'Scrum meeting',
description: 'Discuss about the new project',
start: dayjs().add(5, 'day').set('hour', 8).set('minute', 0).toDate(),
end: dayjs().add(5, 'day').set('hour', 12).set('minute', 0).toDate(),
allDay: false,
},
{
id: 'EV-004',
title: 'Meet the team',
description: 'Introduction to the new team members',
start: dayjs().subtract(11, 'day').startOf('day').toDate(),
end: dayjs().subtract(11, 'day').endOf('day').toDate(),
allDay: true,
},
{
id: 'EV-003',
title: 'Fire John',
description: 'Sorry, John!',
start: dayjs().add(3, 'day').set('hour', 7).set('minute', 30).toDate(),
end: dayjs().add(3, 'day').set('hour', 7).set('minute', 31).toDate(),
allDay: false,
priority: 'high',
},
{
id: 'EV-002',
title: 'Design meeting',
description: 'Plan the new design for the landing page',
start: dayjs().subtract(6, 'day').set('hour', 9).set('minute', 0).toDate(),
end: dayjs().subtract(6, 'day').set('hour', 9).set('minute', 30).toDate(),
allDay: false,
priority: 'medium',
},
{
id: 'EV-001',
title: 'HR meeting',
description: 'Discuss about the new open positions',
start: dayjs().set('hour', 15).set('minute', 30).toDate(),
end: dayjs().set('hour', 17).set('minute', 30).toDate(),
allDay: false,
priority: 'medium',
},
] satisfies Event[];
interface PageProps {
searchParams: { view?: ViewMode };
}
export default function Page({ searchParams }: PageProps): React.JSX.Element {
const { view = 'dayGridMonth' } = searchParams;
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<CalendarProvider events={events}>
<CalendarView view={view} />
</CalendarProvider>
</Box>
);
}

View File

@@ -0,0 +1,18 @@
import * as React from 'react';
import type { Metadata } from 'next';
import { config } from '@/config';
import { ThreadView } from '@/components/dashboard/chat/thread-view';
import type { ThreadType } from '@/components/dashboard/chat/types';
export const metadata = { title: `Thread | Chat | Dashboard | ${config.site.name}` } satisfies Metadata;
interface PageProps {
params: { threadId: string; threadType: ThreadType };
}
export default function Page({ params }: PageProps): React.JSX.Element {
const { threadId, threadType } = params;
return <ThreadView threadId={threadId} threadType={threadType} />;
}

View File

@@ -0,0 +1,11 @@
import * as React from 'react';
import type { Metadata } from 'next';
import { config } from '@/config';
import { ComposeView } from '@/components/dashboard/chat/compose-view';
export const metadata = { title: `Compose | Chat | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element {
return <ComposeView />;
}

View File

@@ -0,0 +1,211 @@
import * as React from 'react';
import { dayjs } from '@/lib/dayjs';
import { ChatProvider } from '@/components/dashboard/chat/chat-context';
import { ChatView } from '@/components/dashboard/chat/chat-view';
import type { Contact, Message, Thread } from '@/components/dashboard/chat/types';
const contacts = [
{
id: 'USR-010',
name: 'Alcides Antonio',
avatar: '/assets/avatar-10.png',
isActive: false,
lastActivity: dayjs().subtract(1, 'hour').toDate(),
},
{
id: 'USR-003',
name: 'Carson Darrin',
avatar: '/assets/avatar-3.png',
isActive: false,
lastActivity: dayjs().subtract(15, 'minute').toDate(),
},
{ id: 'USR-005', name: 'Fran Perez', avatar: '/assets/avatar-5.png', isActive: true, lastActivity: dayjs().toDate() },
{ id: 'USR-006', name: 'Iulia Albu', avatar: '/assets/avatar-6.png', isActive: true, lastActivity: dayjs().toDate() },
{ id: 'USR-008', name: 'Jie Yan', avatar: '/assets/avatar-8.png', isActive: true, lastActivity: dayjs().toDate() },
{
id: 'USR-009',
name: 'Marcus Finn',
avatar: '/assets/avatar-9.png',
isActive: false,
lastActivity: dayjs().subtract(2, 'hour').toDate(),
},
{
id: 'USR-001',
name: 'Miron Vitold',
avatar: '/assets/avatar-1.png',
isActive: true,
lastActivity: dayjs().toDate(),
},
{
id: 'USR-007',
name: 'Nasimiyu Danai',
avatar: '/assets/avatar-7.png',
isActive: true,
lastActivity: dayjs().toDate(),
},
{
id: 'USR-011',
name: 'Omar Darobe',
avatar: '/assets/avatar-11.png',
isActive: true,
lastActivity: dayjs().toDate(),
},
{
id: 'USR-004',
name: 'Penjani Inyene',
avatar: '/assets/avatar-4.png',
isActive: false,
lastActivity: dayjs().subtract(6, 'hour').toDate(),
},
{
id: 'USR-002',
name: 'Siegbert Gottfried',
avatar: '/assets/avatar-2.png',
isActive: true,
lastActivity: dayjs().toDate(),
},
] satisfies Contact[];
const threads = [
{
id: 'TRD-004',
type: 'direct',
participants: [
{ id: 'USR-000', name: 'Sofia Rivers', avatar: '/assets/avatar.png' },
{ id: 'USR-003', name: 'Carson Darrin', avatar: '/assets/avatar-3.png' },
],
unreadCount: 0,
},
{
id: 'TRD-003',
type: 'direct',
participants: [
{ id: 'USR-000', name: 'Sofia Rivers', avatar: '/assets/avatar.png' },
{ id: 'USR-005', name: 'Fran Perez', avatar: '/assets/avatar-5.png' },
],
unreadCount: 1,
},
{
id: 'TRD-002',
type: 'group',
participants: [
{ id: 'USR-000', name: 'Sofia Rivers', avatar: '/assets/avatar.png' },
{ id: 'USR-007', name: 'Nasimiyu Danai', avatar: '/assets/avatar-7.png' },
{ id: 'USR-001', name: 'Miron Vitold', avatar: '/assets/avatar-1.png' },
],
unreadCount: 0,
},
{
id: 'TRD-001',
type: 'direct',
participants: [
{ id: 'USR-000', name: 'Sofia Rivers', avatar: '/assets/avatar.png' },
{ id: 'USR-010', name: 'Alcides Antonio', avatar: '/assets/avatar-10.png' },
],
unreadCount: 2,
},
] satisfies Thread[];
const messages = [
{
id: 'MSG-011',
threadId: 'TRD-004',
type: 'text',
content: 'Hi, how are you?',
author: { id: 'USR-000', name: 'Sofia Rivers', avatar: '/assets/avatar.png' },
createdAt: dayjs().subtract(10, 'minute').toDate(),
},
{
id: 'MSG-010',
threadId: 'TRD-003',
type: 'text',
content: 'Are you available for a call?',
author: { id: 'USR-005', name: 'Fran Perez', avatar: '/assets/avatar-5.png' },
createdAt: dayjs().subtract(5, 'minute').subtract(1, 'hour').toDate(),
},
{
id: 'MSG-009',
threadId: 'TRD-002',
type: 'text',
content: 'Hello everyone 😀',
author: { id: 'USR-001', name: 'Miron Vitold', avatar: '/assets/avatar-1.png' },
createdAt: dayjs().subtract(56, 'minute').subtract(2, 'hour').toDate(),
},
{
id: 'MSG-008',
threadId: 'TRD-002',
type: 'text',
content: 'Hi!',
author: { id: 'USR-000', name: 'Sofia Rivers', avatar: '/assets/avatar.png' },
createdAt: dayjs().subtract(51, 'minute').subtract(3, 'hour').toDate(),
},
{
id: 'MSG-007',
threadId: 'TRD-002',
type: 'text',
content: 'Hey, would you like to collaborate?',
author: { id: 'USR-007', name: 'Nasimiyu Danai', avatar: '/assets/avatar-7.png' },
createdAt: dayjs().subtract(46, 'minute').subtract(5, 'hour').toDate(),
},
{
id: 'MSG-006',
threadId: 'TRD-001',
type: 'image',
content: '/assets/image-abstract-1.png',
author: { id: 'USR-010', name: 'Alcides Antonio', avatar: '/assets/avatar-10.png' },
createdAt: dayjs().subtract(1, 'hour').subtract(2, 'day').toDate(),
},
{
id: 'MSG-005',
threadId: 'TRD-001',
type: 'text',
content: 'Ok, I will think about it. Thanks!',
author: { id: 'USR-010', name: 'Alcides Antonio', avatar: '/assets/avatar-10.png' },
createdAt: dayjs().subtract(2, 'hour').subtract(2, 'day').toDate(),
},
{
id: 'MSG-004',
threadId: 'TRD-001',
type: 'text',
content: "I'm sorry, I can't go lower than $45.",
author: { id: 'USR-000', name: 'Sofia Rivers', avatar: '/assets/avatar.png' },
createdAt: dayjs().subtract(3, 'hour').subtract(3, 'day').toDate(),
},
{
id: 'MSG-003',
threadId: 'TRD-001',
type: 'text',
content: "Can't you make it $40? I'm on a tight budget.",
author: { id: 'USR-010', name: 'Alcides Antonio', avatar: '/assets/avatar-10.png' },
createdAt: dayjs().subtract(5, 'hour').subtract(3, 'day').toDate(),
},
{
id: 'MSG-002',
threadId: 'TRD-001',
type: 'text',
content: 'Sure, it is $50 per hour.',
author: { id: 'USR-000', name: 'Sofia Rivers', avatar: '/assets/avatar.png' },
createdAt: dayjs().subtract(2, 'hour').subtract(4, 'day').toDate(),
},
{
id: 'MSG-001',
threadId: 'TRD-001',
type: 'text',
content: "I'm interested in your services, can you tell me more about your hourly rate?",
author: { id: 'USR-010', name: 'Alcides Antonio', avatar: '/assets/avatar-10.png' },
createdAt: dayjs().subtract(5, 'hour').subtract(4, 'day').toDate(),
},
] satisfies Message[];
interface LayoutProps {
children: React.ReactNode;
}
export default function Layout({ children }: LayoutProps): React.JSX.Element {
return (
<ChatProvider contacts={contacts} messages={messages} threads={threads}>
<ChatView>{children}</ChatView>
</ChatProvider>
);
}

View File

@@ -0,0 +1,32 @@
import * as React from 'react';
import type { Metadata } from 'next';
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { config } from '@/config';
export const metadata = { title: `Chat | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element {
return (
<Box
sx={{
alignItems: 'center',
display: 'flex',
flex: '1 1 auto',
flexDirection: 'column',
justifyContent: 'center',
overflowY: 'auto',
p: 3,
}}
>
<Stack spacing={2} sx={{ alignItems: 'center' }}>
<Box component="img" src="/assets/not-found.svg" sx={{ height: 'auto', maxWidth: '100%', width: '120px' }} />
<Typography color="text.secondary" sx={{ textAlign: 'center' }} variant="subtitle1">
Start meaningful conversations!
</Typography>
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,151 @@
import * as React from 'react';
import type { Metadata } from 'next';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import { config } from '@/config';
import { dayjs } from '@/lib/dayjs';
import { AccountUpgrade } from '@/components/dashboard/crypto/account-upgrade';
import { CreditCard } from '@/components/dashboard/crypto/credit-card';
import { CurrencyConverter } from '@/components/dashboard/crypto/currency-converter';
import { CurrentBalance } from '@/components/dashboard/crypto/current-balance';
import { DigitalWallet } from '@/components/dashboard/crypto/digital-wallet';
import { Transactions } from '@/components/dashboard/crypto/transactions';
export const metadata = { title: `Crypto | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element {
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
<Box sx={{ flex: '1 1 auto' }}>
<Typography variant="h4">Crypto</Typography>
</Box>
<div>
<Button startIcon={<PlusIcon />} variant="contained">
Add wallet
</Button>
</div>
</Stack>
<Grid container spacing={4}>
<Grid xs={12}>
<Box
sx={{
display: 'grid',
gap: 3,
gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr', xl: '1fr 1fr 1fr 500px' },
}}
>
<DigitalWallet
amount={0.7568}
color="var(--mui-palette-primary-main)"
currency="BTC"
data={[
56, 61, 64, 60, 63, 61, 60, 68, 66, 64, 77, 60, 65, 51, 72, 80, 74, 67, 77, 83, 94, 95, 89, 100, 94,
104, 101, 105, 104, 103, 107, 120,
]}
diff={34.1}
trend="up"
value={16213.2}
/>
<DigitalWallet
amount={2.0435}
color="var(--mui-palette-primary-main)"
currency="ETH"
data={[
65, 64, 70, 76, 82, 80, 85, 78, 82, 95, 93, 80, 112, 102, 105, 95, 98, 102, 104, 99, 101, 100, 109,
106, 111, 105, 108, 102, 118, 129,
]}
diff={14.2}
trend="up"
value={9626.8}
/>
<DigitalWallet
amount={25.1602}
color="var(--mui-palette-primary-main)"
currency="BNB"
data={[
99, 101, 104, 98, 99, 99, 102, 103, 100, 101, 99, 101, 101, 98, 95, 97, 98, 92, 94, 93, 95, 82, 78,
75, 80, 78, 76, 54, 45, 32, 31, 27,
]}
diff={18}
trend="down"
value={6640}
/>
<CreditCard
card={{
id: 'CRD-001',
brand: 'mastercard',
cardNumber: '5823 4492 2385 1102',
expiryDate: '05/28',
holderName: 'Sofia Rivers',
}}
/>
</Box>
</Grid>
<Grid md={8} xs={12}>
<CurrentBalance
data={[
{ name: 'USD', value: 10076.81, color: 'var(--mui-palette-success-main)' },
{ name: 'BTC', value: 16213.2, color: 'var(--mui-palette-warning-main)' },
{ name: 'ETH', value: 9626.8, color: 'var(--mui-palette-primary-main)' },
]}
/>
</Grid>
<Grid md={4} xs={12}>
<CurrencyConverter />
</Grid>
<Grid md={8} xs={12}>
<Transactions
transactions={[
{
id: 'TX-003',
description: 'Buy BTC',
type: 'add',
balance: 643,
currency: 'BTC',
amount: 0.2105,
createdAt: dayjs().subtract(2, 'day').subtract(1, 'hour').subtract(32, 'minute').toDate(),
},
{
id: 'TX-002',
description: 'Buy BTC',
type: 'add',
balance: 2344,
currency: 'BTC',
amount: 0.1337,
createdAt: dayjs().subtract(3, 'day').subtract(1, 'hour').subtract(43, 'minute').toDate(),
},
{
id: 'TX-001',
description: 'Sell BTC',
type: 'sub',
balance: 4805,
currency: 'BTC',
amount: 0.2105,
createdAt: dayjs().subtract(6, 'day').subtract(1, 'hour').subtract(32, 'minute').toDate(),
},
]}
/>
</Grid>
<Grid md={4} xs={12}>
<AccountUpgrade />
</Grid>
</Grid>
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,308 @@
import * as React from 'react';
import type { Metadata } from 'next';
import RouterLink from 'next/link';
import Avatar from '@mui/material/Avatar';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import CardHeader from '@mui/material/CardHeader';
import Chip from '@mui/material/Chip';
import Divider from '@mui/material/Divider';
import IconButton from '@mui/material/IconButton';
import LinearProgress from '@mui/material/LinearProgress';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { CreditCard as CreditCardIcon } from '@phosphor-icons/react/dist/ssr/CreditCard';
import { House as HouseIcon } from '@phosphor-icons/react/dist/ssr/House';
import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple';
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import { ShieldWarning as ShieldWarningIcon } from '@phosphor-icons/react/dist/ssr/ShieldWarning';
import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User';
import { config } from '@/config';
import { paths } from '@/paths';
import { dayjs } from '@/lib/dayjs';
import { PropertyItem } from '@/components/core/property-item';
import { PropertyList } from '@/components/core/property-list';
import { Notifications } from '@/components/dashboard/customer/notifications';
import { Payments } from '@/components/dashboard/customer/payments';
import type { Address } from '@/components/dashboard/customer/shipping-address';
import { ShippingAddress } from '@/components/dashboard/customer/shipping-address';
export const metadata = { title: `Details | Customers | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element {
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Stack spacing={3}>
<div>
<Link
color="text.primary"
component={RouterLink}
href={paths.dashboard.customers.list}
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
variant="subtitle2"
>
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
Customers
</Link>
</div>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flex: '1 1 auto' }}>
<Avatar src="/assets/avatar-1.png" sx={{ '--Avatar-size': '64px' }}>
MV
</Avatar>
<div>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>
<Typography variant="h4">Miron Vitold</Typography>
<Chip
icon={<CheckCircleIcon color="var(--mui-palette-success-main)" weight="fill" />}
label="Active"
size="small"
variant="outlined"
/>
</Stack>
<Typography color="text.secondary" variant="body1">
miron.vitold@domain.com
</Typography>
</div>
</Stack>
<div>
<Button endIcon={<CaretDownIcon />} variant="contained">
Action
</Button>
</div>
</Stack>
</Stack>
<Grid container spacing={4}>
<Grid lg={4} xs={12}>
<Stack spacing={4}>
<Card>
<CardHeader
action={
<IconButton>
<PencilSimpleIcon />
</IconButton>
}
avatar={
<Avatar>
<UserIcon fontSize="var(--Icon-fontSize)" />
</Avatar>
}
title="Basic details"
/>
<PropertyList
divider={<Divider />}
orientation="vertical"
sx={{ '--PropertyItem-padding': '12px 24px' }}
>
{(
[
{ key: 'Customer ID', value: <Chip label="USR-001" size="small" variant="soft" /> },
{ key: 'Name', value: 'Miron Vitold' },
{ key: 'Email', value: 'miron.vitold@domain.com' },
{ key: 'Phone', value: '(425) 434-5535' },
{ key: 'Company', value: 'Devias IO' },
{
key: 'Quota',
value: (
<Stack direction="row" spacing={2} sx={{ alignItems: 'center' }}>
<LinearProgress sx={{ flex: '1 1 auto' }} value={50} variant="determinate" />
<Typography color="text.secondary" variant="body2">
50%
</Typography>
</Stack>
),
},
] satisfies { key: string; value: React.ReactNode }[]
).map(
(item): React.JSX.Element => (
<PropertyItem key={item.key} name={item.key} value={item.value} />
)
)}
</PropertyList>
</Card>
<Card>
<CardHeader
avatar={
<Avatar>
<ShieldWarningIcon fontSize="var(--Icon-fontSize)" />
</Avatar>
}
title="Security"
/>
<CardContent>
<Stack spacing={1}>
<div>
<Button color="error" variant="contained">
Delete account
</Button>
</div>
<Typography color="text.secondary" variant="body2">
A deleted customer cannot be restored. All data will be permanently removed.
</Typography>
</Stack>
</CardContent>
</Card>
</Stack>
</Grid>
<Grid lg={8} xs={12}>
<Stack spacing={4}>
<Payments
ordersValue={2069.48}
payments={[
{
currency: 'USD',
amount: 500,
invoiceId: 'INV-005',
status: 'completed',
createdAt: dayjs().subtract(5, 'minute').subtract(1, 'hour').toDate(),
},
{
currency: 'USD',
amount: 324.5,
invoiceId: 'INV-004',
status: 'refunded',
createdAt: dayjs().subtract(21, 'minute').subtract(2, 'hour').toDate(),
},
{
currency: 'USD',
amount: 746.5,
invoiceId: 'INV-003',
status: 'completed',
createdAt: dayjs().subtract(7, 'minute').subtract(3, 'hour').toDate(),
},
{
currency: 'USD',
amount: 56.89,
invoiceId: 'INV-002',
status: 'completed',
createdAt: dayjs().subtract(48, 'minute').subtract(4, 'hour').toDate(),
},
{
currency: 'USD',
amount: 541.59,
invoiceId: 'INV-001',
status: 'completed',
createdAt: dayjs().subtract(31, 'minute').subtract(5, 'hour').toDate(),
},
]}
refundsValue={324.5}
totalOrders={5}
/>
<Card>
<CardHeader
action={
<Button color="secondary" startIcon={<PencilSimpleIcon />}>
Edit
</Button>
}
avatar={
<Avatar>
<CreditCardIcon fontSize="var(--Icon-fontSize)" />
</Avatar>
}
title="Billing details"
/>
<CardContent>
<Card sx={{ borderRadius: 1 }} variant="outlined">
<PropertyList divider={<Divider />} sx={{ '--PropertyItem-padding': '16px' }}>
{(
[
{ key: 'Credit card', value: '**** 4142' },
{ key: 'Country', value: 'United States' },
{ key: 'State', value: 'Michigan' },
{ key: 'City', value: 'Southfield' },
{ key: 'Address', value: '1721 Bartlett Avenue, 48034' },
{ key: 'Tax ID', value: 'EU87956621' },
] satisfies { key: string; value: React.ReactNode }[]
).map(
(item): React.JSX.Element => (
<PropertyItem key={item.key} name={item.key} value={item.value} />
)
)}
</PropertyList>
</Card>
</CardContent>
</Card>
<Card>
<CardHeader
action={
<Button color="secondary" startIcon={<PlusIcon />}>
Add
</Button>
}
avatar={
<Avatar>
<HouseIcon fontSize="var(--Icon-fontSize)" />
</Avatar>
}
title="Shipping addresses"
/>
<CardContent>
<Grid container spacing={3}>
{(
[
{
id: 'ADR-001',
country: 'United States',
state: 'Michigan',
city: 'Lansing',
zipCode: '48933',
street: '480 Haven Lane',
primary: true,
},
{
id: 'ADR-002',
country: 'United States',
state: 'Missouri',
city: 'Springfield',
zipCode: '65804',
street: '4807 Lighthouse Drive',
},
] satisfies Address[]
).map((address) => (
<Grid key={address.id} md={6} xs={12}>
<ShippingAddress address={address} />
</Grid>
))}
</Grid>
</CardContent>
</Card>
<Notifications
notifications={[
{
id: 'EV-002',
type: 'Refund request approved',
status: 'pending',
createdAt: dayjs().subtract(34, 'minute').subtract(5, 'hour').subtract(3, 'day').toDate(),
},
{
id: 'EV-001',
type: 'Order confirmation',
status: 'delivered',
createdAt: dayjs().subtract(49, 'minute').subtract(11, 'hour').subtract(4, 'day').toDate(),
},
]}
/>
</Stack>
</Grid>
</Grid>
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,48 @@
import * as React from 'react';
import type { Metadata } from 'next';
import RouterLink from 'next/link';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { config } from '@/config';
import { paths } from '@/paths';
import { CustomerCreateForm } from '@/components/dashboard/customer/customer-create-form';
export const metadata = { title: `Create | Customers | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element {
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Stack spacing={3}>
<div>
<Link
color="text.primary"
component={RouterLink}
href={paths.dashboard.customers.list}
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
variant="subtitle2"
>
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
Customers
</Link>
</div>
<div>
<Typography variant="h4">Create customer</Typography>
</div>
</Stack>
<CustomerCreateForm />
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,155 @@
import * as React from 'react';
import type { Metadata } from 'next';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import Divider from '@mui/material/Divider';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import { config } from '@/config';
import { dayjs } from '@/lib/dayjs';
import { CustomersFilters } from '@/components/dashboard/customer/customers-filters';
import type { Filters } from '@/components/dashboard/customer/customers-filters';
import { CustomersPagination } from '@/components/dashboard/customer/customers-pagination';
import { CustomersSelectionProvider } from '@/components/dashboard/customer/customers-selection-context';
import { CustomersTable } from '@/components/dashboard/customer/customers-table';
import type { Customer } from '@/components/dashboard/customer/customers-table';
export const metadata = { title: `List | Customers | Dashboard | ${config.site.name}` } satisfies Metadata;
const customers = [
{
id: 'USR-005',
name: 'Fran Perez',
avatar: '/assets/avatar-5.png',
email: 'fran.perez@domain.com',
phone: '(815) 704-0045',
quota: 50,
status: 'active',
createdAt: dayjs().subtract(1, 'hour').toDate(),
},
{
id: 'USR-004',
name: 'Penjani Inyene',
avatar: '/assets/avatar-4.png',
email: 'penjani.inyene@domain.com',
phone: '(803) 937-8925',
quota: 100,
status: 'active',
createdAt: dayjs().subtract(3, 'hour').toDate(),
},
{
id: 'USR-003',
name: 'Carson Darrin',
avatar: '/assets/avatar-3.png',
email: 'carson.darrin@domain.com',
phone: '(715) 278-5041',
quota: 10,
status: 'blocked',
createdAt: dayjs().subtract(1, 'hour').subtract(1, 'day').toDate(),
},
{
id: 'USR-002',
name: 'Siegbert Gottfried',
avatar: '/assets/avatar-2.png',
email: 'siegbert.gottfried@domain.com',
phone: '(603) 766-0431',
quota: 0,
status: 'pending',
createdAt: dayjs().subtract(7, 'hour').subtract(1, 'day').toDate(),
},
{
id: 'USR-001',
name: 'Miron Vitold',
avatar: '/assets/avatar-1.png',
email: 'miron.vitold@domain.com',
phone: '(425) 434-5535',
quota: 50,
status: 'active',
createdAt: dayjs().subtract(2, 'hour').subtract(2, 'day').toDate(),
},
] satisfies Customer[];
interface PageProps {
searchParams: { email?: string; phone?: string; sortDir?: 'asc' | 'desc'; status?: string };
}
export default function Page({ searchParams }: PageProps): React.JSX.Element {
const { email, phone, sortDir, status } = searchParams;
const sortedCustomers = applySort(customers, sortDir);
const filteredCustomers = applyFilters(sortedCustomers, { email, phone, status });
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
<Box sx={{ flex: '1 1 auto' }}>
<Typography variant="h4">Customers</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button startIcon={<PlusIcon />} variant="contained">
Add
</Button>
</Box>
</Stack>
<CustomersSelectionProvider customers={filteredCustomers}>
<Card>
<CustomersFilters filters={{ email, phone, status }} sortDir={sortDir} />
<Divider />
<Box sx={{ overflowX: 'auto' }}>
<CustomersTable rows={filteredCustomers} />
</Box>
<Divider />
<CustomersPagination count={filteredCustomers.length + 100} page={0} />
</Card>
</CustomersSelectionProvider>
</Stack>
</Box>
);
}
// Sorting and filtering has to be done on the server.
function applySort(row: Customer[], sortDir: 'asc' | 'desc' | undefined): Customer[] {
return row.sort((a, b) => {
if (sortDir === 'asc') {
return a.createdAt.getTime() - b.createdAt.getTime();
}
return b.createdAt.getTime() - a.createdAt.getTime();
});
}
function applyFilters(row: Customer[], { email, phone, status }: Filters): Customer[] {
return row.filter((item) => {
if (email) {
if (!item.email?.toLowerCase().includes(email.toLowerCase())) {
return false;
}
}
if (phone) {
if (!item.phone?.toLowerCase().includes(phone.toLowerCase())) {
return false;
}
}
if (status) {
if (item.status !== status) {
return false;
}
}
return true;
});
}

View File

@@ -0,0 +1,157 @@
import * as React from 'react';
import type { Metadata } from 'next';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import { config } from '@/config';
import { Conversions } from '@/components/dashboard/e-commerce/conversions';
import { CostBreakdown } from '@/components/dashboard/e-commerce/cost-breakdown';
import { SalesByCountry } from '@/components/dashboard/e-commerce/sales-by-country';
import { Stats } from '@/components/dashboard/e-commerce/stats';
import { TopProducts } from '@/components/dashboard/e-commerce/top-products';
export const metadata = { title: `E-commerce | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element {
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
<Box sx={{ flex: '1 1 auto' }}>
<Typography variant="h4">E-commerce</Typography>
</Box>
<div>
<Button startIcon={<PlusIcon />} variant="contained">
Add product
</Button>
</div>
</Stack>
<Grid container spacing={4}>
<Grid xs={12}>
<Stats
data={[
{ name: 'Jan 1', v1: 35, v2: 3350 },
{ name: 'Jan 2', v1: 41, v2: 3440 },
{ name: 'Jan 2', v1: 32, v2: 3054 },
{ name: 'Jan 3', v1: 34, v2: 3780 },
{ name: 'Jan 4', v1: 53, v2: 3849 },
{ name: 'Jan 5', v1: 48, v2: 4241 },
{ name: 'Jan 6', v1: 59, v2: 4170 },
{ name: 'Jan 7', v1: 57, v2: 4051 },
{ name: 'Jan 8', v1: 66, v2: 4364 },
{ name: 'Jan 9', v1: 71, v2: 4385 },
{ name: 'Jan 10', v1: 72, v2: 4912 },
{ name: 'Jan 11', v1: 78, v2: 4623 },
{ name: 'Jan 12', v1: 71, v2: 4673 },
{ name: 'Jan 13', v1: 63, v2: 4465 },
{ name: 'Jan 14', v1: 66, v2: 4221 },
{ name: 'Jan 15', v1: 80, v2: 5237 },
{ name: 'Jan 16', v1: 78, v2: 5303 },
{ name: 'Jan 17', v1: 95, v2: 5701 },
{ name: 'Jan 18', v1: 92, v2: 5725 },
{ name: 'Jan 19', v1: 87, v2: 5856 },
{ name: 'Jan 20', v1: 98, v2: 6401 },
{ name: 'Jan 21', v1: 101, v2: 6733 },
{ name: 'Jan 22', v1: 97, v2: 6640 },
{ name: 'Jan 23', v1: 97, v2: 6576 },
{ name: 'Jan 24', v1: 104, v2: 7300 },
{ name: 'Jan 25', v1: 105, v2: 7285 },
{ name: 'Jan 26', v1: 106, v2: 7389 },
{ name: 'Jan 27', v1: 112, v2: 7705 },
{ name: 'Jan 28', v1: 115, v2: 8212 },
{ name: 'Jan 29', v1: 110, v2: 8301 },
{ name: 'Jan 30', v1: 115, v2: 8531 },
{ name: 'Jan 31', v1: 118, v2: 8700 },
]}
/>
</Grid>
<Grid lg={8} xs={12}>
<Conversions
data={[
{ name: 'Direct calls', value: 35690 },
{ name: 'Quote requests', value: 14859 },
{ name: 'Ads', value: 45120 },
{ name: 'Affiliate links', value: 3950 },
{ name: 'Email campaigns', value: 12011 },
{ name: 'Other', value: 5486 },
]}
/>
</Grid>
<Grid lg={4} xs={12}>
<CostBreakdown
data={[
{ name: 'Strategy', value: 14859, color: 'var(--mui-palette-success-main)' },
{ name: 'Outsourcing', value: 35690, color: 'var(--mui-palette-warning-main)' },
{ name: 'Marketing', value: 45120, color: 'var(--mui-palette-primary-main)' },
{ name: 'Other', value: 25486, color: 'var(--mui-palette-background-level2)' },
]}
/>
</Grid>
<Grid lg={8} xs={12}>
<SalesByCountry
sales={[
{ countryCode: 'us', countryName: 'United States', value: 60 },
{ countryCode: 'es', countryName: 'Spain', value: 20 },
{ countryCode: 'uk', countryName: 'United Kingdom', value: 10 },
{ countryCode: 'de', countryName: 'Germany', value: 5 },
{ countryCode: 'ca', countryName: 'Canada', value: 5 },
]}
/>
</Grid>
<Grid lg={4} xs={12}>
<TopProducts
products={[
{
id: 'PRD-001',
name: 'Erbology Aloe Vera',
image: '/assets/product-1.png',
category: 'Healthcare',
sales: 13153,
},
{
id: 'PRD-002',
name: 'Lancome Rouge',
image: '/assets/product-2.png',
category: 'Makeup',
sales: 10300,
},
{
id: 'PRD-003',
name: 'Ritual of Sakura',
image: '/assets/product-3.png',
category: 'Skincare',
sales: 5300,
},
{
id: 'PRD-004',
name: 'Necessaire Body Lotion',
image: '/assets/product-4.png',
category: 'Skincare',
sales: 1203,
},
{
id: 'PRD-005',
name: 'Soja & Co. Eucalyptus',
image: '/assets/product-5.png',
category: 'Skincare',
sales: 254,
},
]}
/>
</Grid>
</Grid>
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,228 @@
import * as React from 'react';
import type { Metadata } from 'next';
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import { config } from '@/config';
import { dayjs } from '@/lib/dayjs';
import { ItemsFilters } from '@/components/dashboard/file-storage/items-filters';
import type { Filters } from '@/components/dashboard/file-storage/items-filters';
import { ItemsPagination } from '@/components/dashboard/file-storage/items-pagination';
import { Stats } from '@/components/dashboard/file-storage/stats';
import { StorageProvider } from '@/components/dashboard/file-storage/storage-context';
import { StorageView } from '@/components/dashboard/file-storage/storage-view';
import type { Item } from '@/components/dashboard/file-storage/types';
import { UplaodButton } from '@/components/dashboard/file-storage/upload-button';
export const metadata = { title: `File storage | Dashboard | ${config.site.name}` } satisfies Metadata;
const items = [
{
id: 'FILE-009',
type: 'folder',
name: 'AWS Credentials',
author: { name: 'Sofia Rivers', avatar: '/assets/avatar.png' },
isFavorite: true,
isPublic: false,
tags: ['Secrets', 'Important'],
shared: [
{ name: 'Miron Vitold', avatar: '/assets/avatar-1.png' },
{ name: 'Alcides Antonio', avatar: '/assets/avatar-10.png' },
{ name: 'Nasimiyu Danai', avatar: '/assets/avatar-7.png' },
],
itemsCount: 3,
size: '5 MB',
createdAt: dayjs().subtract(3, 'minute').subtract(4, 'hour').toDate(),
updatedAt: dayjs().subtract(3, 'minute').subtract(4, 'hour').toDate(),
},
{
id: 'FILE-008',
type: 'folder',
name: 'invoices',
author: { name: 'Sofia Rivers', avatar: '/assets/avatar.png' },
isFavorite: false,
isPublic: false,
tags: ['Work', 'Accounting'],
shared: [],
itemsCount: 71,
size: '1.2 GB',
createdAt: dayjs().subtract(16, 'minute').subtract(20, 'hour').toDate(),
updatedAt: dayjs().subtract(16, 'minute').subtract(20, 'hour').toDate(),
},
{
id: 'FILE-007',
type: 'folder',
name: 'assets',
author: { name: 'Carson Darrin', avatar: '/assets/avatar-3.png' },
isFavorite: false,
isPublic: true,
tags: [],
shared: [],
itemsCount: 12,
size: '325.2 MB',
createdAt: dayjs().subtract(23, 'minute').subtract(26, 'hour').toDate(),
updatedAt: dayjs().subtract(23, 'minute').subtract(26, 'hour').toDate(),
},
{
id: 'FILE-006',
type: 'file',
name: 'company-logo.svg',
extension: 'svg',
author: { name: 'Siegbert Gottfried', avatar: '/assets/avatar-2.png' },
isFavorite: false,
isPublic: false,
tags: ['Work'],
shared: [
{ name: 'Sofia Rivers', avatar: '/assets/avatar.png' },
{ name: 'Nasimiyu Danai', avatar: '/assets/avatar-7.png' },
],
size: '1.2 MB',
createdAt: dayjs().subtract(41, 'minute').subtract(6, 'hour').subtract(2, 'day').toDate(),
updatedAt: dayjs().subtract(41, 'minute').subtract(6, 'hour').subtract(2, 'day').toDate(),
},
{
id: 'FILE-005',
type: 'file',
name: 'devias-kit.fig',
extension: 'pkg',
author: { name: 'Sofia Rivers', avatar: '/assets/avatar.png' },
isFavorite: true,
isPublic: false,
tags: ['Personal'],
shared: [{ name: 'Omar Darobe', avatar: '/assets/avatar-11.png' }],
size: '16.7 MB',
createdAt: dayjs().subtract(11, 'minute').subtract(16, 'hour').subtract(18, 'day').toDate(),
updatedAt: dayjs().subtract(11, 'minute').subtract(16, 'hour').subtract(18, 'day').toDate(),
},
{
id: 'FILE-004',
type: 'file',
name: 'not_a_virus.exe',
extension: 'exe',
author: { name: 'Sofia Rivers', avatar: '/assets/avatar.png' },
isFavorite: false,
isPublic: false,
tags: ['Security'],
shared: [{ name: 'Siegbert Gottfried', avatar: '/assets/avatar-2.png' }],
size: '13.37 KB',
createdAt: dayjs().subtract(1, 'minute').subtract(3, 'hour').subtract(37, 'day').toDate(),
updatedAt: dayjs().subtract(1, 'minute').subtract(3, 'hour').subtract(37, 'day').toDate(),
},
{
id: 'FILE-003',
type: 'file',
name: 'website-hero-illustration.psd',
extension: 'psd',
author: { name: 'Sofia Rivers', avatar: '/assets/avatar.png' },
isFavorite: false,
isPublic: false,
tags: ['Work', 'Design'],
shared: [{ name: 'Siegbert Gottfried', avatar: '/assets/avatar-2.png' }],
size: '2.3 MB',
createdAt: dayjs().subtract(12, 'minute').subtract(11, 'hour').subtract(40, 'day').toDate(),
updatedAt: dayjs().subtract(12, 'minute').subtract(11, 'hour').subtract(40, 'day').toDate(),
},
{
id: 'FILE-002',
type: 'file',
name: 'ssl-certs.tar',
extension: 'tar',
author: { name: 'Sofia Rivers', avatar: '/assets/avatar.png' },
isFavorite: false,
isPublic: false,
tags: ['Work', 'Security'],
shared: [{ name: 'Omar Darobe', avatar: '/assets/avatar-11.png' }],
size: '684.1 KB',
createdAt: dayjs().subtract(15, 'minute').subtract(23, 'hour').subtract(41, 'day').toDate(),
updatedAt: dayjs().subtract(15, 'minute').subtract(23, 'hour').subtract(41, 'day').toDate(),
},
{
id: 'FILE-001',
type: 'file',
name: 'tablet-driver-adapter.deb',
extension: 'deb',
author: { name: 'Sofia Rivers', avatar: '/assets/avatar.png' },
isFavorite: false,
isPublic: false,
tags: [],
shared: [],
size: '5.2 MB',
createdAt: dayjs().subtract(40, 'minute').subtract(11, 'hour').subtract(62, 'day').toDate(),
updatedAt: dayjs().subtract(40, 'minute').subtract(11, 'hour').subtract(62, 'day').toDate(),
},
] satisfies Item[];
interface PageProps {
searchParams: { query?: string; sortDir?: 'asc' | 'desc'; view?: 'grid' | 'list' };
}
export default function Page({ searchParams }: PageProps): React.JSX.Element {
const { query, sortDir, view = 'grid' } = searchParams;
const filters = { query };
const sortedItems = applySort(items, sortDir);
const filteredItems = applyFilters(sortedItems, filters);
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
<Box sx={{ flex: '1 1 auto' }}>
<Typography variant="h4">File storage</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
<UplaodButton />
</Box>
</Stack>
<Grid container spacing={4}>
<Grid md={8} xs={12}>
<Stack spacing={4}>
<ItemsFilters filters={filters} sortDir={sortDir} view={view} />
<StorageProvider items={filteredItems}>
<StorageView view={view} />
</StorageProvider>
<ItemsPagination count={filteredItems.length} page={0} />
</Stack>
</Grid>
<Grid md={4} xs={12}>
<Stats />
</Grid>
</Grid>
</Stack>
</Box>
);
}
// Sorting and filtering has to be done on the server.
function applySort(row: Item[], sortDir: 'asc' | 'desc' | undefined): Item[] {
return row.sort((a, b) => {
if (sortDir === 'asc') {
return a.createdAt.getTime() - b.createdAt.getTime();
}
return b.createdAt.getTime() - a.createdAt.getTime();
});
}
function applyFilters(row: Item[], { query }: Filters): Item[] {
return row.filter((item) => {
if (query) {
if (!item.name?.toLowerCase().includes(query.toLowerCase())) {
return false;
}
}
return true;
});
}

View File

@@ -0,0 +1,202 @@
import * as React from 'react';
import type { Metadata } from 'next';
import RouterLink from 'next/link';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import Chip from '@mui/material/Chip';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { config } from '@/config';
import { paths } from '@/paths';
import { dayjs } from '@/lib/dayjs';
import { DynamicLogo } from '@/components/core/logo';
import { InvoicePDFLink } from '@/components/dashboard/invoice/invoice-pdf-link';
import { LineItemsTable } from '@/components/dashboard/invoice/line-items-table';
import type { LineItem } from '@/components/dashboard/invoice/line-items-table';
export const metadata = { title: `Details | Invoices | Dashboard | ${config.site.name}` } satisfies Metadata;
const lineItems = [
{ id: 'LI-001', name: 'Pro Subscription', quantity: 1, currency: 'USD', unitAmount: 14.99, totalAmount: 14.99 },
] satisfies LineItem[];
export default function Page(): React.JSX.Element {
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Stack spacing={3}>
<div>
<Link
color="text.primary"
component={RouterLink}
href={paths.dashboard.invoices.list}
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
variant="subtitle2"
>
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
Invoices
</Link>
</div>
<Stack direction="row" spacing={3} sx={{ alignItems: 'flex-start', justifyContent: 'space-between' }}>
<Stack spacing={1}>
<Typography variant="h4">INV-001</Typography>
<div>
<Chip color="warning" label="Pending" variant="soft" />
</div>
</Stack>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center' }}>
<InvoicePDFLink invoice={undefined}>
<Button color="secondary">Download</Button>
</InvoicePDFLink>
<Button component="a" href={paths.pdf.invoice('1')} target="_blank" variant="contained">
Preview
</Button>
</Stack>
</Stack>
</Stack>
<Card sx={{ p: 6 }}>
<Stack spacing={6}>
<Stack direction="row" spacing={3} sx={{ alignItems: 'flex-start' }}>
<Box sx={{ flex: '1 1 auto' }}>
<Typography variant="h4">Invoice</Typography>
</Box>
<Box sx={{ flex: '0 0 auto' }}>
<DynamicLogo colorDark="light" colorLight="dark" emblem height={60} width={60} />
</Box>
</Stack>
<Stack spacing={1}>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center' }}>
<Box sx={{ flex: '0 1 150px' }}>
<Typography variant="subtitle2">Number:</Typography>
</Box>
<div>
<Typography variant="body2">INV-008</Typography>
</div>
</Stack>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center' }}>
<Box sx={{ flex: '0 1 150px' }}>
<Typography variant="subtitle2">Due date:</Typography>
</Box>
<div>
<Typography variant="body2">{dayjs().add(15, 'day').format('MMM D,YYYY')}</Typography>
</div>
</Stack>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center' }}>
<Box sx={{ flex: '0 1 150px' }}>
<Typography variant="subtitle2">Issue date:</Typography>
</Box>
<div>
<Typography variant="body2">{dayjs().subtract(1, 'hour').format('MMM D, YYYY')}</Typography>
</div>
</Stack>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center' }}>
<Box sx={{ flex: '0 1 150px' }}>
<Typography variant="subtitle2">Issuer VAT No:</Typography>
</Box>
<Typography variant="body2">RO4675933</Typography>
</Stack>
</Stack>
<Grid container spacing={3}>
<Grid md={6} xs={12}>
<Stack spacing={1}>
<Typography variant="subtitle1">Devias IO</Typography>
<Typography variant="body2">
2674 Alfred Drive
<br />
Brooklyn, New York, United States
<br />
11206
<br />
accounts@devias.io
<br />
(+1) 757 737 1980
</Typography>
</Stack>
</Grid>
<Grid md={6} xs={12}>
<Stack spacing={1}>
<Typography variant="subtitle1">Billed to</Typography>
<Typography variant="body2">
Miron Vitold
<br />
Acme Inc.
<br />
1721 Bartlett Avenue
<br />
Southfield, Michigan, United States
<br />
48034
<br />
RO8795621
</Typography>
</Stack>
</Grid>
</Grid>
<div>
<Typography variant="h5">
{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(19.99)} due{' '}
{dayjs().add(15, 'day').format('MMM D, YYYY')}
</Typography>
</div>
<Stack spacing={2}>
<Card sx={{ borderRadius: 1, overflowX: 'auto' }} variant="outlined">
<LineItemsTable rows={lineItems} />
</Card>
<Stack spacing={2}>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', justifyContent: 'flex-end' }}>
<Box sx={{ flex: '0 1 150px' }}>
<Typography>Subtotal</Typography>
</Box>
<Box sx={{ flex: '0 1 100px', textAlign: 'right' }}>
<Typography>
{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(14.99)}
</Typography>
</Box>
</Stack>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', justifyContent: 'flex-end' }}>
<Box sx={{ flex: '0 1 150px' }}>
<Typography>Tax</Typography>
</Box>
<Box sx={{ flex: '0 1 100px', textAlign: 'right' }}>
<Typography>
{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(5)}
</Typography>
</Box>
</Stack>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', justifyContent: 'flex-end' }}>
<Box sx={{ flex: '0 1 150px' }}>
<Typography variant="h6">Total</Typography>
</Box>
<Box sx={{ flex: '0 1 100px', textAlign: 'right' }}>
<Typography variant="h6">
{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(19.99)}
</Typography>
</Box>
</Stack>
</Stack>
</Stack>
<Stack spacing={1}>
<Typography variant="h6">Notes</Typography>
<Typography color="text.secondary" variant="body2">
Please make sure you have the right bank registration number as I had issues before and make sure you
cover transfer expenses.
</Typography>
</Stack>
</Stack>
</Card>
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,48 @@
import * as React from 'react';
import type { Metadata } from 'next';
import RouterLink from 'next/link';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { config } from '@/config';
import { paths } from '@/paths';
import { InvoiceCreateForm } from '@/components/dashboard/invoice/invoice-create-form';
export const metadata = { title: `Create | Invoices | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element {
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Stack spacing={3}>
<div>
<Link
color="text.primary"
component={RouterLink}
href={paths.dashboard.invoices.list}
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
variant="subtitle2"
>
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
Invoices
</Link>
</div>
<div>
<Typography variant="h4">Create invoice</Typography>
</div>
</Stack>
<InvoiceCreateForm />
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,175 @@
import * as React from 'react';
import type { Metadata } from 'next';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import { config } from '@/config';
import { dayjs } from '@/lib/dayjs';
import { InvoicesFiltersCard } from '@/components/dashboard/invoice//invoices-filters-card';
import { InvoicesPagination } from '@/components/dashboard/invoice//invoices-pagination';
import { InvoicesStats } from '@/components/dashboard/invoice//invoices-stats';
import { InvoicesTable } from '@/components/dashboard/invoice//invoices-table';
import { ViewModeButton } from '@/components/dashboard/invoice//view-mode-button';
import type { Filters } from '@/components/dashboard/invoice/invoices-filters';
import { InvoicesFiltersButton } from '@/components/dashboard/invoice/invoices-filters-button';
import { InvoicesSort } from '@/components/dashboard/invoice/invoices-sort';
import type { Invoice } from '@/components/dashboard/invoice/invoices-table';
export const metadata = { title: `List | Invoices | Dashboard | ${config.site.name}` } satisfies Metadata;
const invoices = [
{
id: 'INV-005',
customer: { name: 'Jie Yan', avatar: '/assets/avatar-8.png' },
currency: 'USD',
totalAmount: 23.11,
status: 'pending',
issueDate: dayjs().subtract(1, 'hour').toDate(),
dueDate: dayjs().add(25, 'day').toDate(),
},
{
id: 'INV-004',
customer: { name: 'Omar Darobe', avatar: '/assets/avatar-11.png' },
currency: 'USD',
totalAmount: 253.76,
status: 'paid',
issueDate: dayjs().subtract(2, 'hour').subtract(4, 'day').toDate(),
dueDate: dayjs().add(17, 'day').toDate(),
},
{
id: 'INV-003',
customer: { name: 'Carson Darrin', avatar: '/assets/avatar-3.png' },
currency: 'USD',
totalAmount: 781.5,
status: 'canceled',
issueDate: dayjs().subtract(4, 'hour').subtract(6, 'day').toDate(),
dueDate: dayjs().add(11, 'day').toDate(),
},
{
id: 'INV-002',
customer: { name: 'Fran Perez', avatar: '/assets/avatar-5.png' },
currency: 'USD',
totalAmount: 96.64,
status: 'paid',
issueDate: dayjs().subtract(2, 'hour').subtract(15, 'day').toDate(),
dueDate: dayjs().add(3, 'day').toDate(),
},
{
id: 'INV-001',
customer: { name: 'Miron Vitold', avatar: '/assets/avatar-1.png' },
currency: 'USD',
totalAmount: 19.99,
status: 'pending',
issueDate: dayjs().subtract(2, 'hour').subtract(15, 'day').toDate(),
dueDate: dayjs().add(1, 'day').toDate(),
},
] satisfies Invoice[];
interface PageProps {
searchParams: {
customer?: string;
endDate?: string;
id?: string;
sortDir?: 'asc' | 'desc';
startDate?: string;
status?: string;
view?: 'group' | 'list';
};
}
export default function Page({ searchParams }: PageProps): React.JSX.Element {
const { customer, endDate, id, sortDir, startDate, status, view = 'group' } = searchParams;
const filters = { customer, endDate, id, startDate, status };
const sortedInvoices = applySort(invoices, sortDir);
const filteredInvoices = applyFilters(sortedInvoices, filters);
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
<Box sx={{ flex: '1 1 auto' }}>
<Typography variant="h4">Invoices</Typography>
</Box>
<div>
<Button startIcon={<PlusIcon />} variant="contained">
New
</Button>
</div>
</Stack>
<InvoicesStats />
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', justifyContent: 'flex-end' }}>
<InvoicesFiltersButton filters={filters} sortDir={sortDir} view={view} />
<InvoicesSort filters={filters} sortDir={sortDir} view={view} />
<ViewModeButton view={view} />
</Stack>
<Stack direction="row" spacing={4} sx={{ alignItems: 'flex-start' }}>
<InvoicesFiltersCard filters={filters} sortDir={sortDir} view={view} />
<Stack spacing={4} sx={{ flex: '1 1 auto', minWidth: 0 }}>
<InvoicesTable rows={filteredInvoices} view={view} />
<InvoicesPagination count={filteredInvoices.length} page={0} />
</Stack>
</Stack>
</Stack>
</Box>
);
}
// Sorting and filtering has to be done on the server.
function applySort(row: Invoice[], sortDir: 'asc' | 'desc' | undefined): Invoice[] {
return row.sort((a, b) => {
if (sortDir === 'asc') {
return a.issueDate.getTime() - b.issueDate.getTime();
}
return b.issueDate.getTime() - a.issueDate.getTime();
});
}
function applyFilters(row: Invoice[], { customer, id, endDate, status, startDate }: Filters): Invoice[] {
return row.filter((item) => {
if (id) {
if (!item.id.toLowerCase().includes(id.toLowerCase())) {
return false;
}
}
if (status) {
if (item.status !== status) {
return false;
}
}
if (customer) {
if (!item.customer.name?.toLowerCase().includes(customer.toLowerCase())) {
return false;
}
}
if (startDate) {
if (dayjs(item.issueDate).isBefore(dayjs(startDate))) {
return false;
}
}
if (endDate) {
if (dayjs(item.issueDate).isAfter(dayjs(endDate).add(1, 'day'))) {
return false;
}
}
return true;
});
}

View File

@@ -0,0 +1,69 @@
import * as React from 'react';
import Timeline from '@mui/lab/Timeline';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { dayjs } from '@/lib/dayjs';
import { ActivityItem } from '@/components/dashboard/jobs/activity-item';
import type { Event } from '@/components/dashboard/jobs/activity-item';
const events = [
{
id: 'EV-004',
createdAt: dayjs().subtract(7, 'minute').subtract(5, 'hour').subtract(1, 'day').toDate(),
type: 'new_job',
author: { name: 'Jie Yan', avatar: '/assets/avatar-8.png' },
job: { title: 'Remote React / React Native Developer' },
},
{
id: 'EV-003',
createdAt: dayjs().subtract(18, 'minute').subtract(3, 'hour').subtract(5, 'day').toDate(),
type: 'new_job',
author: { name: 'Fran Perez', avatar: '/assets/avatar-5.png' },
job: { title: 'Senior Golang Backend Engineer' },
},
{
id: 'EV-002',
createdAt: dayjs().subtract(41, 'minute').subtract(5, 'hour').subtract(7, 'day').toDate(),
type: 'new_member',
author: { name: 'Jie Yan', avatar: '/assets/avatar-8.png' },
member: { name: 'Omar Darboe' },
},
{
id: 'EV-001',
createdAt: dayjs().subtract(7, 'minute').subtract(8, 'hour').subtract(7, 'day').toDate(),
type: 'new_company',
author: { name: 'Jie Yan', avatar: '/assets/avatar-8.png' },
company: { name: 'Stripe' },
},
] satisfies Event[];
export default function Page(): React.JSX.Element {
return (
<Stack spacing={3}>
<div>
<Typography variant="h6">Activity</Typography>
</div>
<Stack spacing={3}>
<Timeline
sx={{
m: 0,
p: 0,
'& .MuiTimelineItem-root': { '&::before': { display: 'none' } },
'& .MuiTimelineDot-root': { background: 'transparent', border: 0, p: 0 },
'& .MuiTimelineConnector-root': { minHeight: '30px' },
}}
>
{events.map((event, index) => (
<ActivityItem connector={index < events.length - 1} event={event} key={event.id} />
))}
</Timeline>
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
<Button color="secondary">Load more</Button>
</Box>
</Stack>
</Stack>
);
}

View File

@@ -0,0 +1,35 @@
import * as React from 'react';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import { AssetCard } from '@/components/dashboard/jobs/asset-card';
import type { Asset } from '@/components/dashboard/jobs/types';
const assets = [
{
id: 'ASSET-002',
mimeType: 'image/png',
name: 'company-cover.png',
size: '23 KB',
url: '/assets/image-business-2.png',
},
{ id: 'ASSET-001', mimeType: 'application/pdf', name: 'Presentation.pdf', size: '3 MB', url: '' },
] satisfies Asset[];
export default function Page(): React.JSX.Element {
return (
<Stack spacing={3}>
<div>
<Typography variant="h6">Assets ({assets.length})</Typography>
</div>
<Grid container spacing={3}>
{assets.map((asset) => (
<Grid key={asset.id} md={4} sm={6} xs={12}>
<AssetCard asset={asset} />
</Grid>
))}
</Grid>
</Stack>
);
}

View File

@@ -0,0 +1,126 @@
import * as React from 'react';
import type { Metadata } from 'next';
import RouterLink from 'next/link';
import Avatar from '@mui/material/Avatar';
import Box from '@mui/material/Box';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Divider from '@mui/material/Divider';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { config } from '@/config';
import { paths } from '@/paths';
import { PropertyItem } from '@/components/core/property-item';
import { PropertyList } from '@/components/core/property-list';
import { CompanyTabs } from '@/components/dashboard/jobs/company-tabs';
export const metadata = { title: `Company | Jobs | Dashboard | ${config.site.name}` } satisfies Metadata;
interface Founder {
id: string;
name: string;
avatar?: string;
role: string;
}
const founders = [
{ id: 'USR-008', name: 'Jie Yan', avatar: '/assets/avatar-8.png', role: 'CEO & Co-founder' },
{ id: 'USR-005', name: 'Fran Perez', avatar: '/assets/avatar-5.png', role: 'CTO & Co-founder' },
{ id: 'USR-011', name: 'Omar Darboe', avatar: '/assets/avatar-11.png', role: 'CFO' },
] satisfies Founder[];
interface LayoutProps {
children: React.ReactNode;
}
export default function Layout({ children }: LayoutProps): React.JSX.Element {
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<div>
<Link
color="text.primary"
component={RouterLink}
href={paths.dashboard.jobs.browse}
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
variant="subtitle2"
>
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
Jobs
</Link>
</div>
<Grid container spacing={4}>
<Grid lg={8} xs={12}>
<Card>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', p: 3 }}>
<Avatar src="/assets/company-avatar-1.png" variant="rounded" />
<div>
<Typography variant="h6">Stripe</Typography>
<Typography variant="body2">
The new standard in online payments. Stripe is the best software platform for running an internet
</Typography>
</div>
</Stack>
<Divider />
<CompanyTabs />
<Divider />
<CardContent>{children}</CardContent>
</Card>
</Grid>
<Grid lg={4} xs={12}>
<Card>
<CardContent>
<Stack divider={<Divider />} spacing={2}>
<Stack spacing={2}>
<Typography variant="overline">About</Typography>
<PropertyList orientation="vertical">
{(
[
{ key: 'Website', value: <Link variant="body2">https://stripe.com</Link> },
{ key: 'Industry', value: 'Financial Services' },
{ key: 'Locations', value: 'New York City, Milano, Moscow' },
{ key: 'Company size', value: '50-100' },
] satisfies { key: string; value: React.ReactNode }[]
).map(
(item): React.JSX.Element => (
<PropertyItem key={item.key} name={item.key} value={item.value} />
)
)}
</PropertyList>
</Stack>
<Stack spacing={2}>
<Typography variant="overline">Founders</Typography>
<Stack spacing={2}>
{founders.map((founder) => (
<Stack direction="row" key={founder.id} spacing={2} sx={{ alignItems: 'center' }}>
<Avatar src={founder.avatar} />
<div>
<Typography variant="subtitle2">{founder.name}</Typography>
<Typography color="text.secondary" variant="body2">
{founder.role}
</Typography>
</div>
</Stack>
))}
</Stack>
</Stack>
</Stack>
</CardContent>
</Card>
</Grid>
</Grid>
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,140 @@
import * as React from 'react';
import RouterLink from 'next/link';
import Box from '@mui/material/Box';
import Divider from '@mui/material/Divider';
import ImageList from '@mui/material/ImageList';
import ImageListItem from '@mui/material/ImageListItem';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import { ArrowRight as ArrowRightIcon } from '@phosphor-icons/react/dist/ssr/ArrowRight';
import { paths } from '@/paths';
import { dayjs } from '@/lib/dayjs';
import { Description } from '@/components/dashboard/jobs/description';
import { JobsCard } from '@/components/dashboard/jobs/jobs-card';
import { MemberCard } from '@/components/dashboard/jobs/member-card';
import type { Job, Member } from '@/components/dashboard/jobs/types';
const description = `## Stripe is a technology company that builds economic infrastructure for the internet.
Businesses of every size—from new startups to public companies—use our software to accept payments and manage their businesses online.
Stripe's software is used by millions of businesses in over 120 countries and across nearly every industry. We are a global company with a global team that is committed to building a more fair, more inclusive world.
Our mission emphasizes seeking and hiring diverse voices, including those who are traditionally underrepresented in the technology industry, and we consider this to be one of the most important values we hold close. We're a hard-working, fun, and exciting group who value intellectual curiosity and a passion for problem-solving! We have growing offices located in San Francisco, Sunnyvale, Bellevue, Los Angeles, Tokyo, Hamburg, London, and Zurich.`;
const images = [
'/assets/company-image-1.jpg',
'/assets/company-image-2.jpg',
'/assets/company-image-3.jpg',
'/assets/company-image-4.jpg',
'/assets/company-image-5.jpg',
'/assets/company-image-6.jpg',
] satisfies string[];
const jobs = [
{
id: 'JOB-001',
title: 'Senior React Developer',
currency: 'USD',
budgetMin: 94000,
budgetMax: 140000,
isRemote: true,
publishedAt: dayjs().subtract(49, 'minute').subtract(2, 'hour').subtract(7, 'day').toDate(),
},
{
id: 'JOB-002',
title: 'Senior Ruby Engineer',
currency: 'USD',
budgetMin: 120000,
budgetMax: 145000,
isRemote: true,
publishedAt: dayjs().subtract(10, 'minute').subtract(7, 'hour').subtract(8, 'day').toDate(),
},
] satisfies Job[];
const members = [
{
id: 'USR-008',
name: 'Jie Yan',
avatar: '/assets/avatar-8.png',
role: 'CEO & Co-founder',
skills: ['JavaScript', 'React', 'Go'],
},
{
id: 'USR-005',
name: 'Fran Perez',
avatar: '/assets/avatar-5.png',
role: 'CTO & Co-founder',
skills: ['C', 'C++', 'Java'],
},
] satisfies Member[];
export default function Page(): React.JSX.Element {
return (
<Stack spacing={3}>
<Description content={description} />
<ImageList cols={3} gap={24} variant="masonry">
{images.map((image) => (
<ImageListItem key={image}>
<Box alt="Gallery" component="img" src={image} sx={{ height: '100%', width: '100%' }} />
</ImageListItem>
))}
</ImageList>
<Divider />
<Stack spacing={3}>
<Stack
direction="row"
spacing={3}
sx={{ alignItems: 'center', flexWrap: 'wrap', justifyContent: 'space-between' }}
>
<Typography variant="h6">Jobs</Typography>
<Link
color="text.primary"
component={RouterLink}
href={paths.dashboard.jobs.companies.overview('1')}
sx={{ alignItems: 'center', display: 'flex', gap: 1 }}
variant="subtitle2"
>
<Typography variant="subtitle2">Jobs</Typography>
<Box sx={{ display: 'flex', flex: '0 0 auto' }}>
<ArrowRightIcon fontSize="var(--icon-fontSize-md)" />
</Box>
</Link>
</Stack>
<JobsCard jobs={jobs} />
</Stack>
<Divider />
<Stack spacing={3}>
<Stack
direction="row"
spacing={3}
sx={{ alignItems: 'center', flexWrap: 'wrap', justifyContent: 'space-between' }}
>
<Typography variant="h6">Members</Typography>
<Link
color="text.primary"
component={RouterLink}
href={paths.dashboard.jobs.companies.overview('1')}
sx={{ alignItems: 'center', display: 'flex', gap: 1 }}
variant="subtitle2"
>
<Typography variant="subtitle2">Members</Typography>
<Box sx={{ display: 'flex', flex: '0 0 auto' }}>
<ArrowRightIcon fontSize="var(--icon-fontSize-md)" />
</Box>
</Link>
</Stack>
<Grid container spacing={3}>
{members.map((member) => (
<Grid key={member.id} sm={6} xs={12}>
<MemberCard member={member} />
</Grid>
))}
</Grid>
</Stack>
</Stack>
);
}

View File

@@ -0,0 +1,63 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { dayjs } from '@/lib/dayjs';
import { CompanyReviewAdd } from '@/components/dashboard/jobs/review-add';
import { ReviewCard } from '@/components/dashboard/jobs/review-card';
import { ReviewsSummary } from '@/components/dashboard/jobs/reviews-summary';
import type { Review } from '@/components/dashboard/jobs/types';
const reviews = [
{
id: 'REV-003',
title: 'Great company, providing an awesome & easy to use product',
comment:
'I have been working with this company full-time. Great for the work life balance. Cons, decentralized decision making process across the organization.',
rating: 3.8,
author: { name: 'Sofia Rivers', avatar: '/assets/avatar.png' },
createdAt: dayjs().subtract(4, 'minute').subtract(4, 'hour').subtract(2, 'day').toDate(),
},
{
id: 'REV-002',
title: 'Friendly environment',
comment:
'Every day you learn something new - that is a typical day at work in Stripe. I am surrounded by supportive people, from different cultures, we have a strong and unified team and help each other whenever is needed. The most enjoyable part of the job is that you meet new people, experts on different disciplines that might help you in your work, but the hardest part of the job is that there are too many tools and systems to use.',
rating: 3.4,
author: { name: 'Carson Darrin', avatar: '/assets/avatar-3.png' },
createdAt: dayjs().subtract(17, 'minute').subtract(4, 'hour').subtract(5, 'day').toDate(),
},
{
id: 'REV-001',
title: 'Great company, providing an awesome & easy to use product',
comment:
'I have been working with this company full-time. Great for the work life balance. Cons, decentralized decision making process across the organization.',
rating: 3.8,
author: { name: 'Fran Perez', avatar: '/assets/avatar-5.png' },
createdAt: dayjs().subtract(34, 'minute').subtract(5, 'hour').subtract(7, 'day').toDate(),
},
] satisfies Review[];
export default function Page(): React.JSX.Element {
const averageRating = 3.66;
return (
<Stack spacing={3}>
<div>
<Typography variant="h6">Reviews</Typography>
</div>
<Stack spacing={3}>
<ReviewsSummary averageRating={averageRating} totalReviews={reviews.length} />
{reviews.map((review) => (
<ReviewCard key={review.id} review={review} />
))}
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
<Button color="secondary">Load more</Button>
</Box>
<CompanyReviewAdd />
</Stack>
</Stack>
);
}

View File

@@ -0,0 +1,42 @@
import * as React from 'react';
import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { MemberCard } from '@/components/dashboard/jobs/member-card';
import type { Member } from '@/components/dashboard/jobs/types';
const members = [
{
id: 'USR-008',
name: 'Jie Yan',
avatar: '/assets/avatar-8.png',
role: 'CEO & Co-founder',
skills: ['JavaScript', 'React', 'Go'],
},
{
id: 'USR-005',
name: 'Fran Perez',
avatar: '/assets/avatar-5.png',
role: 'CTO & Co-founder',
skills: ['C', 'C++', 'Java'],
},
{ id: 'USR-011', name: 'Omar Darboe', avatar: '/assets/avatar-11.png', role: 'CFO', skills: ['Go', 'Python'] },
] satisfies Member[];
export default function Page(): React.JSX.Element {
return (
<Stack spacing={3}>
<div>
<Typography variant="h6">Team ({members.length})</Typography>
</div>
<Grid container spacing={3}>
{members.map((member) => (
<Grid item key={member.id} sm={6} xs={12}>
<MemberCard member={member} />
</Grid>
))}
</Grid>
</Stack>
);
}

View File

@@ -0,0 +1,35 @@
import * as React from 'react';
import type { Metadata } from 'next';
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { config } from '@/config';
import { JobCreateForm } from '@/components/dashboard/jobs/job-create-form';
export const metadata = { title: `Create | Jobs | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element {
return (
<Box sx={{ display: 'flex', flex: '1 1 0', minHeight: 0 }}>
<Box
sx={{
position: 'relative',
overflow: 'hidden',
backgroundImage: 'url(/assets/people-talking.png)',
backgroundPosition: 'center',
backgroundSize: 'cover',
flex: '0 0 auto',
display: { xs: 'none', md: 'block' },
width: { md: '400px', xl: '500px' },
}}
/>
<Box sx={{ flex: '1 1 auto', overflowY: 'auto', p: { xs: 4, sm: 6, md: 8 } }}>
<Stack maxWidth="md" spacing={4}>
<Typography variant="h4">Create job ad</Typography>
<JobCreateForm />
</Stack>
</Box>
</Box>
);
}

View File

@@ -0,0 +1,188 @@
import * as React from 'react';
import type { Metadata } from 'next';
import RouterLink from 'next/link';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import { CaretLeft as CaretLeftIcon } from '@phosphor-icons/react/dist/ssr/CaretLeft';
import { CaretRight as CaretRightIcon } from '@phosphor-icons/react/dist/ssr/CaretRight';
import { config } from '@/config';
import { paths } from '@/paths';
import { dayjs } from '@/lib/dayjs';
import { CompanyCard } from '@/components/dashboard/jobs/company-card';
import type { Company } from '@/components/dashboard/jobs/company-card';
import { JobsFilters } from '@/components/dashboard/jobs/jobs-filters';
export const metadata = { title: `Browse | Jobs | Dashboard | ${config.site.name}` } satisfies Metadata;
const companies = [
{
id: 'COM-004',
name: 'Vercel',
logo: '/assets/company-avatar-4.png',
description: 'Develop. Preview. Ship. For the best frontend teams.',
rating: 4.5,
employees: '10-20',
isVerified: true,
jobs: [
{
id: 'JOB-005',
title: 'Remote React / React Native Developer',
currency: 'USD',
budgetMin: 55000,
budgetMax: 75000,
isRemote: true,
publishedAt: dayjs().subtract(24, 'minute').toDate(),
},
{
id: 'JOB-006',
title: 'Senior Golang Backend Engineer',
currency: 'USD',
budgetMin: 80000,
budgetMax: 160000,
country: 'Germany',
state: 'Bavaria',
city: 'Munich',
publishedAt: dayjs().subtract(45, 'minute').subtract(2, 'hour').toDate(),
},
],
},
{
id: 'COM-003',
name: 'Auth0',
logo: '/assets/company-avatar-3.png',
description: 'Secure access for everyone. But not just anyone.',
rating: 4.3,
employees: '50-100',
isVerified: false,
jobs: [
{
id: 'JOB-004',
title: 'Remote React / React Native Developer',
currency: 'USD',
budgetMin: 87000,
budgetMax: 135000,
isRemote: true,
publishedAt: dayjs().subtract(1, 'hour').toDate(),
},
],
},
{
id: 'COM-002',
name: 'Google Cloud',
logo: '/assets/company-avatar-2.png',
description: 'Build, modernize, and scale your applications with Google Cloud.',
rating: 4.5,
employees: '1-10',
isVerified: false,
jobs: [
{
id: 'JOB-003',
title: 'Senior Backend Engineer',
currency: 'USD',
budgetMin: 150000,
budgetMax: 210000,
isRemote: true,
publishedAt: dayjs().subtract(39, 'minute').subtract(7, 'hour').subtract(5, 'day').toDate(),
},
],
},
{
id: 'COM-001',
name: 'Stripe',
logo: '/assets/company-avatar-1.png',
description: 'The new standard in online payments',
rating: 4.9,
employees: '50-100',
isVerified: true,
jobs: [
{
id: 'JOB-001',
title: 'Senior React Developer',
currency: 'USD',
budgetMin: 94000,
budgetMax: 140000,
isRemote: true,
publishedAt: dayjs().subtract(49, 'minute').subtract(2, 'hour').subtract(7, 'day').toDate(),
},
{
id: 'JOB-002',
title: 'Senior Ruby Engineer',
currency: 'USD',
budgetMin: 120000,
budgetMax: 145000,
isRemote: true,
publishedAt: dayjs().subtract(10, 'minute').subtract(7, 'hour').subtract(8, 'day').toDate(),
},
],
},
] satisfies Company[];
export default function Page(): React.JSX.Element {
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Box
sx={{
bgcolor: 'var(--mui-palette-neutral-900)',
borderRadius: 1,
color: 'var(--mui-palette-common-white)',
px: 4,
py: 8,
}}
>
<Grid container sx={{ alignItems: 'center' }}>
<Grid sm={7} xs={12}>
<Stack spacing={3}>
<Stack spacing={2}>
<Typography color="inherit" variant="h3">
Reach 50K+ potential candidates.
</Typography>
<Typography color="neutral.400" variant="body1">
Post your job today for free. Promotions start at $99.
</Typography>
</Stack>
<div>
<Button color="primary" component={RouterLink} href={paths.dashboard.jobs.create} variant="contained">
Post a job
</Button>
</div>
</Stack>
</Grid>
<Grid sm={5} sx={{ display: { xs: 'none', sm: 'flex' }, justifyContent: 'center' }}>
<Box
alt="Shield"
component="img"
src="/assets/iconly-glass-shield.svg"
sx={{ height: 'auto', width: '200px' }}
/>
</Grid>
</Grid>
</Box>
<JobsFilters />
{companies.map((company) => (
<CompanyCard company={company} key={company.id} />
))}
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', justifyContent: 'center', px: 3 }}>
<IconButton disabled>
<CaretLeftIcon />
</IconButton>
<IconButton>
<CaretRightIcon />
</IconButton>
</Stack>
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,16 @@
import * as React from 'react';
import { AuthGuard } from '@/components/auth/auth-guard';
import { DynamicLayout } from '@/components/dashboard/layout/dynamic-layout';
interface LayoutProps {
children: React.ReactNode;
}
export default function Layout({ children }: LayoutProps): React.JSX.Element {
return (
<AuthGuard>
<DynamicLayout>{children}</DynamicLayout>
</AuthGuard>
);
}

View File

@@ -0,0 +1,56 @@
import * as React from 'react';
import type { Metadata } from 'next';
import { config } from '@/config';
import { dayjs } from '@/lib/dayjs';
import { FleetView } from '@/components/dashboard/logistics/fleet-view';
import type { Vehicle } from '@/components/dashboard/logistics/types';
export const metadata = { title: `Fleet | Logistics | Dashboard | ${config.site.name}` } satisfies Metadata;
const vehicles = [
{
id: 'VEH-004',
location: 'Brooklyn, New York, United States',
latitude: 40.683717,
longitude: -73.938242,
temperature: 6,
startedAt: dayjs().subtract(21, 'minute').subtract(2, 'hour').toDate(),
departedAt: dayjs().subtract(34, 'minute').toDate(),
arrivedAt: dayjs().subtract(9, 'minute').toDate(),
},
{
id: 'VEH-003',
location: 'Brooklyn, New York, United States',
latitude: 40.698211,
longitude: -73.92369,
temperature: 8,
startedAt: dayjs().subtract(10, 'minute').subtract(3, 'hour').toDate(),
departedAt: dayjs().subtract(56, 'minute').subtract(2, 'hour').toDate(),
arrivedAt: dayjs().subtract(10, 'minute').subtract(1, 'hour').toDate(),
},
{
id: 'VEH-002',
location: 'Brooklyn, New York, United States',
latitude: 40.657431,
longitude: -73.960399,
temperature: 6,
startedAt: dayjs().subtract(34, 'minute').subtract(4, 'hour').toDate(),
departedAt: undefined,
arrivedAt: undefined,
},
{
id: 'VEH-001',
location: 'Brooklyn, New York, United States',
latitude: 40.675966,
longitude: -73.876617,
temperature: 8,
startedAt: dayjs().subtract(9, 'minute').subtract(5, 'hour').toDate(),
departedAt: dayjs().subtract(12, 'minute').subtract(2, 'hour').toDate(),
arrivedAt: dayjs().subtract(21, 'minute').subtract(1, 'hour').toDate(),
},
] satisfies Vehicle[];
export default function Page(): React.JSX.Element {
return <FleetView vehicles={vehicles} />;
}

View File

@@ -0,0 +1,111 @@
import * as React from 'react';
import type { Metadata } from 'next';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import { config } from '@/config';
import { DeviatedVehicles } from '@/components/dashboard/logistics/deviated-vehicles';
import { LateVehicles } from '@/components/dashboard/logistics/late-vehicles';
import { OnRouteVehicles } from '@/components/dashboard/logistics/on-route-vehicles';
import { VehiclesCondition } from '@/components/dashboard/logistics/vehicles-condition';
import { VehiclesOverview } from '@/components/dashboard/logistics/vehicles-overview';
import { VehiclesTable } from '@/components/dashboard/logistics/vehicles-table';
import { VehiclesWithErrors } from '@/components/dashboard/logistics/vehicles-with-errors';
export const metadata = { title: `Metrics | Logistics | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element {
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
<Box sx={{ flex: '1 1 auto' }}>
<Typography variant="h4">Logistics</Typography>
</Box>
<div>
<Button startIcon={<PlusIcon />} variant="contained">
Add vehicle
</Button>
</div>
</Stack>
<Grid container spacing={4}>
<Grid md={3} xs={12}>
<OnRouteVehicles amount={38} />
</Grid>
<Grid md={3} xs={12}>
<VehiclesWithErrors amount={2} />
</Grid>
<Grid md={3} xs={12}>
<DeviatedVehicles amount={1} />
</Grid>
<Grid md={3} xs={12}>
<LateVehicles amount={2} />
</Grid>
<Grid lg={6} xs={12}>
<VehiclesOverview
data={[
{ name: 'Available', value: 38, fill: 'var(--mui-palette-primary-main)' },
{ name: 'Out of service', value: 50, fill: 'var(--mui-palette-warning-main)' },
{ name: 'On route', value: 12, fill: 'var(--mui-palette-info-main)' },
]}
/>
</Grid>
<Grid lg={6} xs={12}>
<VehiclesCondition bad={12} excellent={181} good={24} />
</Grid>
<Grid xs={12}>
<VehiclesTable
rows={[
{
id: 'VEH-004',
endingRoute: 'Dordrecht, Netherlands',
startingRoute: 'Liden, Netherlands',
status: 'success',
temperature: 8,
temperatureLabel: 'Very Good',
},
{
id: 'VEH-003',
endingRoute: 'Paris, France',
startingRoute: 'Lion, France',
status: 'warning',
temperature: 24,
temperatureLabel: 'Bad',
warning: 'Temperature not optimal',
},
{
id: 'VEH-002',
endingRoute: 'Memphis, Tennessee, United States',
startingRoute: 'Dallas, Texas, United States',
status: 'error',
temperature: 8,
temperatureLabel: 'Very Good',
warning: 'ECU not responding',
},
{
id: 'VEH-001',
endingRoute: 'Cleveland, Ohio, United States',
startingRoute: 'Cleveland, Ohio, United States',
status: 'success',
temperature: 12,
temperatureLabel: 'Good',
},
]}
/>
</Grid>
</Grid>
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,17 @@
import * as React from 'react';
import type { Metadata } from 'next';
import { config } from '@/config';
import { ThreadView } from '@/components/dashboard/mail/thread-view';
export const metadata = { title: `Thread | Mail | Dashboard | ${config.site.name}` } satisfies Metadata;
interface PageProps {
params: { threadId: string };
}
export default function Page({ params }: PageProps): React.JSX.Element {
const { threadId } = params;
return <ThreadView threadId={threadId} />;
}

View File

@@ -0,0 +1,142 @@
import * as React from 'react';
import { dayjs } from '@/lib/dayjs';
import { MailProvider } from '@/components/dashboard/mail/mail-context';
import { MailView } from '@/components/dashboard/mail/mail-view';
import type { Label, Thread } from '@/components/dashboard/mail/types';
function filterThreads(threads: Thread[], labelId: string): Thread[] {
return threads.filter((thread) => {
if (['inbox', 'sent', 'drafts', 'spam', 'trash'].includes(labelId)) {
return thread.folder === labelId;
}
if (labelId === 'important') {
return thread.isImportant;
}
if (labelId === 'starred') {
return thread.isStarred;
}
if (thread.labels.includes(labelId)) {
return true;
}
return false;
});
}
const labels = [
{ id: 'inbox', type: 'system', name: 'Inbox', unreadCount: 1, totalCount: 0 },
{ id: 'sent', type: 'system', name: 'Sent', unreadCount: 0, totalCount: 0 },
{ id: 'drafts', type: 'system', name: 'Drafts', unreadCount: 0, totalCount: 0 },
{ id: 'spam', type: 'system', name: 'Spam', unreadCount: 0, totalCount: 0 },
{ id: 'trash', type: 'system', name: 'Trash', unreadCount: 0, totalCount: 1 },
{ id: 'important', type: 'system', name: 'Important', unreadCount: 0, totalCount: 1 },
{ id: 'starred', type: 'system', name: 'Starred', unreadCount: 1, totalCount: 1 },
{ id: 'work', type: 'custom', name: 'Work', color: '#43A048', unreadCount: 0, totalCount: 1 },
{ id: 'business', type: 'custom', name: 'Business', color: '#1E88E5', unreadCount: 1, totalCount: 2 },
{ id: 'personal', type: 'custom', name: 'Personal', color: '#FB8A00', unreadCount: 0, totalCount: 1 },
] satisfies Label[];
const threads = [
{
id: 'TRD-004',
from: { avatar: '/assets/avatar-9.png', email: 'marcus.finn@domain.com', name: 'Marcus Finn' },
to: [{ avatar: '/assets/avatar.png', email: 'sofia@devias.io', name: 'Sofia Rivers' }],
subject: 'Website redesign. Interested in collaboration',
message: `Hey there,
I hope this email finds you well. I'm glad you liked my projects, and I would be happy to provide you with a quote for a similar project.
Please let me know your requirements and any specific details you have in mind, so I can give you an accurate quote.
Looking forward to hearing from you soon.
Best regards,
Marcus Finn`,
attachments: [
{
id: 'ATT-001',
name: 'working-sketch.png',
size: '128.5 KB',
type: 'image',
url: '/assets/image-abstract-1.png',
},
{ id: 'ATT-002', name: 'summer-customers.pdf', size: '782.3 KB', type: 'file', url: '#' },
{
id: 'ATT-003',
name: 'desktop-coffee.png',
size: '568.2 KB',
type: 'image',
url: '/assets/image-minimal-1.png',
},
],
folder: 'inbox',
labels: ['work', 'business'],
isImportant: true,
isStarred: false,
isUnread: true,
createdAt: dayjs().subtract(3, 'hour').toDate(),
},
{
id: 'TRD-003',
to: [{ name: 'Sofia Rivers', avatar: '/assets/avatar.png', email: 'sofia@devias.io' }],
from: { name: 'Miron Vitold', avatar: '/assets/avatar-1.png', email: 'miron.vitold@domain.com' },
subject: 'Amazing work',
message: `Hey, nice projects! I really liked the one in react. What's your quote on kinda similar project?`,
folder: 'spam',
labels: [],
isImportant: false,
isStarred: true,
isUnread: false,
createdAt: dayjs().subtract(1, 'day').toDate(),
},
{
id: 'TRD-002',
from: { name: 'Penjani Inyene', avatar: '/assets/avatar-4.png', email: 'penjani.inyene@domain.com' },
to: [{ name: 'Sofia Rivers', avatar: '/assets/avatar.png', email: 'sofia@devias.io' }],
subject: 'Flight reminder',
message: `Dear Sofia,
Your flight is coming up soon. Please don't forget to check in for your scheduled flight.`,
folder: 'inbox',
labels: ['business'],
isImportant: false,
isStarred: false,
isUnread: false,
createdAt: dayjs().subtract(2, 'day').toDate(),
},
{
id: 'TRD-001',
from: { name: 'Carson Darrin', avatar: '/assets/avatar-3.png', email: 'carson.darrin@domain.com' },
to: [{ name: 'Sofia Rivers', avatar: '/assets/avatar.png', email: 'sofia@devias.io' }],
subject: 'Possible candidates for the position',
message: `My market leading client has another fantastic opportunity for an experienced Software Developer to join them on a heavily remote basis`,
folder: 'trash',
labels: ['personal'],
isImportant: false,
isStarred: false,
isUnread: true,
createdAt: dayjs().subtract(2, 'day').toDate(),
},
] satisfies Thread[];
interface LayoutProps {
children: React.ReactNode;
params: { labelId: string };
}
export default function Layout({ children, params }: LayoutProps): React.JSX.Element {
const { labelId } = params;
const filteredThreads = filterThreads(threads, labelId);
return (
<MailProvider currentLabelId={labelId} labels={labels} threads={filteredThreads}>
<MailView>{children}</MailView>
</MailProvider>
);
}

View File

@@ -0,0 +1,11 @@
import * as React from 'react';
import type { Metadata } from 'next';
import { config } from '@/config';
import { ThreadsView } from '@/components/dashboard/mail/threads-view';
export const metadata = { title: `Mail | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element {
return <ThreadsView />;
}

View File

@@ -0,0 +1,270 @@
import * as React from 'react';
import type { Metadata } from 'next';
import RouterLink from 'next/link';
import Avatar from '@mui/material/Avatar';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import CardHeader from '@mui/material/CardHeader';
import Chip from '@mui/material/Chip';
import Divider from '@mui/material/Divider';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { CreditCard as CreditCardIcon } from '@phosphor-icons/react/dist/ssr/CreditCard';
import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple';
import { ShoppingCartSimple as ShoppingCartSimpleIcon } from '@phosphor-icons/react/dist/ssr/ShoppingCartSimple';
import { Timer as TimerIcon } from '@phosphor-icons/react/dist/ssr/Timer';
import { config } from '@/config';
import { paths } from '@/paths';
import { dayjs } from '@/lib/dayjs';
import { PropertyItem } from '@/components/core/property-item';
import { PropertyList } from '@/components/core/property-list';
import type { Event } from '@/components/dashboard/order/events-timeline';
import { EventsTimeline } from '@/components/dashboard/order/events-timeline';
import { LineItemsTable } from '@/components/dashboard/order/line-items-table';
import type { LineItem } from '@/components/dashboard/order/line-items-table';
export const metadata = { title: `Details | Orders | Dashboard | ${config.site.name}` } satisfies Metadata;
const lineItems = [
{
id: 'LI-001',
product: 'Erbology Aloe Vera',
image: '/assets/product-1.png',
quantity: 1,
currency: 'USD',
unitAmount: 24,
totalAmount: 24,
},
{
id: 'LI-002',
product: 'Lancome Rouge',
image: '/assets/product-2.png',
quantity: 1,
currency: 'USD',
unitAmount: 35,
totalAmount: 35,
},
] satisfies LineItem[];
const events = [
{
id: 'EV-004',
createdAt: dayjs().subtract(3, 'hour').toDate(),
type: 'note_added',
author: { name: 'Fran Perez', avatar: '/assets/avatar-5.png' },
note: 'Customer states that the products have been damaged by the courier.',
},
{
id: 'EV-003',
createdAt: dayjs().subtract(12, 'hour').toDate(),
type: 'shipment_notice',
description: 'Left the package in front of the door',
},
{
id: 'EV-002',
createdAt: dayjs().subtract(18, 'hour').toDate(),
type: 'items_shipped',
carrier: 'USPS',
trackingNumber: '940011189',
},
{ id: 'EV-001', createdAt: dayjs().subtract(21, 'hour').toDate(), type: 'order_created' },
] satisfies Event[];
export default function Page(): React.JSX.Element {
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<div>
<Link
color="text.primary"
component={RouterLink}
href={paths.dashboard.orders.list}
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
variant="subtitle2"
>
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
Orders
</Link>
</div>
<div>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
<Box sx={{ flex: '1 1 auto' }}>
<Typography variant="h5">ORD-001</Typography>
</Box>
<div>
<Button endIcon={<CaretDownIcon />} variant="contained">
Action
</Button>
</div>
</Stack>
</div>
<Grid container spacing={4}>
<Grid md={8} xs={12}>
<Stack spacing={4}>
<Card>
<CardHeader
action={
<Button color="secondary" startIcon={<PencilSimpleIcon />}>
Edit
</Button>
}
avatar={
<Avatar>
<CreditCardIcon fontSize="var(--Icon-fontSize)" />
</Avatar>
}
title="Order information"
/>
<CardContent>
<Card sx={{ borderRadius: 1 }} variant="outlined">
<PropertyList divider={<Divider />} sx={{ '--PropertyItem-padding': '12px 24px' }}>
{(
[
{ key: 'Customer', value: <Link variant="subtitle2">Miron Vitold</Link> },
{
key: 'Address',
value: (
<Typography variant="subtitle2">
1721 Bartlett Avenue
<br />
Southfield, Michigan, United States
<br />
48034
</Typography>
),
},
{ key: 'Date', value: dayjs().subtract(3, 'hour').format('MMMM D, YYYY hh:mm A') },
{
key: 'Status',
value: (
<Chip
icon={<CheckCircleIcon color="var(--mui-palette-success-main)" weight="fill" />}
label="Completed"
size="small"
variant="outlined"
/>
),
},
{
key: 'Payment method',
value: (
<Stack direction="row" spacing={2} sx={{ alignItems: 'center' }}>
<Avatar
sx={{
bgcolor: 'var(--mui-palette-background-paper)',
boxShadow: 'var(--mui-shadows-8)',
}}
>
<Box
component="img"
src="/assets/payment-method-1.png"
sx={{ borderRadius: '50px', height: 'auto', width: '35px' }}
/>
</Avatar>
<div>
<Typography variant="body2">Mastercard</Typography>
<Typography color="text.secondary" variant="body2">
**** 4242
</Typography>
</div>
</Stack>
),
},
] satisfies { key: string; value: React.ReactNode }[]
).map(
(item): React.JSX.Element => (
<PropertyItem key={item.key} name={item.key} value={item.value} />
)
)}
</PropertyList>
</Card>
</CardContent>
</Card>
<Card>
<CardHeader
avatar={
<Avatar>
<ShoppingCartSimpleIcon fontSize="var(--Icon-fontSize)" />
</Avatar>
}
title="Checkout Summary"
/>
<CardContent>
<Stack spacing={2}>
<Card sx={{ borderRadius: 1 }} variant="outlined">
<Box sx={{ overflowX: 'auto' }}>
<LineItemsTable rows={lineItems} />
</Box>
</Card>
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
<Stack spacing={2} sx={{ width: '300px', maxWidth: '100%' }}>
<Stack direction="row" spacing={3} sx={{ justifyContent: 'space-between' }}>
<Typography variant="body2">Subtotal</Typography>
<Typography variant="body2">
{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(59)}
</Typography>
</Stack>
<Stack direction="row" spacing={3} sx={{ justifyContent: 'space-between' }}>
<Typography variant="body2">Discount</Typography>
<Typography variant="body2">-</Typography>
</Stack>
<Stack direction="row" spacing={3} sx={{ justifyContent: 'space-between' }}>
<Typography variant="body2">Shipping</Typography>
<Typography variant="body2">
{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(20)}
</Typography>
</Stack>
<Stack direction="row" spacing={3} sx={{ justifyContent: 'space-between' }}>
<Typography variant="body2">Taxes</Typography>
<Typography variant="body2">
{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(15.01)}
</Typography>
</Stack>
<Stack direction="row" spacing={3} sx={{ justifyContent: 'space-between' }}>
<Typography variant="subtitle1">Total</Typography>
<Typography variant="subtitle1">
{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(94.01)}
</Typography>
</Stack>
</Stack>
</Box>
</Stack>
</CardContent>
</Card>
</Stack>
</Grid>
<Grid md={4} xs={12}>
<Card>
<CardHeader
avatar={
<Avatar>
<TimerIcon fontSize="var(--Icon-fontSize)" />
</Avatar>
}
title="Timeline"
/>
<CardContent>
<EventsTimeline events={events} />
</CardContent>
</Card>
</Grid>
</Grid>
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,48 @@
import * as React from 'react';
import type { Metadata } from 'next';
import RouterLink from 'next/link';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { config } from '@/config';
import { paths } from '@/paths';
import { OrderCreateForm } from '@/components/dashboard/order/order-create-form';
export const metadata = { title: `Create | Orders | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element {
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Stack spacing={3}>
<div>
<Link
color="text.primary"
component={RouterLink}
href={paths.dashboard.orders.list}
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
variant="subtitle2"
>
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
Orders
</Link>
</div>
<div>
<Typography variant="h4">Create order</Typography>
</div>
</Stack>
<OrderCreateForm />
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,159 @@
import * as React from 'react';
import type { Metadata } from 'next';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import Divider from '@mui/material/Divider';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import { config } from '@/config';
import { dayjs } from '@/lib/dayjs';
import { OrderModal } from '@/components/dashboard/order/order-modal';
import { OrdersFilters } from '@/components/dashboard/order/orders-filters';
import type { Filters } from '@/components/dashboard/order/orders-filters';
import { OrdersPagination } from '@/components/dashboard/order/orders-pagination';
import { OrdersSelectionProvider } from '@/components/dashboard/order/orders-selection-context';
import { OrdersTable } from '@/components/dashboard/order/orders-table';
import type { Order } from '@/components/dashboard/order/orders-table';
export const metadata = { title: `List | Orders | Dashboard | ${config.site.name}` } satisfies Metadata;
const orders = [
{
id: 'ORD-005',
customer: { name: 'Penjani Inyene', avatar: '/assets/avatar-4.png', email: 'penjani.inyene@domain.com' },
lineItems: 1,
paymentMethod: { type: 'visa', last4: '4011' },
currency: 'USD',
totalAmount: 56.7,
status: 'pending',
createdAt: dayjs().subtract(3, 'hour').toDate(),
},
{
id: 'ORD-004',
customer: { name: 'Jie Yan', avatar: '/assets/avatar-8.png', email: 'jie.yan@domain.com' },
lineItems: 1,
paymentMethod: { type: 'amex', last4: '5678' },
currency: 'USD',
totalAmount: 49.12,
status: 'completed',
createdAt: dayjs().subtract(6, 'hour').toDate(),
},
{
id: 'ORD-003',
customer: { name: 'Fran Perez', avatar: '/assets/avatar-5.png', email: 'fran.perez@domain.com' },
lineItems: 2,
paymentMethod: { type: 'applepay' },
currency: 'USD',
totalAmount: 18.75,
status: 'canceled',
createdAt: dayjs().subtract(7, 'hour').toDate(),
},
{
id: 'ORD-002',
customer: { name: 'Carson Darrin', avatar: '/assets/avatar-3.png', email: 'carson.darrin@domain.com' },
lineItems: 1,
paymentMethod: { type: 'googlepay' },
currency: 'USD',
totalAmount: 49.99,
status: 'rejected',
createdAt: dayjs().subtract(1, 'hour').subtract(1, 'day').toDate(),
},
{
id: 'ORD-001',
customer: { name: 'Miron Vitold', avatar: '/assets/avatar-1.png', email: 'miron.vitold@domain.com' },
lineItems: 2,
paymentMethod: { type: 'mastercard', last4: '4242' },
currency: 'USD',
totalAmount: 94.01,
status: 'completed',
createdAt: dayjs().subtract(3, 'hour').subtract(1, 'day').toDate(),
},
] satisfies Order[];
interface PageProps {
searchParams: { customer?: string; id?: string; previewId?: string; sortDir?: 'asc' | 'desc'; status?: string };
}
export default function Page({ searchParams }: PageProps): React.JSX.Element {
const { customer, id, previewId, sortDir, status } = searchParams;
const sortedOrders = applySort(orders, sortDir);
const filteredOrders = applyFilters(sortedOrders, { customer, id, status });
return (
<React.Fragment>
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
<Box sx={{ flex: '1 1 auto' }}>
<Typography variant="h4">Orders</Typography>
</Box>
<div>
<Button startIcon={<PlusIcon />} variant="contained">
Add
</Button>
</div>
</Stack>
<OrdersSelectionProvider orders={filteredOrders}>
<Card>
<OrdersFilters filters={{ customer, id, status }} sortDir={sortDir} />
<Divider />
<Box sx={{ overflowX: 'auto' }}>
<OrdersTable rows={filteredOrders} />
</Box>
<Divider />
<OrdersPagination count={filteredOrders.length} page={0} />
</Card>
</OrdersSelectionProvider>
</Stack>
</Box>
<OrderModal open={Boolean(previewId)} />
</React.Fragment>
);
}
// Sorting and filtering has to be done on the server.
function applySort(row: Order[], sortDir: 'asc' | 'desc' | undefined): Order[] {
return row.sort((a, b) => {
if (sortDir === 'asc') {
return a.createdAt.getTime() - b.createdAt.getTime();
}
return b.createdAt.getTime() - a.createdAt.getTime();
});
}
function applyFilters(row: Order[], { customer, id, status }: Filters): Order[] {
return row.filter((item) => {
if (customer) {
if (!item.customer?.name?.toLowerCase().includes(customer.toLowerCase())) {
return false;
}
}
if (id) {
if (!item.id?.toLowerCase().includes(id.toLowerCase())) {
return false;
}
}
if (status) {
if (item.status !== status) {
return false;
}
}
return true;
});
}

View File

@@ -0,0 +1,236 @@
import * as React from 'react';
import type { Metadata } from 'next';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import { ArrowRight as ArrowRightIcon } from '@phosphor-icons/react/dist/ssr/ArrowRight';
import { Briefcase as BriefcaseIcon } from '@phosphor-icons/react/dist/ssr/Briefcase';
import { FileCode as FileCodeIcon } from '@phosphor-icons/react/dist/ssr/FileCode';
import { Info as InfoIcon } from '@phosphor-icons/react/dist/ssr/Info';
import { ListChecks as ListChecksIcon } from '@phosphor-icons/react/dist/ssr/ListChecks';
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import { Users as UsersIcon } from '@phosphor-icons/react/dist/ssr/Users';
import { Warning as WarningIcon } from '@phosphor-icons/react/dist/ssr/Warning';
import { config } from '@/config';
import { dayjs } from '@/lib/dayjs';
import { AppChat } from '@/components/dashboard/overview/app-chat';
import { AppLimits } from '@/components/dashboard/overview/app-limits';
import { AppUsage } from '@/components/dashboard/overview/app-usage';
import { Events } from '@/components/dashboard/overview/events';
import { HelperWidget } from '@/components/dashboard/overview/helper-widget';
import { Subscriptions } from '@/components/dashboard/overview/subscriptions';
import { Summary } from '@/components/dashboard/overview/summary';
export const metadata = { title: `Overview | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element {
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
<Box sx={{ flex: '1 1 auto' }}>
<Typography variant="h4">Overview</Typography>
</Box>
<div>
<Button startIcon={<PlusIcon />} variant="contained">
Dashboard
</Button>
</div>
</Stack>
<Grid container spacing={4}>
<Grid md={4} xs={12}>
<Summary amount={31} diff={15} icon={ListChecksIcon} title="Tickets" trend="up" />
</Grid>
<Grid md={4} xs={12}>
<Summary amount={240} diff={5} icon={UsersIcon} title="Sign ups" trend="down" />
</Grid>
<Grid md={4} xs={12}>
<Summary amount={21} diff={12} icon={WarningIcon} title="Open issues" trend="up" />
</Grid>
<Grid md={8} xs={12}>
<AppUsage
data={[
{ name: 'Jan', v1: 36, v2: 19 },
{ name: 'Feb', v1: 45, v2: 23 },
{ name: 'Mar', v1: 26, v2: 12 },
{ name: 'Apr', v1: 39, v2: 20 },
{ name: 'May', v1: 26, v2: 12 },
{ name: 'Jun', v1: 42, v2: 31 },
{ name: 'Jul', v1: 38, v2: 19 },
{ name: 'Aug', v1: 39, v2: 20 },
{ name: 'Sep', v1: 37, v2: 18 },
{ name: 'Oct', v1: 41, v2: 22 },
{ name: 'Nov', v1: 45, v2: 24 },
{ name: 'Dec', v1: 23, v2: 17 },
]}
/>
</Grid>
<Grid md={4} xs={12}>
<Subscriptions
subscriptions={[
{
id: 'supabase',
title: 'Supabase',
icon: '/assets/company-avatar-5.png',
costs: '$599',
billingCycle: 'year',
status: 'paid',
},
{
id: 'vercel',
title: 'Vercel',
icon: '/assets/company-avatar-4.png',
costs: '$20',
billingCycle: 'month',
status: 'expiring',
},
{
id: 'auth0',
title: 'Auth0',
icon: '/assets/company-avatar-3.png',
costs: '$20-80',
billingCycle: 'month',
status: 'canceled',
},
{
id: 'google_cloud',
title: 'Google Cloud',
icon: '/assets/company-avatar-2.png',
costs: '$100-200',
billingCycle: 'month',
status: 'paid',
},
{
id: 'stripe',
title: 'Stripe',
icon: '/assets/company-avatar-1.png',
costs: '$70',
billingCycle: 'month',
status: 'paid',
},
]}
/>
</Grid>
<Grid md={4} xs={12}>
<AppChat
messages={[
{
id: 'MSG-001',
content: 'Hello, we spoke earlier on the phone',
author: { name: 'Alcides Antonio', avatar: '/assets/avatar-10.png', status: 'online' },
createdAt: dayjs().subtract(2, 'minute').toDate(),
},
{
id: 'MSG-002',
content: 'Is the job still available?',
author: { name: 'Marcus Finn', avatar: '/assets/avatar-9.png', status: 'offline' },
createdAt: dayjs().subtract(56, 'minute').toDate(),
},
{
id: 'MSG-003',
content: "What is a screening task? I'd like to",
author: { name: 'Carson Darrin', avatar: '/assets/avatar-3.png', status: 'online' },
createdAt: dayjs().subtract(3, 'hour').subtract(23, 'minute').toDate(),
},
{
id: 'MSG-004',
content: 'Still waiting for feedback',
author: { name: 'Fran Perez', avatar: '/assets/avatar-5.png', status: 'online' },
createdAt: dayjs().subtract(8, 'hour').subtract(6, 'minute').toDate(),
},
{
id: 'MSG-005',
content: 'Need more information about campaigns',
author: { name: 'Jie Yan', avatar: '/assets/avatar-8.png', status: 'offline' },
createdAt: dayjs().subtract(10, 'hour').subtract(18, 'minute').toDate(),
},
]}
/>
</Grid>
<Grid md={4} xs={12}>
<Events
events={[
{
id: 'EV-004',
title: 'Meeting with partners',
description: '17:00 to 18:00',
createdAt: dayjs().add(1, 'day').toDate(),
},
{
id: 'EV-003',
title: 'Interview with Jonas',
description: '15:30 to 16:45',
createdAt: dayjs().add(4, 'day').toDate(),
},
{
id: 'EV-002',
title: "Doctor's appointment",
description: '12:30 to 15:30',
createdAt: dayjs().add(4, 'day').toDate(),
},
{
id: 'EV-001',
title: 'Weekly meeting',
description: '09:00 to 09:30',
createdAt: dayjs().add(7, 'day').toDate(),
},
]}
/>
</Grid>
<Grid md={4} xs={12}>
<AppLimits usage={80} />
</Grid>
<Grid md={4} xs={12}>
<HelperWidget
action={
<Button color="secondary" endIcon={<ArrowRightIcon />} size="small">
Search jobs
</Button>
}
description="Search for jobs that match your skills and apply to them directly."
icon={BriefcaseIcon}
label="Jobs"
title="Find your dream job"
/>
</Grid>
<Grid md={4} xs={12}>
<HelperWidget
action={
<Button color="secondary" endIcon={<ArrowRightIcon />} size="small">
Help center
</Button>
}
description="Find answers to your questions and get in touch with our team."
icon={InfoIcon}
label="Help center"
title="Need help figuring things out?"
/>
</Grid>
<Grid md={4} xs={12}>
<HelperWidget
action={
<Button color="secondary" endIcon={<ArrowRightIcon />} size="small">
Documentation
</Button>
}
description="Learn how to get started with our product and make the most of it."
icon={FileCodeIcon}
label="Documentation"
title="Explore documentation"
/>
</Grid>
</Grid>
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,73 @@
import * as React from 'react';
import type { Metadata } from 'next';
import RouterLink from 'next/link';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { config } from '@/config';
import { paths } from '@/paths';
import { ProductEditForm } from '@/components/dashboard/product/product-edit-form';
export const metadata = { title: `Details | Products | Dashboard | ${config.site.name}` } satisfies Metadata;
// The page should load the product from the API based on the productId param and pass it to the form component.
// For the sake of simplicity, we are just using a static product object.
export default function Page(): React.JSX.Element {
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Stack spacing={3}>
<div>
<Link
color="text.primary"
component={RouterLink}
href={paths.dashboard.products.list}
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
variant="subtitle2"
>
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
Products
</Link>
</div>
<div>
<Typography variant="h4">Edit product</Typography>
</div>
</Stack>
<ProductEditForm
product={{
id: 'PRD-001',
name: 'Erbology Aloe Vera',
handle: 'healthcare-erbology',
category: 'Healthcare',
type: 'physical',
description:
'<h2>Erbology Aloe Vera is a natural, eco-friendly, and vegan product.</h2><p>It is made from natural ingredients. It is a great product for healthcare.</p>',
tags: 'Natural, Eco-Friendly, Vegan',
currency: 'USD',
price: 24,
images: [{ id: 'IMG-001', url: '/assets/product-1.png', fileName: 'product-1.png' }],
sku: '401_1BBXBK',
barcode: '',
quantity: 10,
backorder: true,
height: 25,
width: 15,
length: 5,
weight: 0.25,
}}
/>
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,48 @@
import * as React from 'react';
import type { Metadata } from 'next';
import RouterLink from 'next/link';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { config } from '@/config';
import { paths } from '@/paths';
import { ProductCreateForm } from '@/components/dashboard/product/product-create-form';
export const metadata = { title: `Create | Products | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element {
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Stack spacing={3}>
<div>
<Link
color="text.primary"
component={RouterLink}
href={paths.dashboard.products.list}
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
variant="subtitle2"
>
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
Products
</Link>
</div>
<div>
<Typography variant="h4">Create product</Typography>
</div>
</Stack>
<ProductCreateForm />
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,178 @@
import * as React from 'react';
import type { Metadata } from 'next';
import RouterLink from 'next/link';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import Divider from '@mui/material/Divider';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import { config } from '@/config';
import { paths } from '@/paths';
import { dayjs } from '@/lib/dayjs';
import { ProductModal } from '@/components/dashboard/product/product-modal';
import type { Filters } from '@/components/dashboard/product/products-filters';
import { ProductsFilters } from '@/components/dashboard/product/products-filters';
import { ProductsPagination } from '@/components/dashboard/product/products-pagination';
import { ProductsTable } from '@/components/dashboard/product/products-table';
import type { Product } from '@/components/dashboard/product/products-table';
export const metadata = { title: `List | Products | Dashboard | ${config.site.name}` } satisfies Metadata;
const products = [
{
id: 'PRD-005',
name: 'Soja & Co. Eucalyptus',
image: '/assets/product-5.png',
category: 'Skincare',
type: 'physical',
quantity: 10,
currency: 'USD',
price: 65.99,
sku: '592_LDKDI',
status: 'draft',
createdAt: dayjs().subtract(23, 'minute').toDate(),
},
{
id: 'PRD-004',
name: 'Necessaire Body Lotion',
image: '/assets/product-4.png',
category: 'Skincare',
type: 'physical',
quantity: 5,
currency: 'USD',
price: 17.99,
sku: '321_UWEAJT',
status: 'published',
createdAt: dayjs().subtract(5, 'minute').subtract(1, 'hour').toDate(),
},
{
id: 'PRD-003',
name: 'Ritual of Sakura',
image: '/assets/product-3.png',
category: 'Skincare',
type: 'physical',
quantity: 8,
currency: 'USD',
price: 155,
sku: '211_QFEXJO',
status: 'draft',
createdAt: dayjs().subtract(43, 'minute').subtract(3, 'hour').toDate(),
},
{
id: 'PRD-002',
name: 'Lancome Rouge',
image: '/assets/product-2.png',
category: 'Makeup',
type: 'physical',
quantity: 0,
currency: 'USD',
price: 95,
sku: '978_UBFGJC',
status: 'published',
createdAt: dayjs().subtract(15, 'minute').subtract(4, 'hour').toDate(),
},
{
id: 'PRD-001',
name: 'Erbology Aloe Vera',
image: '/assets/product-1.png',
category: 'Healthcare',
type: 'physical',
quantity: 10,
currency: 'USD',
price: 24,
sku: '401_1BBXBK',
status: 'published',
createdAt: dayjs().subtract(39, 'minute').subtract(7, 'hour').toDate(),
},
] satisfies Product[];
interface PageProps {
searchParams: { category?: string; previewId?: string; sortDir?: 'asc' | 'desc'; sku?: string; status?: string };
}
export default function Page({ searchParams }: PageProps): React.JSX.Element {
const { category, previewId, sortDir, sku, status } = searchParams;
const orderedProducts = applySort(products, sortDir);
const filteredProducts = applyFilters(orderedProducts, { category, sku, status });
return (
<React.Fragment>
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
<Box sx={{ flex: '1 1 auto' }}>
<Typography variant="h4">Products</Typography>
</Box>
<div>
<Button
component={RouterLink}
href={paths.dashboard.products.create}
startIcon={<PlusIcon />}
variant="contained"
>
Add
</Button>
</div>
</Stack>
<Card>
<ProductsFilters filters={{ category, sku, status }} sortDir={sortDir} />
<Divider />
<Box sx={{ overflowX: 'auto' }}>
<ProductsTable rows={filteredProducts} />
</Box>
<Divider />
<ProductsPagination count={filteredProducts.length} page={0} />
</Card>
</Stack>
</Box>
<ProductModal open={Boolean(previewId)} />
</React.Fragment>
);
}
// Sorting and filtering has to be done on the server.
function applySort(row: Product[], sortDir: 'asc' | 'desc' | undefined): Product[] {
return row.sort((a, b) => {
if (sortDir === 'asc') {
return a.createdAt.getTime() - b.createdAt.getTime();
}
return b.createdAt.getTime() - a.createdAt.getTime();
});
}
function applyFilters(row: Product[], { category, status, sku }: Filters): Product[] {
return row.filter((item) => {
if (category) {
if (item.category !== category) {
return false;
}
}
if (status) {
if (item.status !== status) {
return false;
}
}
if (sku) {
if (!item.sku?.toLowerCase().includes(sku.toLowerCase())) {
return false;
}
}
return true;
});
}

View File

@@ -0,0 +1,28 @@
import * as React from 'react';
import type { Metadata } from 'next';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { config } from '@/config';
import { AccountDetails } from '@/components/dashboard/settings/account-details';
import { DeleteAccount } from '@/components/dashboard/settings/delete-account';
import { Privacy } from '@/components/dashboard/settings/privacy';
import { ThemeSwitch } from '@/components/dashboard/settings/theme-switch';
export const metadata = { title: `Account | Settings | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element {
return (
<Stack spacing={4}>
<div>
<Typography variant="h4">Account</Typography>
</div>
<Stack spacing={4}>
<AccountDetails />
<ThemeSwitch />
<Privacy />
<DeleteAccount />
</Stack>
</Stack>
);
}

View File

@@ -0,0 +1,31 @@
import * as React from 'react';
import type { Metadata } from 'next';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { config } from '@/config';
import { dayjs } from '@/lib/dayjs';
import { Invoices } from '@/components/dashboard/settings/invoices';
import { Plans } from '@/components/dashboard/settings/plans';
export const metadata = { title: `Billing | Settings | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element {
return (
<Stack spacing={4}>
<div>
<Typography variant="h4">Billing & plans</Typography>
</div>
<Stack spacing={4}>
<Plans />
<Invoices
invoices={[
{ id: 'INV-003', currency: 'USD', totalAmount: 14.99, issueDate: dayjs().subtract(1, 'month').toDate() },
{ id: 'INV-002', currency: 'USD', totalAmount: 14.99, issueDate: dayjs().subtract(2, 'months').toDate() },
{ id: 'INV-001', currency: 'USD', totalAmount: 14.99, issueDate: dayjs().subtract(3, 'months').toDate() },
]}
/>
</Stack>
</Stack>
);
}

View File

@@ -0,0 +1,51 @@
import * as React from 'react';
import type { Metadata } from 'next';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { config } from '@/config';
import { Integrations } from '@/components/dashboard/settings/integrations';
export const metadata = { title: `Integrations | Settings | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element {
return (
<Stack spacing={4}>
<div>
<Typography variant="h4">Integrations</Typography>
</div>
<Integrations
integrations={[
{
id: 'vercel',
name: 'Vercel',
icon: '/assets/company-avatar-4.png',
description: 'See your usage and manage your apps',
installed: false,
},
{
id: 'auth0',
name: 'Auth0',
icon: '/assets/company-avatar-3.png',
description: 'Manage your users and roles with Auth0',
installed: false,
},
{
id: 'google_calendar',
name: 'Google Calendar',
icon: '/assets/company-avatar-2.png',
description: 'Add your personal calendar right into the app',
installed: false,
},
{
id: 'stripe',
name: 'Stripe',
icon: '/assets/company-avatar-1.png',
description: 'See your Stripe balance and manage your products',
installed: false,
},
]}
/>
</Stack>
);
}

View File

@@ -0,0 +1,27 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import { SideNav } from '@/components/dashboard/settings/side-nav';
interface LayoutProps {
children: React.ReactNode;
}
export default function Layout({ children }: LayoutProps): React.JSX.Element {
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack direction={{ xs: 'column', md: 'row' }} spacing={4} sx={{ position: 'relative' }}>
<SideNav />
<Box sx={{ flex: '1 1 auto', minWidth: 0 }}>{children}</Box>
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,24 @@
import * as React from 'react';
import type { Metadata } from 'next';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { config } from '@/config';
import { EmailNotifications } from '@/components/dashboard/settings/email-notifications';
import { PhoneNotifications } from '@/components/dashboard/settings/phone-notifications';
export const metadata = { title: `Notifications | Settings | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element {
return (
<Stack spacing={4}>
<div>
<Typography variant="h4">Notifications</Typography>
</div>
<Stack spacing={4}>
<EmailNotifications />
<PhoneNotifications />
</Stack>
</Stack>
);
}

View File

@@ -0,0 +1,44 @@
import * as React from 'react';
import type { Metadata } from 'next';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { config } from '@/config';
import { dayjs } from '@/lib/dayjs';
import { LoginHistory } from '@/components/dashboard/settings/login-history';
import { MultiFactor } from '@/components/dashboard/settings/multi-factor';
import { PasswordForm } from '@/components/dashboard/settings/password-form';
export const metadata = { title: `Security | Settings | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element {
return (
<Stack spacing={4}>
<div>
<Typography variant="h4">Security</Typography>
</div>
<Stack spacing={4}>
<PasswordForm />
<MultiFactor />
<LoginHistory
events={[
{
id: 'EV-002',
type: 'Credential login',
ip: '95.130.17.84',
userAgent: 'Chrome, Mac OS 10.15.7',
createdAt: dayjs().subtract(1, 'day').subtract(1, 'hour').subtract(5, 'minute').toDate(),
},
{
id: 'EV-001',
type: 'Credential login',
ip: '95.130.17.84',
userAgent: 'Chrome, Mac OS 10.15.7',
createdAt: dayjs().subtract(1, 'day').subtract(1, 'hour').subtract(25, 'minute').toDate(),
},
]}
/>
</Stack>
</Stack>
);
}

View File

@@ -0,0 +1,37 @@
import * as React from 'react';
import type { Metadata } from 'next';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { config } from '@/config';
import { Members } from '@/components/dashboard/settings/members';
export const metadata = { title: `Team | Settings | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element {
return (
<Stack spacing={4}>
<div>
<Typography variant="h4">Team</Typography>
</div>
<Members
members={[
{
id: 'USR-000',
name: 'Sofia Rivers',
avatar: '/assets/avatar.png',
email: 'sofia@devias.io',
role: 'Owner',
},
{
id: 'USR-002',
name: 'Siegbert Gottfried',
avatar: '/assets/avatar-2.png',
email: 'siegbert.gottfried@domain.com',
role: 'Standard',
},
]}
/>
</Stack>
);
}

View File

@@ -0,0 +1,103 @@
import * as React from 'react';
import type { Metadata } from 'next';
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { config } from '@/config';
import { dayjs } from '@/lib/dayjs';
import { PostAdd } from '@/components/dashboard/social/post-add';
import { PostCard } from '@/components/dashboard/social/post-card';
import type { Post } from '@/components/dashboard/social/types';
export const metadata = { title: `Feed | Social | Dashboard | ${config.site.name}` } satisfies Metadata;
const posts = [
{
id: 'POST-003',
author: { name: 'Sofia Rivers', avatar: '/assets/avatar.png' },
content: "Hey! What's your favorite framework?",
comments: [
{
id: 'MSG-003',
content: "We use React at work, but I'm a fan of Web Components",
author: { name: 'Nasimiyu Danai', avatar: '/assets/avatar-7.png' },
createdAt: dayjs().subtract(4, 'minute').toDate(),
},
],
createdAt: dayjs().subtract(16, 'minute').toDate(),
isLiked: true,
likes: 1,
},
{
id: 'POST-002',
author: { name: 'Siegbert Gottfried', avatar: '/assets/avatar-2.png' },
content: "What's the most important skill a developer should have?",
comments: [
{
id: 'MSG-004',
content: 'I think it is being able to communicate clearly',
author: { name: 'Iulia Albu', avatar: '/assets/avatar-6.png' },
createdAt: dayjs().subtract(35, 'minute').toDate(),
},
{
id: 'MSG-005',
content: 'Patience definitely',
author: { name: 'Fran Perez', avatar: '/assets/avatar-5.png' },
createdAt: dayjs().subtract(1, 'hour').toDate(),
},
],
isLiked: true,
likes: 6,
createdAt: dayjs().subtract(7, 'hour').toDate(),
},
{
id: 'POST-001',
author: { name: 'Sofia Rivers', avatar: '/assets/avatar.png' },
content: 'Staying focused on the goal is key to success',
media: '/assets/image-business-2.png',
comments: [
{
id: 'MSG-001',
content: "I agree, it's easy to get lost in the details",
author: { name: 'Jie Yan', avatar: '/assets/avatar-8.png' },
createdAt: dayjs().subtract(1, 'hour').toDate(),
},
{
id: 'MSG-002',
content: 'Absolutely!',
author: { name: 'Penjani Inyene', avatar: '/assets/avatar-4.png' },
createdAt: dayjs().subtract(2, 'hour').toDate(),
},
],
createdAt: dayjs().subtract(4, 'hour').toDate(),
isLiked: false,
likes: 4,
},
] satisfies Post[];
export default function Page(): React.JSX.Element {
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Stack spacing={1}>
<Typography color="text.secondary" variant="overline">
Social Feed
</Typography>
<Typography variant="h4">Here&apos;s what your connections posted</Typography>
</Stack>
<PostAdd />
{posts.map((post) => (
<PostCard key={post.id} post={post} />
))}
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,56 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import Card from '@mui/material/Card';
import CardHeader from '@mui/material/CardHeader';
import Divider from '@mui/material/Divider';
import Input from '@mui/material/Input';
import InputAdornment from '@mui/material/InputAdornment';
import Grid from '@mui/material/Unstable_Grid2';
import { MagnifyingGlass as MagnifyingGlassIcon } from '@phosphor-icons/react/dist/ssr/MagnifyingGlass';
import { ConnectionCard } from '@/components/dashboard/social/connection-card';
import type { Connection } from '@/components/dashboard/social/connection-card';
const connections = [
{
id: 'USR-010',
name: 'Alcides Antonio',
avatar: '/assets/avatar-10.png',
commonContacts: 5,
status: 'not_connected',
},
{ id: 'USR-003', name: 'Carson Darrin', avatar: '/assets/avatar-3.png', commonContacts: 10, status: 'rejected' },
{ id: 'USR-005', name: 'Fran Perez', avatar: '/assets/avatar-5.png', commonContacts: 8, status: 'pending' },
{ id: 'USR-004', name: 'Penjani Inyene', avatar: '/assets/avatar-4.png', commonContacts: 1, status: 'connected' },
] satisfies Connection[];
export default function Page(): React.JSX.Element {
return (
<div>
<Card>
<CardHeader title="Connections" />
<Divider />
<Input
fullWidth
placeholder="Search connections"
startAdornment={
<InputAdornment position="start">
<MagnifyingGlassIcon />
</InputAdornment>
}
sx={{ px: 3, py: 2 }}
/>
<Divider />
<Box sx={{ p: 3 }}>
<Grid container spacing={3}>
{connections.map((connection) => (
<Grid key={connection.id} md={6} xs={12}>
<ConnectionCard connection={connection} />
</Grid>
))}
</Grid>
</Box>
</Card>
</div>
);
}

View File

@@ -0,0 +1,101 @@
import * as React from 'react';
import type { Metadata } from 'next';
import Avatar from '@mui/material/Avatar';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import Stack from '@mui/material/Stack';
import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography';
import { ChatText as ChatTextIcon } from '@phosphor-icons/react/dist/ssr/ChatText';
import { DotsThree as DotsThreeIcon } from '@phosphor-icons/react/dist/ssr/DotsThree';
import { Image as ImageIcon } from '@phosphor-icons/react/dist/ssr/Image';
import { UserPlus as UserPlusIcon } from '@phosphor-icons/react/dist/ssr/UserPlus';
import { config } from '@/config';
import { ProfileTabs } from '@/components/dashboard/social/profile-tabs';
export const metadata = { title: `Profile | Social | Dashboard | ${config.site.name}` } satisfies Metadata;
interface LayoutProps {
children: React.ReactNode;
}
export default function Layout({ children }: LayoutProps): React.JSX.Element {
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Stack spacing={4}>
<Box
sx={{
backgroundImage: `url(/assets/image-abstract-2.png)`,
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
backgroundSize: 'cover',
borderRadius: 1,
height: '348px',
position: 'relative',
}}
>
<Box
sx={{
alignItems: 'flex-end',
bottom: 0,
display: 'flex',
justifyContent: 'flex-end',
left: 0,
opacity: 0,
p: 3,
position: 'absolute',
right: 0,
top: 0,
'&:hover': { opacity: '100%' },
}}
>
<Button color="secondary" startIcon={<ImageIcon />} variant="contained">
Change cover
</Button>
</Box>
</Box>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', justifyContent: 'space-between' }}>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center' }}>
<Avatar src="/assets/avatar.png" sx={{ '--Avatar-size': '64px' }} />
<div>
<Typography variant="h6">Sofia Rivers</Typography>
<Typography color="text.secondary" variant="overline">
Product Designer
</Typography>
</div>
</Stack>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center' }}>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', display: { md: 'flex', xs: 'none' } }}>
<Button color="secondary" size="small" startIcon={<UserPlusIcon />}>
Connect
</Button>
<Button size="small" startIcon={<ChatTextIcon />} variant="contained">
Message
</Button>
</Stack>
<Tooltip title="More options">
<IconButton>
<DotsThreeIcon weight="bold" />
</IconButton>
</Tooltip>
</Stack>
</Stack>
</Stack>
<Stack spacing={4}>
<ProfileTabs />
{children}
</Stack>
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,89 @@
import * as React from 'react';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import CardHeader from '@mui/material/CardHeader';
import LinearProgress from '@mui/material/LinearProgress';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import { dayjs } from '@/lib/dayjs';
import { About } from '@/components/dashboard/social/about';
import { PostAdd } from '@/components/dashboard/social/post-add';
import { PostCard } from '@/components/dashboard/social/post-card';
import type { Post } from '@/components/dashboard/social/types';
const posts = [
{
id: 'POST-003',
author: { name: 'Sofia Rivers', avatar: '/assets/avatar.png' },
content: "Hey! What's your favorite framework?",
comments: [
{
id: 'MSG-003',
content: "We use React at work, but I'm a fan of Web Components",
author: { name: 'Nasimiyu Danai', avatar: '/assets/avatar-7.png' },
createdAt: dayjs().subtract(4, 'minute').toDate(),
},
],
createdAt: dayjs().subtract(16, 'minute').toDate(),
isLiked: true,
likes: 1,
},
{
id: 'POST-001',
author: { name: 'Sofia Rivers', avatar: '/assets/avatar.png' },
content: 'Staying focused on the goal is key to success',
media: '/assets/image-business-2.png',
comments: [
{
id: 'MSG-001',
content: "I agree, it's easy to get lost in the details",
author: { name: 'Jie Yan', avatar: '/assets/avatar-8.png' },
createdAt: dayjs().subtract(1, 'hour').toDate(),
},
{
id: 'MSG-002',
content: 'Absolutely!',
author: { name: 'Penjani Inyene', avatar: '/assets/avatar-4.png' },
createdAt: dayjs().subtract(2, 'hour').toDate(),
},
],
createdAt: dayjs().subtract(4, 'hour').toDate(),
isLiked: false,
likes: 4,
},
] satisfies Post[];
export default function Page(): React.JSX.Element {
return (
<div>
<Grid container spacing={4}>
<Grid lg={4} xs={12}>
<Stack spacing={4}>
<Card>
<CardHeader title="Profile progress" />
<CardContent>
<Stack spacing={2}>
<LinearProgress value={50} variant="determinate" />
<Typography color="text.secondary" variant="subtitle2">
50% set up complete
</Typography>
</Stack>
</CardContent>
</Card>
<About />
</Stack>
</Grid>
<Grid lg={8} xs={12}>
<Stack spacing={4}>
<PostAdd />
{posts.map((post) => (
<PostCard key={post.id} post={post} />
))}
</Stack>
</Grid>
</Grid>
</div>
);
}

View File

@@ -0,0 +1,125 @@
import * as React from 'react';
import type { Metadata } from 'next';
import { config } from '@/config';
import { dayjs } from '@/lib/dayjs';
import { TasksProvider } from '@/components/dashboard/tasks/tasks-context';
import { TasksView } from '@/components/dashboard/tasks/tasks-view';
import type { Column, Task } from '@/components/dashboard/tasks/types';
export const metadata = { title: `Tasks | Dashboard | ${config.site.name}` } satisfies Metadata;
const columns = [
{ id: 'COL-001', name: 'Todo', taskIds: ['TSK-001', 'TSK-002'] },
{ id: 'COL-002', name: 'Progress', taskIds: [] },
{ id: 'COL-003', name: 'Done', taskIds: ['TSK-003', 'TSK-004'] },
] satisfies Column[];
const tasks = [
{
id: 'TSK-001',
author: { id: 'USR-000', name: 'Sofia Rivers', username: 'sofia.rivers', avatar: '/assets/avatar.png' },
title: 'Update the customer API for payments',
description: 'Stripe has a new API version, we need to update it to the latest version',
columnId: 'COL-001',
createdAt: dayjs().subtract(8, 'day').toDate(),
labels: ['Business', 'Design'],
subscribed: true,
dueDate: dayjs().add(7, 'day').toDate(),
assignees: [{ id: 'USR-007', name: 'Nasimiyu Danai', username: 'nasimiyu.danai', avatar: '/assets/avatar-7.png' }],
attachments: [
{
id: 'ATT-001',
name: 'image-abstract-1.png',
extension: 'png',
url: '/assets/image-abstract-1.png',
size: '24.8 KB',
},
],
subtasks: [
{ id: 'STSK-001', title: 'Create a logo', done: true },
{ id: 'STSK-002', title: 'Create text styles', done: true },
{ id: 'STSK-003', title: 'Create color styles', done: true },
{ id: 'STSK-004', title: 'Create effect styles', done: false },
{ id: 'STSK-005', title: 'Create multiple elements', done: false },
],
comments: [
{
id: 'MSG-001',
author: { id: 'USR-007', name: 'Nasimiyu Danai', username: 'nasimiyu.danai', avatar: '/assets/avatar-7.png' },
createdAt: dayjs().subtract(5, 'day').toDate(),
content: 'Hi, I have updated the API to the latest version.',
comments: [
{
id: 'MSG-002',
author: { id: 'USR-000', name: 'Sofia Rivers', username: 'sofia.rivers', avatar: '/assets/avatar.png' },
createdAt: dayjs().subtract(4, 'day').toDate(),
content: 'Great! Thanks for the update.',
},
],
},
],
},
{
id: 'TSK-002',
author: { id: 'USR-005', name: 'Fran Perez', username: 'fran.perez', avatar: '/assets/avatar-5.png' },
title: 'Fix the responsive issues on the home page',
description: 'On mobile devices it looks a bit off, need to fix it',
columnId: 'COL-001',
createdAt: dayjs().subtract(2, 'day').toDate(),
dueDate: dayjs().add(6, 'day').toDate(),
subscribed: true,
assignees: [
{ id: 'USR-005', name: 'Fran Perez', username: 'fran.perez', avatar: '/assets/avatar-5.png' },
{ id: 'USR-007', name: 'Nasimiyu Danai', username: 'nasimiyu.danai', avatar: '/assets/avatar-7.png' },
],
attachments: [],
subtasks: [],
comments: [],
},
{
id: 'TSK-003',
author: { id: 'USR-009', name: 'Marcus Finn', username: 'marcus.finn', avatar: '/assets/avatar-9.png' },
title: 'Setup onboarding tour for new users',
columnId: 'COL-003',
createdAt: dayjs().subtract(3, 'day').toDate(),
assignees: [
{ id: 'USR-003', name: 'Carson Darrin', username: 'carson.darrin', avatar: '/assets/avatar-3.png' },
{ id: 'USR-000', name: 'Sofia Rivers', username: 'sofia.rivers', avatar: '/assets/avatar.png' },
],
attachments: [],
subtasks: [
{ id: 'STSK-006', title: 'Create a new user flow', done: true },
{ id: 'STSK-008', title: 'Create an organization settings step', done: false },
],
comments: [],
},
{
id: 'TSK-004',
author: { id: 'USR-003', name: 'Carson Darrin', username: 'carson.darrin', avatar: '/assets/avatar-3.png' },
title: 'Follow up with Devias team',
description: 'We need to finish the project as soon as possible',
columnId: 'COL-003',
createdAt: dayjs().subtract(3, 'day').toDate(),
subscribed: true,
assignees: [{ id: 'USR-009', name: 'Marcus Finn', username: 'marcus.finn', avatar: '/assets/avatar-9.png' }],
attachments: [],
subtasks: [],
comments: [
{
id: 'MSG-003',
author: { id: 'USR-003', name: 'Carson Darrin', username: 'carson.darrin', avatar: '/assets/avatar-3.png' },
createdAt: dayjs().subtract(2, 'day').toDate(),
content: 'Marcus, can you please follow up with the Devias team?',
},
],
},
] satisfies Task[];
export default function Page(): React.JSX.Element {
return (
<TasksProvider columns={columns} tasks={tasks}>
<TasksView />
</TasksProvider>
);
}