build ok,
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
114
002_source/cms/src/app/dashboard/academy/page.tsx
Normal file
114
002_source/cms/src/app/dashboard/academy/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
125
002_source/cms/src/app/dashboard/analytics/page.tsx
Normal file
125
002_source/cms/src/app/dashboard/analytics/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
38
002_source/cms/src/app/dashboard/blank/page.tsx
Normal file
38
002_source/cms/src/app/dashboard/blank/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
158
002_source/cms/src/app/dashboard/blog/[postId]/page.tsx
Normal file
158
002_source/cms/src/app/dashboard/blog/[postId]/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
79
002_source/cms/src/app/dashboard/blog/create/page.tsx
Normal file
79
002_source/cms/src/app/dashboard/blog/create/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
141
002_source/cms/src/app/dashboard/blog/page.tsx
Normal file
141
002_source/cms/src/app/dashboard/blog/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
96
002_source/cms/src/app/dashboard/calendar/page.tsx
Normal file
96
002_source/cms/src/app/dashboard/calendar/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
@@ -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} />;
|
||||
}
|
11
002_source/cms/src/app/dashboard/chat/compose/page.tsx
Normal file
11
002_source/cms/src/app/dashboard/chat/compose/page.tsx
Normal 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 />;
|
||||
}
|
211
002_source/cms/src/app/dashboard/chat/layout.tsx
Normal file
211
002_source/cms/src/app/dashboard/chat/layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
32
002_source/cms/src/app/dashboard/chat/page.tsx
Normal file
32
002_source/cms/src/app/dashboard/chat/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
151
002_source/cms/src/app/dashboard/crypto/page.tsx
Normal file
151
002_source/cms/src/app/dashboard/crypto/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
308
002_source/cms/src/app/dashboard/customers/[customerId]/page.tsx
Normal file
308
002_source/cms/src/app/dashboard/customers/[customerId]/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
48
002_source/cms/src/app/dashboard/customers/create/page.tsx
Normal file
48
002_source/cms/src/app/dashboard/customers/create/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
155
002_source/cms/src/app/dashboard/customers/page.tsx
Normal file
155
002_source/cms/src/app/dashboard/customers/page.tsx
Normal 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;
|
||||
});
|
||||
}
|
157
002_source/cms/src/app/dashboard/e-commerce/page.tsx
Normal file
157
002_source/cms/src/app/dashboard/e-commerce/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
228
002_source/cms/src/app/dashboard/file-storage/page.tsx
Normal file
228
002_source/cms/src/app/dashboard/file-storage/page.tsx
Normal 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;
|
||||
});
|
||||
}
|
202
002_source/cms/src/app/dashboard/invoices/[invoiceId]/page.tsx
Normal file
202
002_source/cms/src/app/dashboard/invoices/[invoiceId]/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
48
002_source/cms/src/app/dashboard/invoices/create/page.tsx
Normal file
48
002_source/cms/src/app/dashboard/invoices/create/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
175
002_source/cms/src/app/dashboard/invoices/page.tsx
Normal file
175
002_source/cms/src/app/dashboard/invoices/page.tsx
Normal 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;
|
||||
});
|
||||
}
|
@@ -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>
|
||||
);
|
||||
}
|
@@ -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>
|
||||
);
|
||||
}
|
@@ -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>
|
||||
);
|
||||
}
|
@@ -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>
|
||||
);
|
||||
}
|
@@ -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>
|
||||
);
|
||||
}
|
@@ -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>
|
||||
);
|
||||
}
|
35
002_source/cms/src/app/dashboard/jobs/create/page.tsx
Normal file
35
002_source/cms/src/app/dashboard/jobs/create/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
188
002_source/cms/src/app/dashboard/jobs/page.tsx
Normal file
188
002_source/cms/src/app/dashboard/jobs/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
16
002_source/cms/src/app/dashboard/layout.tsx
Normal file
16
002_source/cms/src/app/dashboard/layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
56
002_source/cms/src/app/dashboard/logistics/fleet/page.tsx
Normal file
56
002_source/cms/src/app/dashboard/logistics/fleet/page.tsx
Normal 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} />;
|
||||
}
|
111
002_source/cms/src/app/dashboard/logistics/page.tsx
Normal file
111
002_source/cms/src/app/dashboard/logistics/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
@@ -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} />;
|
||||
}
|
142
002_source/cms/src/app/dashboard/mail/[labelId]/layout.tsx
Normal file
142
002_source/cms/src/app/dashboard/mail/[labelId]/layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
11
002_source/cms/src/app/dashboard/mail/[labelId]/page.tsx
Normal file
11
002_source/cms/src/app/dashboard/mail/[labelId]/page.tsx
Normal 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 />;
|
||||
}
|
270
002_source/cms/src/app/dashboard/orders/[orderId]/page.tsx
Normal file
270
002_source/cms/src/app/dashboard/orders/[orderId]/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
48
002_source/cms/src/app/dashboard/orders/create/page.tsx
Normal file
48
002_source/cms/src/app/dashboard/orders/create/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
159
002_source/cms/src/app/dashboard/orders/page.tsx
Normal file
159
002_source/cms/src/app/dashboard/orders/page.tsx
Normal 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;
|
||||
});
|
||||
}
|
236
002_source/cms/src/app/dashboard/page.tsx
Normal file
236
002_source/cms/src/app/dashboard/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
@@ -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>
|
||||
);
|
||||
}
|
48
002_source/cms/src/app/dashboard/products/create/page.tsx
Normal file
48
002_source/cms/src/app/dashboard/products/create/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
178
002_source/cms/src/app/dashboard/products/page.tsx
Normal file
178
002_source/cms/src/app/dashboard/products/page.tsx
Normal 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;
|
||||
});
|
||||
}
|
28
002_source/cms/src/app/dashboard/settings/account/page.tsx
Normal file
28
002_source/cms/src/app/dashboard/settings/account/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
31
002_source/cms/src/app/dashboard/settings/billing/page.tsx
Normal file
31
002_source/cms/src/app/dashboard/settings/billing/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
@@ -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>
|
||||
);
|
||||
}
|
27
002_source/cms/src/app/dashboard/settings/layout.tsx
Normal file
27
002_source/cms/src/app/dashboard/settings/layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
@@ -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>
|
||||
);
|
||||
}
|
44
002_source/cms/src/app/dashboard/settings/security/page.tsx
Normal file
44
002_source/cms/src/app/dashboard/settings/security/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
37
002_source/cms/src/app/dashboard/settings/team/page.tsx
Normal file
37
002_source/cms/src/app/dashboard/settings/team/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
103
002_source/cms/src/app/dashboard/social/feed/page.tsx
Normal file
103
002_source/cms/src/app/dashboard/social/feed/page.tsx
Normal 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's what your connections posted</Typography>
|
||||
</Stack>
|
||||
<PostAdd />
|
||||
{posts.map((post) => (
|
||||
<PostCard key={post.id} post={post} />
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
@@ -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>
|
||||
);
|
||||
}
|
101
002_source/cms/src/app/dashboard/social/profile/layout.tsx
Normal file
101
002_source/cms/src/app/dashboard/social/profile/layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
89
002_source/cms/src/app/dashboard/social/profile/page.tsx
Normal file
89
002_source/cms/src/app/dashboard/social/profile/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
125
002_source/cms/src/app/dashboard/tasks/page.tsx
Normal file
125
002_source/cms/src/app/dashboard/tasks/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user