implemented identity, updated session, added 'workings' list

added paginated querying, added 'common'
This commit is contained in:
lnn0q 2025-05-17 00:29:55 +03:00
parent 027deba422
commit 6ada519e06
36 changed files with 1422 additions and 279 deletions

View 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'
}
};

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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);
}

View 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>
);
}

View file

@ -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');
};

View file

@ -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>