implemented identity, updated session, added 'workings' list
added paginated querying, added 'common'
This commit is contained in:
parent
027deba422
commit
6ada519e06
36 changed files with 1422 additions and 279 deletions
159
app/client/components/Editor.tsx
Normal file
159
app/client/components/Editor.tsx
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
import { Card, Heading, SystemStyleObject } from '@chakra-ui/react';
|
||||
import EditorJS, { EditorConfig } from '@editorjs/editorjs';
|
||||
import Header from '@editorjs/header';
|
||||
import List from '@editorjs/list';
|
||||
import Quote from '@editorjs/quote';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
// @ts-ignore
|
||||
import Link from '@coolbytes/editorjs-link';
|
||||
|
||||
export function Editor({ initialData }: { initialData?: EditorConfig['data'] }) {
|
||||
const ejInstance = useRef<EditorJS | undefined | null>();
|
||||
|
||||
const initEditor = () => {
|
||||
const editor = new EditorJS({
|
||||
holder: 'editorjs',
|
||||
onReady: () => {
|
||||
ejInstance.current = editor;
|
||||
},
|
||||
autofocus: true,
|
||||
data: initialData,
|
||||
// onChange: async () => {
|
||||
// const content = await editor.saver.save();
|
||||
// },
|
||||
tools: {
|
||||
header: Header,
|
||||
list: List,
|
||||
link: Link,
|
||||
quote: Quote
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (ejInstance.current === null) {
|
||||
initEditor();
|
||||
}
|
||||
|
||||
return () => {
|
||||
ejInstance?.current?.destroy();
|
||||
ejInstance.current = null;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card.Root
|
||||
variant={'subtle'}
|
||||
width="100%"
|
||||
maxWidth="840px"
|
||||
minHeight="sm"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Card.Header>
|
||||
<Heading textAlign="center">Editor</Heading>
|
||||
</Card.Header>
|
||||
<Card.Body
|
||||
width="90%"
|
||||
css={{
|
||||
'& *': {
|
||||
fontSize: 'revert',
|
||||
fontWeight: 'revert'
|
||||
},
|
||||
...editorJsStyles
|
||||
}}
|
||||
>
|
||||
<div className="editorjs" id="editorjs"></div>
|
||||
</Card.Body>
|
||||
</Card.Root>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const editorJsStyles:
|
||||
| SystemStyleObject
|
||||
| Omit<Array<SystemStyleObject | undefined>, keyof any[]>
|
||||
| undefined = {
|
||||
'& .ce-block--selected .ce-block__content, \
|
||||
& .ce-inline-toolbar, \
|
||||
& .codex-editor--narrow .ce-toolbox, \
|
||||
& .ce-conversion-toolbar, \
|
||||
& .ce-settings, \
|
||||
& .ce-settings__button, \
|
||||
& .ce-toolbar__settings-btn, \
|
||||
& .cdx-button, \
|
||||
& .ce-popover': {
|
||||
background: 'colorPalette.500',
|
||||
border: 'none'
|
||||
},
|
||||
|
||||
'& .ce-inline-tool, \
|
||||
& .ce-conversion-toolbar__label, \
|
||||
& .ce-toolbox__button, \
|
||||
& .cdx-settings-button, \
|
||||
& .ce-toolbar__plus': {
|
||||
color: 'colorPalette.500'
|
||||
},
|
||||
|
||||
'& ::selection': {
|
||||
background: 'colorPalette.500'
|
||||
},
|
||||
|
||||
'& .cdx-settings-button:hover, \
|
||||
& .ce-settings__button:hover, \
|
||||
& .ce-toolbox__button--active, \
|
||||
& .ce-toolbox__button:hover, \
|
||||
& .cdx-button:hover, \
|
||||
& .ce-inline-toolbar__dropdown:hover, \
|
||||
& .ce-inline-tool:hover, \
|
||||
& .ce-popover__item:hover, \
|
||||
& .ce-toolbar__settings-btn:hover': {
|
||||
background: 'colorPalette.500',
|
||||
color: 'fg'
|
||||
},
|
||||
|
||||
'& .cdx-notify--error': {
|
||||
background: 'red !important'
|
||||
},
|
||||
|
||||
'& .cdx-notify__cross::after, & .cdx-notify__cross::before': {
|
||||
background: 'white'
|
||||
},
|
||||
|
||||
'& .ce-toolbar__plus:hover, \
|
||||
& .ce-popover__container': {
|
||||
backgroundColor: 'bg.emphasized',
|
||||
color: 'colorPalette.500',
|
||||
border: 'none'
|
||||
},
|
||||
|
||||
'& .ce-popover-item:hover:not(.ce-popover-item--no-hover)': {
|
||||
backgroundColor: 'colorPalette.500',
|
||||
color: 'fg'
|
||||
},
|
||||
|
||||
'& .cdx-search-field': {
|
||||
background: 'bg.muted',
|
||||
color: 'fg',
|
||||
border: 'none'
|
||||
},
|
||||
|
||||
'& .ce-popover-item__title, \
|
||||
& .ce-popover-item__icon': {
|
||||
color: 'fg'
|
||||
},
|
||||
|
||||
'& .ce-popover-item-separator': {
|
||||
color: 'bg.muted'
|
||||
},
|
||||
|
||||
'& .ce-toolbar__settings-btn': {
|
||||
color: 'white'
|
||||
},
|
||||
|
||||
'& .ce-toolbar__settings-btn:hover': {
|
||||
color: 'black'
|
||||
}
|
||||
};
|
||||
44
app/client/components/Pagination.tsx
Normal file
44
app/client/components/Pagination.tsx
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { ButtonGroup, Pagination as ChakraPagination, IconButton } from '@chakra-ui/react';
|
||||
import { LuChevronLeft, LuChevronRight } from 'react-icons/lu';
|
||||
|
||||
export function Pagination({
|
||||
total,
|
||||
page,
|
||||
setPage,
|
||||
pageSize = 5
|
||||
}: {
|
||||
total: number;
|
||||
page: number;
|
||||
setPage: (value: number) => void;
|
||||
pageSize?: number;
|
||||
}) {
|
||||
return (
|
||||
<ChakraPagination.Root
|
||||
count={total}
|
||||
page={page}
|
||||
pageSize={pageSize}
|
||||
onPageChange={e => setPage(e.page)}
|
||||
defaultPage={1}
|
||||
>
|
||||
<ButtonGroup variant="ghost" size="sm">
|
||||
<ChakraPagination.PrevTrigger asChild>
|
||||
<IconButton>
|
||||
<LuChevronLeft />
|
||||
</IconButton>
|
||||
</ChakraPagination.PrevTrigger>
|
||||
|
||||
<ChakraPagination.Items
|
||||
render={page => (
|
||||
<IconButton variant={{ base: 'ghost', _selected: 'solid' }}>{page.value}</IconButton>
|
||||
)}
|
||||
/>
|
||||
|
||||
<ChakraPagination.NextTrigger asChild>
|
||||
<IconButton>
|
||||
<LuChevronRight />
|
||||
</IconButton>
|
||||
</ChakraPagination.NextTrigger>
|
||||
</ButtonGroup>
|
||||
</ChakraPagination.Root>
|
||||
);
|
||||
}
|
||||
14
app/client/components/WorkingList.tsx
Normal file
14
app/client/components/WorkingList.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { List } from '@chakra-ui/react';
|
||||
import { WorkingCardProps, WorkingListCard } from './WorkingListCard';
|
||||
|
||||
export function WorkingList({ workingsList }: { workingsList: WorkingCardProps[] }) {
|
||||
return (
|
||||
<List.Root variant="plain" gap={4}>
|
||||
{workingsList.map(workingData => (
|
||||
<List.Item key={workingData.id}>
|
||||
<WorkingListCard {...workingData} />
|
||||
</List.Item>
|
||||
))}
|
||||
</List.Root>
|
||||
);
|
||||
}
|
||||
115
app/client/components/WorkingListCard.tsx
Normal file
115
app/client/components/WorkingListCard.tsx
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import { Badge, BadgeProps, Box, Card, Heading, Icon, Link, Stack, Text } from '@chakra-ui/react';
|
||||
import { WorkingProgressStatus } from '@generated/types';
|
||||
import { ReactNode } from 'react';
|
||||
import { WorkingProgressStatusBadge } from './WorkingProgressStatusBadge';
|
||||
import { WorkingRatingBadge } from './WorkingRatingBadge';
|
||||
|
||||
export interface WorkingCardProps {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
author: { id: string; username: string };
|
||||
progressStatus: WorkingProgressStatus;
|
||||
updatedAt: string;
|
||||
chaptersTotal: number;
|
||||
wordsTotal: number;
|
||||
ratingTotal: number;
|
||||
reviewsTotal: number;
|
||||
}
|
||||
|
||||
export function WorkingListCard({
|
||||
title,
|
||||
description,
|
||||
author,
|
||||
progressStatus,
|
||||
updatedAt,
|
||||
chaptersTotal,
|
||||
wordsTotal,
|
||||
ratingTotal,
|
||||
reviewsTotal
|
||||
}: WorkingCardProps) {
|
||||
return (
|
||||
<Card.Root width="100%" variant="elevated">
|
||||
<Card.Body>
|
||||
<Stack>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Link>
|
||||
<Heading size="lg">{title}</Heading>
|
||||
</Link>
|
||||
|
||||
<Stack direction="row" justifyContent="center" alignItems="center">
|
||||
<WorkingRatingBadge ratingTotal={ratingTotal} />
|
||||
<IconBadge backgroundColor="orange.400" iconClass="nf nf-oct-comment">
|
||||
<Text>{reviewsTotal}</Text>
|
||||
</IconBadge>
|
||||
<WorkingProgressStatusBadge progressStatus={progressStatus} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Stack>
|
||||
<Stack direction="row">
|
||||
<Text>by:</Text>
|
||||
<Link>{author.username}</Link>
|
||||
</Stack>
|
||||
|
||||
<Stack direction="row">
|
||||
<Box>
|
||||
<Badge size="md" variant="solid">
|
||||
<Stack direction="row">
|
||||
<Text>last updated:</Text>
|
||||
<Text>{updatedAt}</Text>
|
||||
</Stack>
|
||||
</Badge>
|
||||
</Box>
|
||||
<Box>
|
||||
<Badge size="md" variant="solid">
|
||||
<Stack direction="row">
|
||||
<Text>chapters:</Text>
|
||||
<Text>{chaptersTotal}</Text>
|
||||
</Stack>
|
||||
</Badge>
|
||||
</Box>
|
||||
<Box>
|
||||
<Badge size="md" variant="solid">
|
||||
<Stack direction="row">
|
||||
<Text>words:</Text>
|
||||
<Text>{wordsTotal}</Text>
|
||||
</Stack>
|
||||
</Badge>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Stack>
|
||||
<Text lineClamp={4}>{description}</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card.Body>
|
||||
</Card.Root>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconBadge({
|
||||
children,
|
||||
iconClass,
|
||||
backgroundColor,
|
||||
size = 'lg'
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
iconClass: string;
|
||||
backgroundColor?: BadgeProps['backgroundColor'];
|
||||
size?: 'sm' | 'md' | 'lg' | 'xs';
|
||||
}) {
|
||||
return (
|
||||
<Box flex={1} direction="row" justifyContent="center" alignItems="center">
|
||||
<Badge variant="solid" size={size} backgroundColor={backgroundColor}>
|
||||
<Stack direction="row">
|
||||
<Icon fontSize="xl">
|
||||
<i className={iconClass} />
|
||||
</Icon>
|
||||
{children}
|
||||
</Stack>
|
||||
</Badge>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
40
app/client/components/WorkingListSearch.tsx
Normal file
40
app/client/components/WorkingListSearch.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import { Button, Icon, Input, InputGroup, Stack } from '@chakra-ui/react';
|
||||
|
||||
export function WorkingListSearch({
|
||||
value,
|
||||
onChange,
|
||||
onSubmit
|
||||
}: {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
onSubmit: (value: string) => void;
|
||||
}) {
|
||||
return (
|
||||
<form
|
||||
onSubmit={event => {
|
||||
event.preventDefault();
|
||||
onSubmit(value);
|
||||
}}
|
||||
>
|
||||
<Stack direction="row">
|
||||
<InputGroup
|
||||
flex="1"
|
||||
startElement={
|
||||
<Icon color="colorPalette.500">
|
||||
<i className="nf nf-oct-search" />
|
||||
</Icon>
|
||||
}
|
||||
>
|
||||
<Input
|
||||
value={value}
|
||||
onChange={event => onChange(event.currentTarget.value)}
|
||||
placeholder="Search works..."
|
||||
/>
|
||||
</InputGroup>
|
||||
<Button type="submit" border="none">
|
||||
Refresh
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
37
app/client/components/WorkingProgressStatusBadge.tsx
Normal file
37
app/client/components/WorkingProgressStatusBadge.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { Badge, Box, Icon, Stack, Text } from '@chakra-ui/react';
|
||||
import { WorkingProgressStatus } from '@generated/types';
|
||||
import {
|
||||
assertUnreachable,
|
||||
toBgColorByProgressStatus,
|
||||
toWorkingProgressStatusLabel
|
||||
} from '../helpers';
|
||||
|
||||
export function WorkingProgressStatusBadge({
|
||||
progressStatus
|
||||
}: {
|
||||
progressStatus: WorkingProgressStatus;
|
||||
}) {
|
||||
return (
|
||||
<Box flex={1} justifyContent="center" alignItems="center">
|
||||
<Badge
|
||||
variant="solid"
|
||||
backgroundColor={toBgColorByProgressStatus(progressStatus)}
|
||||
size={'lg'}
|
||||
>
|
||||
<Stack direction="row">
|
||||
<Icon fontSize="xl">
|
||||
<i className={`${toWorkingProgressStatusIcon(progressStatus)}`} />
|
||||
</Icon>
|
||||
<Text>{toWorkingProgressStatusLabel(progressStatus)}</Text>
|
||||
</Stack>
|
||||
</Badge>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function toWorkingProgressStatusIcon(value: WorkingProgressStatus): string {
|
||||
if (value === WorkingProgressStatus.InProgress) return 'nf nf-fa-clock';
|
||||
if (value === WorkingProgressStatus.Frozen) return 'nf nf-fa-snowflake';
|
||||
if (value === WorkingProgressStatus.Finished) return 'nf nf-fa-circle_check';
|
||||
assertUnreachable(value);
|
||||
}
|
||||
28
app/client/components/WorkingRatingBadge.tsx
Normal file
28
app/client/components/WorkingRatingBadge.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { BadgeProps, Text } from '@chakra-ui/react';
|
||||
import { IconBadge } from './WorkingListCard';
|
||||
|
||||
interface WorkingRatingParams {
|
||||
rating: string;
|
||||
color: BadgeProps['backgroundColor'];
|
||||
iconClass: string;
|
||||
}
|
||||
|
||||
export function WorkingRatingBadge({ ratingTotal }: { ratingTotal: number }) {
|
||||
const ratingParams: WorkingRatingParams =
|
||||
ratingTotal >= 0
|
||||
? {
|
||||
rating: ratingTotal.toString(),
|
||||
color: 'green.500',
|
||||
iconClass: 'nf nf-fa-angle_up'
|
||||
}
|
||||
: {
|
||||
rating: ratingTotal.toString().slice(1),
|
||||
color: 'red.500',
|
||||
iconClass: 'nf nf-fa-angle_down'
|
||||
};
|
||||
return (
|
||||
<IconBadge backgroundColor={ratingParams.color} iconClass={ratingParams.iconClass}>
|
||||
<Text>{ratingParams.rating}</Text>
|
||||
</IconBadge>
|
||||
);
|
||||
}
|
||||
|
|
@ -15,7 +15,6 @@ export function ColorModeProvider(props: ColorModeProviderProps) {
|
|||
|
||||
export function useColorMode() {
|
||||
const { resolvedTheme, setTheme } = useTheme();
|
||||
console.log(resolvedTheme);
|
||||
const toggleColorMode = () => {
|
||||
setTheme(resolvedTheme === 'light' ? 'dark' : 'light');
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
'use client';
|
||||
|
||||
import { ChakraProvider, defaultSystem, Theme } from '@chakra-ui/react';
|
||||
import { ChakraProvider, createSystem, defaultConfig, Theme } from '@chakra-ui/react';
|
||||
import { ColorModeProvider, type ColorModeProviderProps } from './color-mode';
|
||||
|
||||
export function Provider(props: ColorModeProviderProps) {
|
||||
const system = createSystem(defaultConfig, {
|
||||
theme: {
|
||||
tokens: {}
|
||||
}
|
||||
});
|
||||
return (
|
||||
<ChakraProvider value={defaultSystem}>
|
||||
<ChakraProvider value={system}>
|
||||
<Theme colorPalette="cyan">
|
||||
<ColorModeProvider defaultTheme="dark">{props.children}</ColorModeProvider>
|
||||
</Theme>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue