refactor, added slate, added editor, added book
This commit is contained in:
parent
bc250e73bb
commit
029b7e9353
33 changed files with 714 additions and 496 deletions
|
|
@ -1,16 +1,18 @@
|
||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
import { Skeleton, Stack } from '@chakra-ui/react';
|
import { Stack } from '@chakra-ui/react';
|
||||||
import { BooksListQuery } from '@generated/types';
|
import { BooksListQuery } from '@generated/types';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Pagination } from '../components/Pagination';
|
import { Pagination } from '../components/Pagination';
|
||||||
import { WorkingList } from '../components/WorkingList';
|
import { WorkingList } from '../components/WorkingList';
|
||||||
import { WorkingListSearch } from '../components/WorkingListSearch';
|
import { ListSearch } from '../components/ListSearch';
|
||||||
|
import { bookFragment } from '../fragments';
|
||||||
import { usePaginatedQuery } from '../hooks/usePaginatedQuery';
|
import { usePaginatedQuery } from '../hooks/usePaginatedQuery';
|
||||||
|
import { PaginatedListSkeleton } from './PaginatedListSkeleton';
|
||||||
|
|
||||||
export function BookSearchList() {
|
export function BookSearchList() {
|
||||||
const [workingSearchValue, setWorkingSearchValue] = useState<string>('');
|
const [workingSearchValue, setWorkingSearchValue] = useState<string>('');
|
||||||
const [workingsListPage, setWorkingsListPage] = useState<number>(1);
|
const [workingsListPage, setWorkingsListPage] = useState<number>(1);
|
||||||
const [workingsListPageSize, setWorkingsListPageSize] = useState<number>(2);
|
const [workingsListPageSize, setWorkingsListPageSize] = useState<number>(10);
|
||||||
const startIndex = (workingsListPage - 1) * workingsListPageSize;
|
const startIndex = (workingsListPage - 1) * workingsListPageSize;
|
||||||
|
|
||||||
const { data } = usePaginatedQuery<BooksListQuery>({
|
const { data } = usePaginatedQuery<BooksListQuery>({
|
||||||
|
|
@ -26,35 +28,24 @@ export function BookSearchList() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<ListSearch placeholder="Search works..." onSubmit={value => setWorkingSearchValue(value)} />
|
||||||
{data != null ? (
|
{data != null ? (
|
||||||
<>
|
<>
|
||||||
<WorkingListSearch
|
|
||||||
onSubmit={value => {
|
|
||||||
setWorkingSearchValue(value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<WorkingList workingsList={[...data.bookMany.items]} />
|
<WorkingList workingsList={[...data.bookMany.items]} />
|
||||||
|
{data.bookMany.total > workingsListPageSize && (
|
||||||
{data.bookMany.items.length !== 0 && (
|
<Stack justifyContent="center" alignItems="center">
|
||||||
<Pagination
|
<Pagination
|
||||||
total={data.bookMany.total}
|
total={data.bookMany.total}
|
||||||
page={workingsListPage}
|
page={workingsListPage}
|
||||||
setPage={setWorkingsListPage}
|
setPage={setWorkingsListPage}
|
||||||
pageSize={workingsListPageSize}
|
pageSize={workingsListPageSize}
|
||||||
|
setPageSize={setWorkingsListPageSize}
|
||||||
/>
|
/>
|
||||||
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<PaginatedListSkeleton />
|
||||||
<Stack direction="row" justifyContent="center" alignItems="center" gap={2}>
|
|
||||||
<Skeleton height={10} width="80%" />
|
|
||||||
<Skeleton height={10} width="calc(20% - 6px)" />
|
|
||||||
</Stack>
|
|
||||||
<Skeleton height={20} />
|
|
||||||
<Skeleton height={20} />
|
|
||||||
<Skeleton height={20} />
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
@ -64,21 +55,10 @@ const query = gql`
|
||||||
query BooksList($input: BookManyInput!) {
|
query BooksList($input: BookManyInput!) {
|
||||||
bookMany(input: $input) {
|
bookMany(input: $input) {
|
||||||
items {
|
items {
|
||||||
id
|
...Book
|
||||||
title
|
|
||||||
description
|
|
||||||
author {
|
|
||||||
id
|
|
||||||
username
|
|
||||||
}
|
|
||||||
updatedAt
|
|
||||||
progressStatus
|
|
||||||
ratingTotal
|
|
||||||
reviewsTotal
|
|
||||||
wordsTotal
|
|
||||||
chaptersTotal
|
|
||||||
}
|
}
|
||||||
total
|
total
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
${bookFragment}
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Button, Card, Stack, Text, Wrap } from '@chakra-ui/react';
|
import { Button, Card, Stack, Text, Wrap } from '@chakra-ui/react';
|
||||||
import isHotkey from 'is-hotkey';
|
import isHotkey from 'is-hotkey';
|
||||||
import React, { KeyboardEvent, MouseEvent, useCallback, useMemo, useState } from 'react';
|
import React, { KeyboardEvent, MouseEvent, useCallback, useMemo } from 'react';
|
||||||
import { createEditor, Descendant, Editor, Element as SlateElement, Transforms } from 'slate';
|
import { createEditor, Descendant, Editor, Element as SlateElement, Transforms } from 'slate';
|
||||||
import { withHistory } from 'slate-history';
|
import { withHistory } from 'slate-history';
|
||||||
import {
|
import {
|
||||||
|
|
@ -44,7 +44,7 @@ export function EditorSlate() {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RichTextExample />
|
<EditorViewport />
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
</Card.Root>
|
</Card.Root>
|
||||||
</>
|
</>
|
||||||
|
|
@ -65,7 +65,7 @@ type AlignType = (typeof TEXT_ALIGN_TYPES)[number];
|
||||||
type ListType = (typeof LIST_TYPES)[number];
|
type ListType = (typeof LIST_TYPES)[number];
|
||||||
type CustomElementFormat = CustomElementType | AlignType | ListType;
|
type CustomElementFormat = CustomElementType | AlignType | ListType;
|
||||||
|
|
||||||
const RichTextExample = () => {
|
function EditorViewport() {
|
||||||
const renderElement = useCallback((props: RenderElementProps) => <Element {...props} />, []);
|
const renderElement = useCallback((props: RenderElementProps) => <Element {...props} />, []);
|
||||||
const renderLeaf = useCallback((props: RenderLeafProps) => <Leaf {...props} />, []);
|
const renderLeaf = useCallback((props: RenderLeafProps) => <Leaf {...props} />, []);
|
||||||
const editor = useMemo(() => withHistory(withReact(createEditor())), []);
|
const editor = useMemo(() => withHistory(withReact(createEditor())), []);
|
||||||
|
|
@ -123,12 +123,17 @@ const RichTextExample = () => {
|
||||||
<BlockButton format="right" icon="nf nf-fa-align_right" />
|
<BlockButton format="right" icon="nf nf-fa-align_right" />
|
||||||
<BlockButton format="justify" icon="nf nf-fa-align_justify" />
|
<BlockButton format="justify" icon="nf nf-fa-align_justify" />
|
||||||
</Wrap>
|
</Wrap>
|
||||||
<Stack width="100%" display="flex" justifyContent="center" alignItems="center">
|
<Stack
|
||||||
|
width="100%"
|
||||||
|
maxWidth="100%"
|
||||||
|
paddingX={3}
|
||||||
|
display="flex"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
<Prose
|
<Prose
|
||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
minWidth="100%"
|
||||||
maxWidth="unset"
|
|
||||||
maxHeight="unset"
|
|
||||||
display="flex"
|
display="flex"
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
|
|
@ -137,7 +142,11 @@ const RichTextExample = () => {
|
||||||
className="slate-editor"
|
className="slate-editor"
|
||||||
style={{
|
style={{
|
||||||
outline: 'none',
|
outline: 'none',
|
||||||
minWidth: '100%'
|
minWidth: '100%',
|
||||||
|
maxWidth: 'unset',
|
||||||
|
maxHeight: '75vh',
|
||||||
|
padding: 0,
|
||||||
|
overflowY: 'auto'
|
||||||
}}
|
}}
|
||||||
renderElement={renderElement}
|
renderElement={renderElement}
|
||||||
renderLeaf={renderLeaf}
|
renderLeaf={renderLeaf}
|
||||||
|
|
@ -158,9 +167,9 @@ const RichTextExample = () => {
|
||||||
</Stack>
|
</Stack>
|
||||||
</Slate>
|
</Slate>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
const toggleBlock = (editor: CustomEditor, format: CustomElementFormat) => {
|
function toggleBlock(editor: CustomEditor, format: CustomElementFormat) {
|
||||||
const isActive = isBlockActive(editor, format, isAlignType(format) ? 'align' : 'type');
|
const isActive = isBlockActive(editor, format, isAlignType(format) ? 'align' : 'type');
|
||||||
const isList = isListType(format);
|
const isList = isListType(format);
|
||||||
|
|
||||||
|
|
@ -188,9 +197,9 @@ const toggleBlock = (editor: CustomEditor, format: CustomElementFormat) => {
|
||||||
const block = { type: format, children: [] };
|
const block = { type: format, children: [] };
|
||||||
Transforms.wrapNodes(editor, block);
|
Transforms.wrapNodes(editor, block);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const toggleMark = (editor: CustomEditor, format: CustomTextKey) => {
|
function toggleMark(editor: CustomEditor, format: CustomTextKey) {
|
||||||
const isActive = isMarkActive(editor, format);
|
const isActive = isMarkActive(editor, format);
|
||||||
|
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
|
|
@ -198,13 +207,13 @@ const toggleMark = (editor: CustomEditor, format: CustomTextKey) => {
|
||||||
} else {
|
} else {
|
||||||
Editor.addMark(editor, format, true);
|
Editor.addMark(editor, format, true);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const isBlockActive = (
|
function isBlockActive(
|
||||||
editor: CustomEditor,
|
editor: CustomEditor,
|
||||||
format: CustomElementFormat,
|
format: CustomElementFormat,
|
||||||
blockType: 'type' | 'align' = 'type'
|
blockType: 'type' | 'align' = 'type'
|
||||||
) => {
|
) {
|
||||||
const { selection } = editor;
|
const { selection } = editor;
|
||||||
if (!selection) return false;
|
if (!selection) return false;
|
||||||
|
|
||||||
|
|
@ -223,15 +232,15 @@ const isBlockActive = (
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
return !!match;
|
return Boolean(match);
|
||||||
};
|
}
|
||||||
|
|
||||||
const isMarkActive = (editor: CustomEditor, format: CustomTextKey) => {
|
function isMarkActive(editor: CustomEditor, format: CustomTextKey) {
|
||||||
const marks = Editor.marks(editor);
|
const marks = Editor.marks(editor);
|
||||||
return marks ? marks[format] === true : false;
|
return marks ? marks[format] === true : false;
|
||||||
};
|
}
|
||||||
|
|
||||||
const Element = ({ attributes, children, element }: RenderElementProps) => {
|
function Element({ attributes, children, element }: RenderElementProps) {
|
||||||
const style: React.CSSProperties = {};
|
const style: React.CSSProperties = {};
|
||||||
if (isAlignElement(element)) {
|
if (isAlignElement(element)) {
|
||||||
style.textAlign = element.align as AlignType;
|
style.textAlign = element.align as AlignType;
|
||||||
|
|
@ -280,9 +289,9 @@ const Element = ({ attributes, children, element }: RenderElementProps) => {
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const Leaf = ({ attributes, children, leaf }: RenderLeafProps) => {
|
function Leaf({ attributes, children, leaf }: RenderLeafProps) {
|
||||||
if (leaf.bold) {
|
if (leaf.bold) {
|
||||||
children = <strong>{children}</strong>;
|
children = <strong>{children}</strong>;
|
||||||
}
|
}
|
||||||
|
|
@ -300,7 +309,7 @@ const Leaf = ({ attributes, children, leaf }: RenderLeafProps) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return <span {...attributes}>{children}</span>;
|
return <span {...attributes}>{children}</span>;
|
||||||
};
|
}
|
||||||
|
|
||||||
interface BlockButtonProps {
|
interface BlockButtonProps {
|
||||||
format: CustomElementFormat;
|
format: CustomElementFormat;
|
||||||
|
|
@ -308,7 +317,7 @@ interface BlockButtonProps {
|
||||||
text?: string;
|
text?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BlockButton = ({ format, icon, text }: BlockButtonProps) => {
|
function BlockButton({ format, icon, text }: BlockButtonProps) {
|
||||||
const editor = useSlate();
|
const editor = useSlate();
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -325,7 +334,7 @@ const BlockButton = ({ format, icon, text }: BlockButtonProps) => {
|
||||||
</Stack>
|
</Stack>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
interface MarkButtonProps {
|
interface MarkButtonProps {
|
||||||
format: CustomTextKey;
|
format: CustomTextKey;
|
||||||
|
|
@ -333,7 +342,7 @@ interface MarkButtonProps {
|
||||||
text?: string;
|
text?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MarkButton = ({ format, icon, text }: MarkButtonProps) => {
|
function MarkButton({ format, icon, text }: MarkButtonProps) {
|
||||||
const editor = useSlate();
|
const editor = useSlate();
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -347,16 +356,16 @@ const MarkButton = ({ format, icon, text }: MarkButtonProps) => {
|
||||||
{text}
|
{text}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
const isAlignType = (format: CustomElementFormat): format is AlignType => {
|
function isAlignType(format: CustomElementFormat): format is AlignType {
|
||||||
return TEXT_ALIGN_TYPES.includes(format as AlignType);
|
return TEXT_ALIGN_TYPES.includes(format as AlignType);
|
||||||
};
|
}
|
||||||
|
|
||||||
const isListType = (format: CustomElementFormat): format is ListType => {
|
function isListType(format: CustomElementFormat): format is ListType {
|
||||||
return LIST_TYPES.includes(format as ListType);
|
return LIST_TYPES.includes(format as ListType);
|
||||||
};
|
}
|
||||||
|
|
||||||
const isAlignElement = (element: CustomElement): element is CustomElementWithAlign => {
|
function isAlignElement(element: CustomElement): element is CustomElementWithAlign {
|
||||||
return 'align' in element;
|
return 'align' in element;
|
||||||
};
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
import { Card, Heading } from '@chakra-ui/react';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { BaseEditor, createEditor, Descendant } from 'slate';
|
|
||||||
import { Editable, ReactEditor, Slate, withReact } from 'slate-react';
|
|
||||||
|
|
||||||
type CustomElement = { type: 'paragraph'; children: CustomText[] };
|
|
||||||
type CustomText = { text: string };
|
|
||||||
|
|
||||||
declare module 'slate' {
|
|
||||||
interface CustomTypes {
|
|
||||||
Editor: BaseEditor & ReactEditor;
|
|
||||||
Element: CustomElement;
|
|
||||||
Text: CustomText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function EditorSlate() {
|
|
||||||
const initialValue: Descendant[] = [
|
|
||||||
{
|
|
||||||
type: 'paragraph',
|
|
||||||
children: [{ text: 'A line of text in a paragraph.' }]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const [editor] = useState(() => withReact(createEditor()));
|
|
||||||
return (
|
|
||||||
// Add the editable component inside the context.
|
|
||||||
<>
|
|
||||||
<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'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Slate editor={editor} initialValue={initialValue}>
|
|
||||||
<Editable />
|
|
||||||
</Slate>
|
|
||||||
</Card.Body>
|
|
||||||
</Card.Root>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
25
app/client/components/Link.tsx
Normal file
25
app/client/components/Link.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { Text } from '@chakra-ui/react';
|
||||||
|
import { LinkProps as LinkExtended, Link as TanstackLink } from '@tanstack/react-router';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
interface LinkProps {
|
||||||
|
children: ReactNode;
|
||||||
|
to: LinkExtended['to'];
|
||||||
|
link?: LinkExtended;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Link(props: LinkProps) {
|
||||||
|
return (
|
||||||
|
<TanstackLink {...props}>
|
||||||
|
<Text
|
||||||
|
color="colorPalette.500"
|
||||||
|
_hover={{
|
||||||
|
textDecoration: 'underline',
|
||||||
|
textDecorationColor: 'colorPalette.700'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</Text>
|
||||||
|
</TanstackLink>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,13 @@
|
||||||
import { Button, Icon, Input, InputGroup, Stack } from '@chakra-ui/react';
|
import { Button, Icon, Input, InputGroup, Stack } from '@chakra-ui/react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
export function WorkingListSearch({ onSubmit }: { onSubmit: (value: string) => void }) {
|
export function ListSearch({
|
||||||
|
placeholder,
|
||||||
|
onSubmit
|
||||||
|
}: {
|
||||||
|
readonly placeholder?: string;
|
||||||
|
readonly onSubmit: (value: string) => void;
|
||||||
|
}) {
|
||||||
const [searchValue, setSearchValue] = useState<string>('');
|
const [searchValue, setSearchValue] = useState<string>('');
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
|
|
@ -22,7 +28,8 @@ export function WorkingListSearch({ onSubmit }: { onSubmit: (value: string) => v
|
||||||
<Input
|
<Input
|
||||||
value={searchValue}
|
value={searchValue}
|
||||||
onChange={event => setSearchValue(event.currentTarget.value)}
|
onChange={event => setSearchValue(event.currentTarget.value)}
|
||||||
placeholder="Search works..."
|
onSubmit={event => event.preventDefault()}
|
||||||
|
placeholder={placeholder}
|
||||||
/>
|
/>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
<Button type="submit" border="none">
|
<Button type="submit" border="none">
|
||||||
17
app/client/components/PaginatedListSkeleton.tsx
Normal file
17
app/client/components/PaginatedListSkeleton.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { Skeleton, Stack } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
export function PaginatedListSkeleton({ disableSearch }: { readonly disableSearch?: boolean }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!disableSearch && (
|
||||||
|
<Stack direction="row" justifyContent="center" alignItems="center" gap={2}>
|
||||||
|
<Skeleton height={10} width="80%" />
|
||||||
|
<Skeleton height={10} width="calc(20% - 6px)" />
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
<Skeleton height={20} />
|
||||||
|
<Skeleton height={20} />
|
||||||
|
<Skeleton height={20} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,7 @@ export function Pagination({
|
||||||
page: number;
|
page: number;
|
||||||
setPage: (value: number) => void;
|
setPage: (value: number) => void;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
|
setPageSize?: (value: number) => void;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<ChakraPagination.Root
|
<ChakraPagination.Root
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { Card, List, Text } from '@chakra-ui/react';
|
import { Card, List, Text } from '@chakra-ui/react';
|
||||||
import { WorkingCardProps, WorkingListCard } from './WorkingListCard';
|
import { WorkingListCard } from './WorkingListCard';
|
||||||
|
import { BookOne } from '../interfaces';
|
||||||
|
|
||||||
export function WorkingList({ workingsList }: { workingsList: WorkingCardProps[] }) {
|
export function WorkingList({ workingsList }: { workingsList: BookOne[] }) {
|
||||||
return (
|
return (
|
||||||
<List.Root variant="plain" gap={4}>
|
<List.Root variant="plain" gap={4}>
|
||||||
{workingsList.length !== 0 ? (
|
{workingsList.length !== 0 ? (
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,14 @@
|
||||||
import { Badge, Box, Card, Heading, Link, Stack, Text } from '@chakra-ui/react';
|
import { Badge, Box, Card, Heading, Stack, Text } from '@chakra-ui/react';
|
||||||
import { WorkingProgressStatus } from '@generated/types';
|
import { Link } from '@tanstack/react-router';
|
||||||
|
import { toFormattedDate } from '../helpers';
|
||||||
|
import { BookOne } from '../interfaces';
|
||||||
import { IconBadge } from './IconBadge';
|
import { IconBadge } from './IconBadge';
|
||||||
import { WorkingProgressStatusBadge } from './WorkingProgressStatusBadge';
|
import { WorkingProgressStatusBadge } from './WorkingProgressStatusBadge';
|
||||||
import { WorkingRatingBadge } from './WorkingRatingBadge';
|
import { WorkingRatingBadge } from './WorkingRatingBadge';
|
||||||
|
import { WorkingReviewsBadge } from './WorkingReviewsTotalBadge';
|
||||||
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({
|
export function WorkingListCard({
|
||||||
|
id,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
author,
|
author,
|
||||||
|
|
@ -27,29 +18,47 @@ export function WorkingListCard({
|
||||||
wordsTotal,
|
wordsTotal,
|
||||||
ratingTotal,
|
ratingTotal,
|
||||||
reviewsTotal
|
reviewsTotal
|
||||||
}: WorkingCardProps) {
|
}: BookOne) {
|
||||||
return (
|
return (
|
||||||
<Card.Root width="100%" variant="elevated">
|
<Card.Root width="100%" variant="elevated">
|
||||||
<Card.Body>
|
<Card.Body>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Stack direction="row" justifyContent="space-between">
|
<Stack direction="row" justifyContent="space-between">
|
||||||
<Link>
|
<Text as="div" minWidth={0}>
|
||||||
<Heading size="lg">{title}</Heading>
|
<Link to={`/work/book/${id}`}>
|
||||||
|
<Heading
|
||||||
|
color="colorPalette.400"
|
||||||
|
_hover={{
|
||||||
|
textDecoration: 'underline',
|
||||||
|
textDecorationColor: 'colorPalette.700'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Heading>
|
||||||
</Link>
|
</Link>
|
||||||
|
</Text>
|
||||||
|
|
||||||
<Stack direction="row" justifyContent="center" alignItems="center">
|
<Stack direction="row" justifyContent="center" alignItems="center">
|
||||||
<WorkingRatingBadge ratingTotal={ratingTotal} />
|
<WorkingRatingBadge ratingTotal={ratingTotal} />
|
||||||
<IconBadge backgroundColor="orange.400" iconClass="nf nf-oct-comment">
|
<WorkingReviewsBadge reviewsTotal={reviewsTotal} />
|
||||||
<Text>{reviewsTotal}</Text>
|
|
||||||
</IconBadge>
|
|
||||||
<WorkingProgressStatusBadge progressStatus={progressStatus} />
|
<WorkingProgressStatusBadge progressStatus={progressStatus} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Stack>
|
<Stack>
|
||||||
<Stack direction="row">
|
<Stack direction="row">
|
||||||
<Text>by:</Text>
|
<Link>by:</Link>
|
||||||
<Link>{author.username}</Link>
|
<Link to={`/profile/${author.username}`}>
|
||||||
|
<Text
|
||||||
|
color="colorPalette.400"
|
||||||
|
_hover={{
|
||||||
|
textDecoration: 'underline',
|
||||||
|
textDecorationColor: 'colorPalette.700'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{author.username}
|
||||||
|
</Text>
|
||||||
|
</Link>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Stack direction="row">
|
<Stack direction="row">
|
||||||
|
|
@ -57,7 +66,7 @@ export function WorkingListCard({
|
||||||
<Badge size="md" variant="solid">
|
<Badge size="md" variant="solid">
|
||||||
<Stack direction="row">
|
<Stack direction="row">
|
||||||
<Text>last updated:</Text>
|
<Text>last updated:</Text>
|
||||||
<Text>{updatedAt}</Text>
|
<Text>{toFormattedDate({ value: updatedAt })}</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Badge>
|
</Badge>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ export function WorkingProgressStatusBadge({
|
||||||
progressStatus: WorkingProgressStatus;
|
progressStatus: WorkingProgressStatus;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Box flex={1} justifyContent="center" alignItems="center">
|
<Box>
|
||||||
<Badge
|
<Badge
|
||||||
variant="solid"
|
variant="solid"
|
||||||
backgroundColor={toBgColorByProgressStatus(progressStatus)}
|
backgroundColor={toBgColorByProgressStatus(progressStatus)}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { BadgeProps, Text } from '@chakra-ui/react';
|
import { BadgeProps, Box, Text } from '@chakra-ui/react';
|
||||||
import { IconBadge } from './IconBadge';
|
import { IconBadge } from './IconBadge';
|
||||||
|
|
||||||
interface WorkingRatingParams {
|
interface WorkingRatingParams {
|
||||||
|
|
@ -21,8 +21,10 @@ export function WorkingRatingBadge({ ratingTotal }: { ratingTotal: number }) {
|
||||||
iconClass: 'nf nf-fa-angle_down'
|
iconClass: 'nf nf-fa-angle_down'
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
<Box>
|
||||||
<IconBadge backgroundColor={ratingParams.color} iconClass={ratingParams.iconClass}>
|
<IconBadge backgroundColor={ratingParams.color} iconClass={ratingParams.iconClass}>
|
||||||
<Text>{ratingParams.rating}</Text>
|
<Text>{ratingParams.rating}</Text>
|
||||||
</IconBadge>
|
</IconBadge>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
app/client/components/WorkingReviewsTotalBadge.tsx
Normal file
12
app/client/components/WorkingReviewsTotalBadge.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Box, Text } from '@chakra-ui/react';
|
||||||
|
import { IconBadge } from './IconBadge';
|
||||||
|
|
||||||
|
export function WorkingReviewsBadge({ reviewsTotal }: { readonly reviewsTotal: number }) {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<IconBadge backgroundColor="orange.400" iconClass="nf nf-oct-comment">
|
||||||
|
<Text>{reviewsTotal}</Text>
|
||||||
|
</IconBadge>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,26 +1,22 @@
|
||||||
import { Link as ChakraLink } from "@chakra-ui/react";
|
import { Link as ChakraLink } from '@chakra-ui/react';
|
||||||
import { createLink, LinkComponent } from "@tanstack/react-router";
|
import { createLink, LinkComponent } from '@tanstack/react-router';
|
||||||
import * as React from "react";
|
import * as React from 'react';
|
||||||
|
|
||||||
interface ChakraLinkProps
|
interface ChakraLinkProps extends Omit<React.ComponentPropsWithoutRef<typeof ChakraLink>, 'href'> {}
|
||||||
extends Omit<React.ComponentPropsWithoutRef<typeof ChakraLink>, "href"> {}
|
|
||||||
|
|
||||||
const ChakraLinkComponent = React.forwardRef<
|
const ChakraLinkComponent = React.forwardRef<HTMLAnchorElement, ChakraLinkProps>((props, ref) => {
|
||||||
HTMLAnchorElement,
|
|
||||||
ChakraLinkProps
|
|
||||||
>((props, ref) => {
|
|
||||||
return <ChakraLink ref={ref} {...props} />;
|
return <ChakraLink ref={ref} {...props} />;
|
||||||
});
|
});
|
||||||
|
|
||||||
const CreatedLinkComponent = createLink(ChakraLinkComponent);
|
const CreatedLinkComponent = createLink(ChakraLinkComponent);
|
||||||
|
|
||||||
export const Link: LinkComponent<typeof ChakraLinkComponent> = (props) => {
|
export const Link: LinkComponent<typeof ChakraLinkComponent> = props => {
|
||||||
return (
|
return (
|
||||||
<CreatedLinkComponent
|
<CreatedLinkComponent
|
||||||
textDecoration={"underline"}
|
textDecoration={'underline'}
|
||||||
_hover={{ textDecoration: "none" }}
|
_hover={{ textDecoration: 'none' }}
|
||||||
_focus={{ textDecoration: "none" }}
|
_focus={{ textDecoration: 'none' }}
|
||||||
preload={"intent"}
|
preload={'intent'}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,34 @@ export const sessionFragment = gql`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// refreshToken {
|
export const bookFragment = gql`
|
||||||
// token
|
fragment Book on Book {
|
||||||
// maxAge
|
id
|
||||||
// secure
|
title
|
||||||
// httpOnly
|
description
|
||||||
// sameSite
|
author {
|
||||||
// }
|
id
|
||||||
|
username
|
||||||
|
}
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
progressStatus
|
||||||
|
wordsTotal
|
||||||
|
chaptersTotal
|
||||||
|
ratingTotal
|
||||||
|
reviewsTotal
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const bookChapterFragment = gql`
|
||||||
|
fragment BookChapter on BookChapter {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
foreword
|
||||||
|
footnote
|
||||||
|
sequenceNumber
|
||||||
|
reviewsTotal
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,16 @@
|
||||||
import { WorkingProgressStatus } from '@generated/types';
|
import { WorkingProgressStatus } from '@generated/types';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
export function assertUnreachable(value: never): never {
|
export function assertUnreachable(value: never): never {
|
||||||
throw new Error(`Unhandled scenario: ${value}`);
|
throw new Error(`Unhandled scenario: ${value}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toFormattedDate({ value }: { value: string }): string {
|
||||||
|
const date = new Date(value);
|
||||||
|
const day = dayjs(date).format('DD.MM.YYYY');
|
||||||
|
return day;
|
||||||
|
}
|
||||||
|
|
||||||
export function toBgColorByProgressStatus(value: WorkingProgressStatus): string {
|
export function toBgColorByProgressStatus(value: WorkingProgressStatus): string {
|
||||||
if (value === WorkingProgressStatus.InProgress) return 'yellow.500';
|
if (value === WorkingProgressStatus.InProgress) return 'yellow.500';
|
||||||
if (value === WorkingProgressStatus.Frozen) return 'blue.500';
|
if (value === WorkingProgressStatus.Frozen) return 'blue.500';
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { DocumentNode } from 'graphql';
|
||||||
interface UsePaginatedQueryProps {
|
interface UsePaginatedQueryProps {
|
||||||
query: DocumentNode;
|
query: DocumentNode;
|
||||||
input: {
|
input: {
|
||||||
|
[key: string]: any; // for the custom inputs
|
||||||
filter: {
|
filter: {
|
||||||
terms?: string | null;
|
terms?: string | null;
|
||||||
offset?: number | null;
|
offset?: number | null;
|
||||||
|
|
|
||||||
18
app/client/interfaces.tsx
Normal file
18
app/client/interfaces.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { WorkingProgressStatus } from '@generated/types';
|
||||||
|
|
||||||
|
export interface BookOne {
|
||||||
|
readonly id: string;
|
||||||
|
readonly title: string;
|
||||||
|
readonly description: string;
|
||||||
|
readonly author: {
|
||||||
|
readonly id: string;
|
||||||
|
readonly username: string;
|
||||||
|
};
|
||||||
|
readonly createdAt: string;
|
||||||
|
readonly updatedAt: string;
|
||||||
|
readonly progressStatus: WorkingProgressStatus;
|
||||||
|
readonly wordsTotal: number;
|
||||||
|
readonly chaptersTotal: number;
|
||||||
|
readonly ratingTotal: number;
|
||||||
|
readonly reviewsTotal: number;
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,5 @@
|
||||||
import {
|
import { Icon, Input, InputGroup, Kbd, Menu, Separator, Stack, Text } from '@chakra-ui/react';
|
||||||
Link as ChakraLink,
|
import { createRootRouteWithContext, Outlet, Link } from '@tanstack/react-router';
|
||||||
Icon,
|
|
||||||
Input,
|
|
||||||
InputGroup,
|
|
||||||
Kbd,
|
|
||||||
Menu,
|
|
||||||
Separator,
|
|
||||||
Stack,
|
|
||||||
Text
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import { createRootRouteWithContext, Link, Outlet } from '@tanstack/react-router';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Avatar } from '../components/ui/avatar';
|
import { Avatar } from '../components/ui/avatar';
|
||||||
import { Button } from '../components/ui/button';
|
import { Button } from '../components/ui/button';
|
||||||
|
|
@ -18,7 +8,6 @@ import { Tooltip } from '../components/ui/tooltip';
|
||||||
import { useAuth } from '../hooks/auth';
|
import { useAuth } from '../hooks/auth';
|
||||||
import { useIdentity } from '../hooks/identity';
|
import { useIdentity } from '../hooks/identity';
|
||||||
import { appName } from '../main';
|
import { appName } from '../main';
|
||||||
// import { Link as ChakraLink } from '../components/ui/link';
|
|
||||||
|
|
||||||
const TanStackRouterDevtools =
|
const TanStackRouterDevtools =
|
||||||
process.env.NODE_ENV === 'production'
|
process.env.NODE_ENV === 'production'
|
||||||
|
|
@ -67,12 +56,28 @@ export const Route = createRootRouteWithContext<RouterContext>()({
|
||||||
<>
|
<>
|
||||||
<Stack direction="row" alignItems="center">
|
<Stack direction="row" alignItems="center">
|
||||||
<Link to="/editor">
|
<Link to="/editor">
|
||||||
<ChakraLink>editor</ChakraLink>
|
<Text
|
||||||
|
color="colorPalette.400"
|
||||||
|
_hover={{
|
||||||
|
textDecoration: 'underline',
|
||||||
|
textDecorationColor: 'colorPalette.700'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
editor
|
||||||
|
</Text>
|
||||||
</Link>
|
</Link>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack direction="row" alignItems="center">
|
<Stack direction="row" alignItems="center">
|
||||||
<Link to="/new/book">
|
<Link to="/new/book">
|
||||||
<ChakraLink>create</ChakraLink>
|
<Text
|
||||||
|
color="colorPalette.400"
|
||||||
|
_hover={{
|
||||||
|
textDecoration: 'underline',
|
||||||
|
textDecorationColor: 'colorPalette.700'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
create
|
||||||
|
</Text>
|
||||||
</Link>
|
</Link>
|
||||||
</Stack>
|
</Stack>
|
||||||
</>
|
</>
|
||||||
|
|
@ -99,10 +104,7 @@ export const Route = createRootRouteWithContext<RouterContext>()({
|
||||||
{identity != null ? (
|
{identity != null ? (
|
||||||
<Menu.Root>
|
<Menu.Root>
|
||||||
<Menu.Trigger css={{ '&:hover': { cursor: 'pointer' } }}>
|
<Menu.Trigger css={{ '&:hover': { cursor: 'pointer' } }}>
|
||||||
<Avatar
|
<Avatar size="sm" />
|
||||||
size="sm"
|
|
||||||
// src="https://avatars.githubusercontent.com/u/40203084?v=4"
|
|
||||||
/>
|
|
||||||
</Menu.Trigger>
|
</Menu.Trigger>
|
||||||
<Menu.Positioner mt={2}>
|
<Menu.Positioner mt={2}>
|
||||||
<Menu.Content backgroundColor={'bg.subtle'}>
|
<Menu.Content backgroundColor={'bg.subtle'}>
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
// import { gql } from '@apollo/client';
|
|
||||||
import { createLazyFileRoute } from '@tanstack/react-router';
|
|
||||||
|
|
||||||
export const Route = createLazyFileRoute('/book')({
|
|
||||||
component: RouteComponent
|
|
||||||
});
|
|
||||||
|
|
||||||
function RouteComponent() {
|
|
||||||
return <div>"/book"</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// const getBookQuery = gql`
|
|
||||||
// query BookOne {
|
|
||||||
// booksMany {
|
|
||||||
// id
|
|
||||||
// author
|
|
||||||
// title
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// `;
|
|
||||||
|
|
@ -15,49 +15,3 @@ function RouteComponent() {
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// const workingData1: WorkingCardProps = {
|
|
||||||
// id: '1',
|
|
||||||
// title: 'Worth the Candle',
|
|
||||||
// description: `A teenager struggling after the death of his best friend finds himself in a fantasy
|
|
||||||
// world - one which seems to be an amalgamation of every Dungeons and Dragons campaign
|
|
||||||
// they ever played together. Now he's stuck trying to find the answers to why he's there
|
|
||||||
// and what this world is trying to say. The most terrifying answer might be that this
|
|
||||||
// world is an expression of the person he was back on Earth.`,
|
|
||||||
// author: {
|
|
||||||
// id: '1',
|
|
||||||
// createdAt: '12.8.2025',
|
|
||||||
// updatedAt: '4.9.2025',
|
|
||||||
// username: 'AlexanderWhales'
|
|
||||||
// },
|
|
||||||
// progressStatus: WorkingProgressStatus.Finished,
|
|
||||||
// updatedAt: '20.10.2025',
|
|
||||||
// chaptersTotal: 10,
|
|
||||||
// wordsTotal: 34000,
|
|
||||||
// ratingTotal: 10,
|
|
||||||
// reviewsTotal: 3
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// const workingData2: WorkingCardProps = {
|
|
||||||
// id: '2',
|
|
||||||
// title: 'Test',
|
|
||||||
// description: `Description.`,
|
|
||||||
// author: {
|
|
||||||
// id: '1',
|
|
||||||
// createdAt: '12.8.2025',
|
|
||||||
// updatedAt: '4.9.2025',
|
|
||||||
// username: 'JohnSmith'
|
|
||||||
// },
|
|
||||||
// progressStatus: WorkingProgressStatus.InProgress,
|
|
||||||
// updatedAt: '20.10.2025',
|
|
||||||
// chaptersTotal: 10,
|
|
||||||
// wordsTotal: 1200,
|
|
||||||
// ratingTotal: -10,
|
|
||||||
// reviewsTotal: 0
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// const mockWorkingData = {
|
|
||||||
// total: 4,
|
|
||||||
// items: [workingData1, workingData2, { ...workingData1, id: '3' }, { ...workingData2, id: '4' }]
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
|
import { gql } from '@apollo/client';
|
||||||
|
import { useToastMutation } from '@app/client/hooks/useToastMutation';
|
||||||
import { Heading, Input, Stack, Textarea } from '@chakra-ui/react';
|
import { Heading, Input, Stack, Textarea } from '@chakra-ui/react';
|
||||||
import { Button } from '@client/components/ui/button';
|
import { Button } from '@client/components/ui/button';
|
||||||
import { Field } from '@client/components/ui/field';
|
import { Field } from '@client/components/ui/field';
|
||||||
|
import { CreateNewBookMutation } from '@generated/types';
|
||||||
import { valibotResolver } from '@hookform/resolvers/valibot';
|
import { valibotResolver } from '@hookform/resolvers/valibot';
|
||||||
import { createFileRoute, redirect } from '@tanstack/react-router';
|
import { createFileRoute, redirect, useNavigate } from '@tanstack/react-router';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import * as v from 'valibot';
|
import * as v from 'valibot';
|
||||||
|
|
||||||
|
|
@ -28,10 +31,16 @@ interface FormValues {
|
||||||
}
|
}
|
||||||
|
|
||||||
function NewBookForm() {
|
function NewBookForm() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { update: bookCreate } = useToastMutation<CreateNewBookMutation>({
|
||||||
|
mutation: bookCreateMutation,
|
||||||
|
successMsg: 'Book created successfully!'
|
||||||
|
});
|
||||||
|
|
||||||
const schema = v.pipe(
|
const schema = v.pipe(
|
||||||
v.object({
|
v.object({
|
||||||
title: v.pipe(v.string(), v.nonEmpty('Title is required')),
|
title: v.pipe(v.string(), v.nonEmpty('Title is required'), v.maxLength(60)),
|
||||||
description: v.pipe(v.string(), v.nonEmpty('Password is required'))
|
description: v.pipe(v.string(), v.nonEmpty('Description is required'), v.maxLength(900))
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -42,7 +51,23 @@ function NewBookForm() {
|
||||||
} = useForm<FormValues>({ resolver: valibotResolver(schema) });
|
} = useForm<FormValues>({ resolver: valibotResolver(schema) });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form style={{ width: '80%' }} onSubmit={handleSubmit(async values => {})}>
|
<form
|
||||||
|
style={{ width: '80%', maxWidth: '840px' }}
|
||||||
|
onSubmit={handleSubmit(async values => {
|
||||||
|
const { data } = await bookCreate({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
title: values.title,
|
||||||
|
description: values.description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (data != null)
|
||||||
|
navigate({
|
||||||
|
to: `/work/book/${data.bookCreate.id}`
|
||||||
|
});
|
||||||
|
})}
|
||||||
|
>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Field label="Title" invalid={errors.title != null} errorText={errors.title?.message}>
|
<Field label="Title" invalid={errors.title != null} errorText={errors.title?.message}>
|
||||||
<Input {...register('title')} />
|
<Input {...register('title')} />
|
||||||
|
|
@ -52,7 +77,7 @@ function NewBookForm() {
|
||||||
invalid={errors.description != null}
|
invalid={errors.description != null}
|
||||||
errorText={errors.description?.message}
|
errorText={errors.description?.message}
|
||||||
>
|
>
|
||||||
<Textarea width="100%" lineHeight={4} {...register('description')} />
|
<Textarea width="100%" height={'sm'} {...register('description')} />
|
||||||
</Field>
|
</Field>
|
||||||
<Button type="submit">Create new book</Button>
|
<Button type="submit">Create new book</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
@ -60,38 +85,12 @@ function NewBookForm() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// function LoginForm() {
|
const bookCreateMutation = gql`
|
||||||
// const schema = v.pipe(
|
mutation CreateNewBook($input: BookCreateInput!) {
|
||||||
// v.object({
|
bookCreate(input: $input) {
|
||||||
// email: v.pipe(v.string(), v.nonEmpty('Email is required'), v.email('Invalid email')),
|
id
|
||||||
// password: v.pipe(v.string(), v.nonEmpty('Password is required'))
|
title
|
||||||
// })
|
description
|
||||||
// );
|
}
|
||||||
//
|
}
|
||||||
// const {
|
`;
|
||||||
// register,
|
|
||||||
// handleSubmit,
|
|
||||||
// formState: { errors }
|
|
||||||
// } = useForm<FormValues>({ resolver: valibotResolver(schema) });
|
|
||||||
//
|
|
||||||
// const { login } = useAuth();
|
|
||||||
//
|
|
||||||
// return (
|
|
||||||
// <form
|
|
||||||
// >
|
|
||||||
// <Stack>
|
|
||||||
// <Field label="Email" invalid={errors.email != null} errorText={errors.email?.message}>
|
|
||||||
// <Input {...register('email')} />
|
|
||||||
// </Field>
|
|
||||||
// <Field
|
|
||||||
// label="Password"
|
|
||||||
// invalid={errors.password != null}
|
|
||||||
// errorText={errors.password?.message}
|
|
||||||
// >
|
|
||||||
// <PasswordInput {...register('password')} />
|
|
||||||
// </Field>
|
|
||||||
// <Button type="submit">Sign in</Button>
|
|
||||||
// </Stack>
|
|
||||||
// </form>
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
|
||||||
185
app/client/pages/work/book/$bookId.tsx
Normal file
185
app/client/pages/work/book/$bookId.tsx
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
import { gql, useQuery } from '@apollo/client';
|
||||||
|
import { PaginatedListSkeleton } from '@app/client/components/PaginatedListSkeleton';
|
||||||
|
import { Pagination } from '@app/client/components/Pagination';
|
||||||
|
import { WorkingProgressStatusBadge } from '@app/client/components/WorkingProgressStatusBadge';
|
||||||
|
import { WorkingRatingBadge } from '@app/client/components/WorkingRatingBadge';
|
||||||
|
import { WorkingReviewsBadge } from '@app/client/components/WorkingReviewsTotalBadge';
|
||||||
|
import { bookChapterFragment, bookFragment } from '@app/client/fragments';
|
||||||
|
import { toFormattedDate } from '@app/client/helpers';
|
||||||
|
import { usePaginatedQuery } from '@app/client/hooks/usePaginatedQuery';
|
||||||
|
import { BookOne } from '@app/client/interfaces';
|
||||||
|
import {
|
||||||
|
Heading,
|
||||||
|
HStack,
|
||||||
|
Skeleton,
|
||||||
|
SkeletonCircle,
|
||||||
|
SkeletonText,
|
||||||
|
Stack,
|
||||||
|
Text
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { BookChapterListQuery, BookOverviewQuery } from '@generated/types';
|
||||||
|
import { createFileRoute, Link } from '@tanstack/react-router';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/work/book/$bookId')({
|
||||||
|
component: RouteComponent
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
const { bookId: id } = Route.useParams();
|
||||||
|
const { data: bookData } = useQuery<BookOverviewQuery>(bookQuery, { variables: { id } });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack width="100%" justifyContent="center" alignItems="center" padding={3}>
|
||||||
|
{bookData != null ? <BookOverview {...bookData.bookOne} /> : <BookOverviewSkeleton />}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BookOverview({
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
author,
|
||||||
|
progressStatus,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
wordsTotal,
|
||||||
|
chaptersTotal,
|
||||||
|
reviewsTotal,
|
||||||
|
ratingTotal
|
||||||
|
}: BookOne) {
|
||||||
|
return (
|
||||||
|
<Stack width="80%" maxWidth="840px" gap={3}>
|
||||||
|
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||||
|
<Heading color="colorPalette.400" size="2xl" overflow="auto">
|
||||||
|
{title}
|
||||||
|
</Heading>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack direction="column" gap={1}>
|
||||||
|
<Stack direction="row">
|
||||||
|
<Link>
|
||||||
|
<Text fontWeight="bold">by:</Text>
|
||||||
|
</Link>
|
||||||
|
<Link to={`/profile/${author.username}`}>
|
||||||
|
<Text
|
||||||
|
color="colorPalette.400"
|
||||||
|
_hover={{
|
||||||
|
textDecoration: 'underline',
|
||||||
|
textDecorationColor: 'colorPalette.700'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{author.username}
|
||||||
|
</Text>
|
||||||
|
</Link>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack direction="row">
|
||||||
|
<Text fontWeight="bold">created at:</Text>
|
||||||
|
<Text>{toFormattedDate({ value: createdAt })}</Text>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack direction="row">
|
||||||
|
<Text fontWeight="bold">last updated:</Text>
|
||||||
|
<Text>{toFormattedDate({ value: updatedAt })}</Text>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack direction="row">
|
||||||
|
<Text fontWeight="bold">chapters:</Text>
|
||||||
|
<Text>{chaptersTotal}</Text>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack direction="row">
|
||||||
|
<Text fontWeight="bold">words:</Text>
|
||||||
|
<Text>{wordsTotal}</Text>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack direction="row" justifyContent="left" alignItems="center">
|
||||||
|
<WorkingRatingBadge ratingTotal={ratingTotal} />
|
||||||
|
<WorkingReviewsBadge reviewsTotal={reviewsTotal} />
|
||||||
|
<WorkingProgressStatusBadge progressStatus={progressStatus} />
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Text>{description}</Text>
|
||||||
|
|
||||||
|
<BookChapterList id={id} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BookChapterList({ id }: { readonly id: string }) {
|
||||||
|
const [chaptersListPage, setChaptersListPage] = useState<number>(1);
|
||||||
|
const [chaptersListPageSize, setChaptersListPageSize] = useState<number>(10);
|
||||||
|
const startIndex = (chaptersListPage - 1) * chaptersListPageSize;
|
||||||
|
|
||||||
|
const { data } = usePaginatedQuery<BookChapterListQuery>({
|
||||||
|
query: bookChapterListQuery,
|
||||||
|
input: {
|
||||||
|
bookId: id,
|
||||||
|
filter: {
|
||||||
|
offset: startIndex,
|
||||||
|
size: chaptersListPageSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
{data != null ? (
|
||||||
|
<>
|
||||||
|
<Stack>...chapters</Stack>
|
||||||
|
{data.bookChapterMany.total > chaptersListPageSize && (
|
||||||
|
<Stack justifyContent="center" alignItems="center">
|
||||||
|
<Pagination
|
||||||
|
total={data.bookChapterMany.total}
|
||||||
|
page={chaptersListPage}
|
||||||
|
setPage={setChaptersListPage}
|
||||||
|
pageSize={chaptersListPageSize}
|
||||||
|
setPageSize={setChaptersListPageSize}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<PaginatedListSkeleton disableSearch />
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BookOverviewSkeleton() {
|
||||||
|
return (
|
||||||
|
<Stack width="80%" maxWidth="840px">
|
||||||
|
<Stack gap="6" maxWidth="100%">
|
||||||
|
<HStack width="full">
|
||||||
|
<SkeletonCircle size="10" />
|
||||||
|
<SkeletonText noOfLines={5} />
|
||||||
|
</HStack>
|
||||||
|
<Skeleton height="200px" />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bookQuery = gql`
|
||||||
|
query BookOverview($id: ID!) {
|
||||||
|
bookOne(id: $id) {
|
||||||
|
...Book
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${bookFragment}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const bookChapterListQuery = gql`
|
||||||
|
query BookChapterList($input: BookChapterManyInput!) {
|
||||||
|
bookChapterMany(input: $input) {
|
||||||
|
items {
|
||||||
|
...BookChapter
|
||||||
|
}
|
||||||
|
total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${bookChapterFragment}
|
||||||
|
`;
|
||||||
|
|
@ -8,8 +8,6 @@
|
||||||
// You should NOT make any changes in this file as it will be overwritten.
|
// You should NOT make any changes in this file as it will be overwritten.
|
||||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||||
|
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
|
||||||
|
|
||||||
// Import Routes
|
// Import Routes
|
||||||
|
|
||||||
import { Route as rootRoute } from './pages/__root'
|
import { Route as rootRoute } from './pages/__root'
|
||||||
|
|
@ -20,19 +18,10 @@ import { Route as AboutImport } from './pages/about'
|
||||||
import { Route as IndexImport } from './pages/index'
|
import { Route as IndexImport } from './pages/index'
|
||||||
import { Route as ProfileUsernameImport } from './pages/profile/$username'
|
import { Route as ProfileUsernameImport } from './pages/profile/$username'
|
||||||
import { Route as NewBookImport } from './pages/new/book'
|
import { Route as NewBookImport } from './pages/new/book'
|
||||||
|
import { Route as WorkBookBookIdImport } from './pages/work/book/$bookId'
|
||||||
// Create Virtual Routes
|
|
||||||
|
|
||||||
const BookLazyImport = createFileRoute('/book')()
|
|
||||||
|
|
||||||
// Create/Update Routes
|
// Create/Update Routes
|
||||||
|
|
||||||
const BookLazyRoute = BookLazyImport.update({
|
|
||||||
id: '/book',
|
|
||||||
path: '/book',
|
|
||||||
getParentRoute: () => rootRoute,
|
|
||||||
} as any).lazy(() => import('./pages/book.lazy').then((d) => d.Route))
|
|
||||||
|
|
||||||
const RegisterRoute = RegisterImport.update({
|
const RegisterRoute = RegisterImport.update({
|
||||||
id: '/register',
|
id: '/register',
|
||||||
path: '/register',
|
path: '/register',
|
||||||
|
|
@ -75,6 +64,12 @@ const NewBookRoute = NewBookImport.update({
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const WorkBookBookIdRoute = WorkBookBookIdImport.update({
|
||||||
|
id: '/work/book/$bookId',
|
||||||
|
path: '/work/book/$bookId',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
// Populate the FileRoutesByPath interface
|
// Populate the FileRoutesByPath interface
|
||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
declare module '@tanstack/react-router' {
|
||||||
|
|
@ -114,13 +109,6 @@ declare module '@tanstack/react-router' {
|
||||||
preLoaderRoute: typeof RegisterImport
|
preLoaderRoute: typeof RegisterImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
'/book': {
|
|
||||||
id: '/book'
|
|
||||||
path: '/book'
|
|
||||||
fullPath: '/book'
|
|
||||||
preLoaderRoute: typeof BookLazyImport
|
|
||||||
parentRoute: typeof rootRoute
|
|
||||||
}
|
|
||||||
'/new/book': {
|
'/new/book': {
|
||||||
id: '/new/book'
|
id: '/new/book'
|
||||||
path: '/new/book'
|
path: '/new/book'
|
||||||
|
|
@ -135,6 +123,13 @@ declare module '@tanstack/react-router' {
|
||||||
preLoaderRoute: typeof ProfileUsernameImport
|
preLoaderRoute: typeof ProfileUsernameImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
|
'/work/book/$bookId': {
|
||||||
|
id: '/work/book/$bookId'
|
||||||
|
path: '/work/book/$bookId'
|
||||||
|
fullPath: '/work/book/$bookId'
|
||||||
|
preLoaderRoute: typeof WorkBookBookIdImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,9 +141,9 @@ export interface FileRoutesByFullPath {
|
||||||
'/editor': typeof EditorRoute
|
'/editor': typeof EditorRoute
|
||||||
'/login': typeof LoginRoute
|
'/login': typeof LoginRoute
|
||||||
'/register': typeof RegisterRoute
|
'/register': typeof RegisterRoute
|
||||||
'/book': typeof BookLazyRoute
|
|
||||||
'/new/book': typeof NewBookRoute
|
'/new/book': typeof NewBookRoute
|
||||||
'/profile/$username': typeof ProfileUsernameRoute
|
'/profile/$username': typeof ProfileUsernameRoute
|
||||||
|
'/work/book/$bookId': typeof WorkBookBookIdRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
|
|
@ -157,9 +152,9 @@ export interface FileRoutesByTo {
|
||||||
'/editor': typeof EditorRoute
|
'/editor': typeof EditorRoute
|
||||||
'/login': typeof LoginRoute
|
'/login': typeof LoginRoute
|
||||||
'/register': typeof RegisterRoute
|
'/register': typeof RegisterRoute
|
||||||
'/book': typeof BookLazyRoute
|
|
||||||
'/new/book': typeof NewBookRoute
|
'/new/book': typeof NewBookRoute
|
||||||
'/profile/$username': typeof ProfileUsernameRoute
|
'/profile/$username': typeof ProfileUsernameRoute
|
||||||
|
'/work/book/$bookId': typeof WorkBookBookIdRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileRoutesById {
|
export interface FileRoutesById {
|
||||||
|
|
@ -169,9 +164,9 @@ export interface FileRoutesById {
|
||||||
'/editor': typeof EditorRoute
|
'/editor': typeof EditorRoute
|
||||||
'/login': typeof LoginRoute
|
'/login': typeof LoginRoute
|
||||||
'/register': typeof RegisterRoute
|
'/register': typeof RegisterRoute
|
||||||
'/book': typeof BookLazyRoute
|
|
||||||
'/new/book': typeof NewBookRoute
|
'/new/book': typeof NewBookRoute
|
||||||
'/profile/$username': typeof ProfileUsernameRoute
|
'/profile/$username': typeof ProfileUsernameRoute
|
||||||
|
'/work/book/$bookId': typeof WorkBookBookIdRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileRouteTypes {
|
export interface FileRouteTypes {
|
||||||
|
|
@ -182,9 +177,9 @@ export interface FileRouteTypes {
|
||||||
| '/editor'
|
| '/editor'
|
||||||
| '/login'
|
| '/login'
|
||||||
| '/register'
|
| '/register'
|
||||||
| '/book'
|
|
||||||
| '/new/book'
|
| '/new/book'
|
||||||
| '/profile/$username'
|
| '/profile/$username'
|
||||||
|
| '/work/book/$bookId'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to:
|
to:
|
||||||
| '/'
|
| '/'
|
||||||
|
|
@ -192,9 +187,9 @@ export interface FileRouteTypes {
|
||||||
| '/editor'
|
| '/editor'
|
||||||
| '/login'
|
| '/login'
|
||||||
| '/register'
|
| '/register'
|
||||||
| '/book'
|
|
||||||
| '/new/book'
|
| '/new/book'
|
||||||
| '/profile/$username'
|
| '/profile/$username'
|
||||||
|
| '/work/book/$bookId'
|
||||||
id:
|
id:
|
||||||
| '__root__'
|
| '__root__'
|
||||||
| '/'
|
| '/'
|
||||||
|
|
@ -202,9 +197,9 @@ export interface FileRouteTypes {
|
||||||
| '/editor'
|
| '/editor'
|
||||||
| '/login'
|
| '/login'
|
||||||
| '/register'
|
| '/register'
|
||||||
| '/book'
|
|
||||||
| '/new/book'
|
| '/new/book'
|
||||||
| '/profile/$username'
|
| '/profile/$username'
|
||||||
|
| '/work/book/$bookId'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -214,9 +209,9 @@ export interface RootRouteChildren {
|
||||||
EditorRoute: typeof EditorRoute
|
EditorRoute: typeof EditorRoute
|
||||||
LoginRoute: typeof LoginRoute
|
LoginRoute: typeof LoginRoute
|
||||||
RegisterRoute: typeof RegisterRoute
|
RegisterRoute: typeof RegisterRoute
|
||||||
BookLazyRoute: typeof BookLazyRoute
|
|
||||||
NewBookRoute: typeof NewBookRoute
|
NewBookRoute: typeof NewBookRoute
|
||||||
ProfileUsernameRoute: typeof ProfileUsernameRoute
|
ProfileUsernameRoute: typeof ProfileUsernameRoute
|
||||||
|
WorkBookBookIdRoute: typeof WorkBookBookIdRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootRouteChildren: RootRouteChildren = {
|
const rootRouteChildren: RootRouteChildren = {
|
||||||
|
|
@ -225,9 +220,9 @@ const rootRouteChildren: RootRouteChildren = {
|
||||||
EditorRoute: EditorRoute,
|
EditorRoute: EditorRoute,
|
||||||
LoginRoute: LoginRoute,
|
LoginRoute: LoginRoute,
|
||||||
RegisterRoute: RegisterRoute,
|
RegisterRoute: RegisterRoute,
|
||||||
BookLazyRoute: BookLazyRoute,
|
|
||||||
NewBookRoute: NewBookRoute,
|
NewBookRoute: NewBookRoute,
|
||||||
ProfileUsernameRoute: ProfileUsernameRoute,
|
ProfileUsernameRoute: ProfileUsernameRoute,
|
||||||
|
WorkBookBookIdRoute: WorkBookBookIdRoute,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const routeTree = rootRoute
|
export const routeTree = rootRoute
|
||||||
|
|
@ -245,9 +240,9 @@ export const routeTree = rootRoute
|
||||||
"/editor",
|
"/editor",
|
||||||
"/login",
|
"/login",
|
||||||
"/register",
|
"/register",
|
||||||
"/book",
|
|
||||||
"/new/book",
|
"/new/book",
|
||||||
"/profile/$username"
|
"/profile/$username",
|
||||||
|
"/work/book/$bookId"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"/": {
|
"/": {
|
||||||
|
|
@ -265,14 +260,14 @@ export const routeTree = rootRoute
|
||||||
"/register": {
|
"/register": {
|
||||||
"filePath": "register.tsx"
|
"filePath": "register.tsx"
|
||||||
},
|
},
|
||||||
"/book": {
|
|
||||||
"filePath": "book.lazy.tsx"
|
|
||||||
},
|
|
||||||
"/new/book": {
|
"/new/book": {
|
||||||
"filePath": "new/book.tsx"
|
"filePath": "new/book.tsx"
|
||||||
},
|
},
|
||||||
"/profile/$username": {
|
"/profile/$username": {
|
||||||
"filePath": "profile/$username.tsx"
|
"filePath": "profile/$username.tsx"
|
||||||
|
},
|
||||||
|
"/work/book/$bookId": {
|
||||||
|
"filePath": "work/book/$bookId.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,18 @@
|
||||||
import { MutationResolvers, QueryResolvers } from '@generated/types';
|
import { MutationResolvers, QueryResolvers } from '@generated/types';
|
||||||
|
|
||||||
/* Query */
|
/* Query */
|
||||||
import { IdentityResolver } from './resolvers/identityResolver';
|
import { BookChapterManyResolver } from './resolvers/bookChapterManyResolver';
|
||||||
import { UserOneResolver } from './resolvers/userOneResolver';
|
import { BookChapterOneResolver } from './resolvers/bookChapterOneResolver';
|
||||||
import { BookChapterManyResolver } from './resolvers/bookChapterMany';
|
|
||||||
import { BookChapterOneResolver } from './resolvers/bookChapterOne';
|
|
||||||
import { BookManyResolver } from './resolvers/bookManyResolver';
|
import { BookManyResolver } from './resolvers/bookManyResolver';
|
||||||
import { BookOneResolver } from './resolvers/bookOneResolver';
|
import { BookOneResolver } from './resolvers/bookOneResolver';
|
||||||
|
import { IdentityResolver } from './resolvers/identityResolver';
|
||||||
|
import { UserOneResolver } from './resolvers/userOneResolver';
|
||||||
|
|
||||||
/* Mutation */
|
/* Mutation */
|
||||||
import { LoginResolver } from './resolvers/auth/login';
|
import { LoginResolver } from './resolvers/auth/login';
|
||||||
import { LogoutResolver } from './resolvers/auth/logout';
|
import { LogoutResolver } from './resolvers/auth/logout';
|
||||||
import { SignUpResolver } from './resolvers/auth/signUp';
|
import { SignUpResolver } from './resolvers/auth/signUp';
|
||||||
|
import { BookCreateResolver } from './resolvers/bookCreateResolver';
|
||||||
|
|
||||||
export const Query: QueryResolvers = {
|
export const Query: QueryResolvers = {
|
||||||
bookOne: BookOneResolver,
|
bookOne: BookOneResolver,
|
||||||
|
|
@ -26,7 +27,8 @@ export const Query: QueryResolvers = {
|
||||||
export const Mutation: MutationResolvers = {
|
export const Mutation: MutationResolvers = {
|
||||||
signUp: SignUpResolver,
|
signUp: SignUpResolver,
|
||||||
login: LoginResolver,
|
login: LoginResolver,
|
||||||
logout: LogoutResolver
|
logout: LogoutResolver,
|
||||||
|
bookCreate: BookCreateResolver
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Subscription = {};
|
export const Subscription = {};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { IdentityContext } from '@app/server/identity';
|
import { IdentityContext } from '@app/server/identity';
|
||||||
import { prisma } from '@app/server/prisma/client';
|
import { prisma } from '@app/server/prisma/client';
|
||||||
import { BookChapterManyOutput, QueryBookChapterManyArgs } from '@generated/types';
|
import { BookChapterManyOutput, QueryBookChapterManyArgs } from '@generated/types';
|
||||||
|
import { Prisma } from '@prisma/client';
|
||||||
import { toBookChapterSchema } from './mappers/bookChapterMapper';
|
import { toBookChapterSchema } from './mappers/bookChapterMapper';
|
||||||
|
|
||||||
export async function BookChapterManyResolver(
|
export async function BookChapterManyResolver(
|
||||||
|
|
@ -9,11 +10,14 @@ export async function BookChapterManyResolver(
|
||||||
{ identity }: IdentityContext
|
{ identity }: IdentityContext
|
||||||
): Promise<BookChapterManyOutput> {
|
): Promise<BookChapterManyOutput> {
|
||||||
const { bookId, filter } = input;
|
const { bookId, filter } = input;
|
||||||
|
const terms = filter.terms?.toLowerCase();
|
||||||
|
|
||||||
|
const bookChapterWhere: Prisma.BookChapterWhereInput = {
|
||||||
|
AND: [{ bookId }, { OR: terms != null ? [{ title: { contains: terms } }] : undefined }]
|
||||||
|
};
|
||||||
|
|
||||||
const items = await prisma.bookChapter.findMany({
|
const items = await prisma.bookChapter.findMany({
|
||||||
where: {
|
where: bookChapterWhere,
|
||||||
bookId
|
|
||||||
},
|
|
||||||
skip: filter.offset ?? 0,
|
skip: filter.offset ?? 0,
|
||||||
take: filter.size ?? 10
|
take: filter.size ?? 10
|
||||||
});
|
});
|
||||||
27
app/server/graphql/resolvers/bookCreateResolver.ts
Normal file
27
app/server/graphql/resolvers/bookCreateResolver.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { ensureIdentity } from '@app/server/helpers';
|
||||||
|
import { IdentityContext } from '@app/server/identity';
|
||||||
|
import { prisma, uuid } from '@app/server/prisma/client';
|
||||||
|
import { Book, MutationBookCreateArgs } from '@generated/types';
|
||||||
|
import { toBookSchema } from './mappers/bookMapper';
|
||||||
|
|
||||||
|
export async function BookCreateResolver(
|
||||||
|
_: any,
|
||||||
|
{ input }: MutationBookCreateArgs,
|
||||||
|
{ identity }: IdentityContext
|
||||||
|
): Promise<Book> {
|
||||||
|
ensureIdentity(identity);
|
||||||
|
|
||||||
|
console.log(input.title.length);
|
||||||
|
if (input.title.length > 60) throw new Error('Title is too long.');
|
||||||
|
if (input.description.length > 900) throw new Error('Description is too long.');
|
||||||
|
|
||||||
|
const model = await prisma.book.create({
|
||||||
|
data: {
|
||||||
|
id: uuid(),
|
||||||
|
title: input.title,
|
||||||
|
description: input.description,
|
||||||
|
authorId: identity.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return toBookSchema(model);
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { IdentityContext } from '@app/server/identity';
|
import { IdentityContext } from '@app/server/identity';
|
||||||
import { prisma } from '@app/server/prisma/client';
|
import { prisma } from '@app/server/prisma/client';
|
||||||
import { BookManyOutput, QueryBookManyArgs } from '@generated/types';
|
import { BookManyOutput, QueryBookManyArgs } from '@generated/types';
|
||||||
|
import { Prisma } from '@prisma/client';
|
||||||
import { toBookSchema } from './mappers/bookMapper';
|
import { toBookSchema } from './mappers/bookMapper';
|
||||||
|
|
||||||
export async function BookManyResolver(
|
export async function BookManyResolver(
|
||||||
|
|
@ -11,19 +12,19 @@ export async function BookManyResolver(
|
||||||
const { filter } = input;
|
const { filter } = input;
|
||||||
const terms = filter.terms?.toLowerCase();
|
const terms = filter.terms?.toLowerCase();
|
||||||
|
|
||||||
const items = await prisma.book.findMany({
|
const bookWhere: Prisma.BookWhereInput = {
|
||||||
where: {
|
|
||||||
OR:
|
OR:
|
||||||
terms != null
|
terms != null ? [{ title: { contains: terms } }, { author: { username: terms } }] : undefined
|
||||||
? [{ title: { contains: terms } }, { author: { username: terms } }]
|
};
|
||||||
: undefined
|
|
||||||
},
|
const items = await prisma.book.findMany({
|
||||||
|
where: bookWhere,
|
||||||
skip: filter.offset ?? 0,
|
skip: filter.offset ?? 0,
|
||||||
take: filter.size ?? 10
|
take: filter.size ?? 10
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: await Promise.all(items.map(value => toBookSchema(value))),
|
items: await Promise.all(items.map(value => toBookSchema(value))),
|
||||||
total: items.length
|
total: await prisma.book.count({ where: bookWhere })
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ type Mutation {
|
||||||
signUp(input: SignUpInput!): Void
|
signUp(input: SignUpInput!): Void
|
||||||
login(input: LoginInput!): Session!
|
login(input: LoginInput!): Session!
|
||||||
logout: Void
|
logout: Void
|
||||||
|
bookCreate(input: BookCreateInput!): Book!
|
||||||
}
|
}
|
||||||
|
|
||||||
input SignUpInput {
|
input SignUpInput {
|
||||||
|
|
|
||||||
7
app/server/helpers.ts
Normal file
7
app/server/helpers.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { IdentityContext } from './identity';
|
||||||
|
|
||||||
|
export function ensureIdentity(
|
||||||
|
identity: IdentityContext['identity']
|
||||||
|
): asserts identity is NonNullable<IdentityContext['identity']> {
|
||||||
|
if (identity == null) throw new Error('Unauthorized.');
|
||||||
|
}
|
||||||
289
package-lock.json
generated
289
package-lock.json
generated
|
|
@ -28,6 +28,7 @@
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"cookie-parser": "^1.4.7",
|
"cookie-parser": "^1.4.7",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
"graphql": "^16.9.0",
|
"graphql": "^16.9.0",
|
||||||
|
|
@ -2512,9 +2513,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
|
||||||
"integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==",
|
"integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
|
@ -2528,9 +2529,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm": {
|
"node_modules/@esbuild/android-arm": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz",
|
||||||
"integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==",
|
"integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -2544,9 +2545,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm64": {
|
"node_modules/@esbuild/android-arm64": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz",
|
||||||
"integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==",
|
"integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -2560,9 +2561,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-x64": {
|
"node_modules/@esbuild/android-x64": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz",
|
||||||
"integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==",
|
"integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -2576,9 +2577,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-arm64": {
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
|
||||||
"integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==",
|
"integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -2592,9 +2593,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-x64": {
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz",
|
||||||
"integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==",
|
"integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -2608,9 +2609,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-arm64": {
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz",
|
||||||
"integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==",
|
"integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -2624,9 +2625,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-x64": {
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz",
|
||||||
"integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==",
|
"integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -2640,9 +2641,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm": {
|
"node_modules/@esbuild/linux-arm": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz",
|
||||||
"integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==",
|
"integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -2656,9 +2657,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm64": {
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz",
|
||||||
"integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==",
|
"integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -2672,9 +2673,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ia32": {
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz",
|
||||||
"integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==",
|
"integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
|
|
@ -2688,9 +2689,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-loong64": {
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz",
|
||||||
"integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==",
|
"integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
|
|
@ -2704,9 +2705,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-mips64el": {
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz",
|
||||||
"integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==",
|
"integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
|
|
@ -2720,9 +2721,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ppc64": {
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz",
|
||||||
"integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==",
|
"integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
|
@ -2736,9 +2737,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-riscv64": {
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz",
|
||||||
"integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==",
|
"integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
|
@ -2752,9 +2753,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-s390x": {
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz",
|
||||||
"integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==",
|
"integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
|
|
@ -2768,9 +2769,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-x64": {
|
"node_modules/@esbuild/linux-x64": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
|
||||||
"integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==",
|
"integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -2784,9 +2785,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-arm64": {
|
"node_modules/@esbuild/netbsd-arm64": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz",
|
||||||
"integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==",
|
"integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -2800,9 +2801,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-x64": {
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz",
|
||||||
"integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==",
|
"integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -2816,9 +2817,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-arm64": {
|
"node_modules/@esbuild/openbsd-arm64": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz",
|
||||||
"integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==",
|
"integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -2832,9 +2833,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-x64": {
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz",
|
||||||
"integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==",
|
"integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -2848,9 +2849,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/sunos-x64": {
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz",
|
||||||
"integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==",
|
"integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -2864,9 +2865,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-arm64": {
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz",
|
||||||
"integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==",
|
"integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -2880,9 +2881,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-ia32": {
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz",
|
||||||
"integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==",
|
"integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
|
|
@ -2896,9 +2897,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-x64": {
|
"node_modules/@esbuild/win32-x64": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz",
|
||||||
"integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==",
|
"integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -3712,13 +3713,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@graphql-tools/apollo-engine-loader/node_modules/@whatwg-node/fetch": {
|
"node_modules/@graphql-tools/apollo-engine-loader/node_modules/@whatwg-node/fetch": {
|
||||||
"version": "0.10.7",
|
"version": "0.10.8",
|
||||||
"resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.10.7.tgz",
|
"resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.10.8.tgz",
|
||||||
"integrity": "sha512-sL31zX8BqZovZc38ovBFmKEfao9AzZ/24sWSHKNhDhcnzIO/PYAX2xF6vYtgU9hinrEGlvScTTyKSMynHGdfEA==",
|
"integrity": "sha512-Rw9z3ctmeEj8QIB9MavkNJqekiu9usBCSMZa+uuAvM0lF3v70oQVCXNppMIqaV6OTZbdaHF1M2HLow58DEw+wg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@whatwg-node/node-fetch": "^0.7.19",
|
"@whatwg-node/node-fetch": "^0.7.21",
|
||||||
"urlpattern-polyfill": "^10.0.0"
|
"urlpattern-polyfill": "^10.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -3726,9 +3727,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@graphql-tools/apollo-engine-loader/node_modules/@whatwg-node/node-fetch": {
|
"node_modules/@graphql-tools/apollo-engine-loader/node_modules/@whatwg-node/node-fetch": {
|
||||||
"version": "0.7.20",
|
"version": "0.7.21",
|
||||||
"resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.7.20.tgz",
|
"resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.7.21.tgz",
|
||||||
"integrity": "sha512-2LI5HkZcdAODb6umb7MUxkWYNUi3j6apgQvIzp+X3w3AVPdvVsshvjaMo4gwLeFtfs+cyMNOLTm9Hhm/TXq6DA==",
|
"integrity": "sha512-QC16IdsEyIW7kZd77aodrMO7zAoDyyqRCTLg+qG4wqtP4JV9AA+p7/lgqMdD29XyiYdVvIdFrfI9yh7B1QvRvw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -4021,13 +4022,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@graphql-tools/executor-http/node_modules/@whatwg-node/fetch": {
|
"node_modules/@graphql-tools/executor-http/node_modules/@whatwg-node/fetch": {
|
||||||
"version": "0.10.7",
|
"version": "0.10.8",
|
||||||
"resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.10.7.tgz",
|
"resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.10.8.tgz",
|
||||||
"integrity": "sha512-sL31zX8BqZovZc38ovBFmKEfao9AzZ/24sWSHKNhDhcnzIO/PYAX2xF6vYtgU9hinrEGlvScTTyKSMynHGdfEA==",
|
"integrity": "sha512-Rw9z3ctmeEj8QIB9MavkNJqekiu9usBCSMZa+uuAvM0lF3v70oQVCXNppMIqaV6OTZbdaHF1M2HLow58DEw+wg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@whatwg-node/node-fetch": "^0.7.19",
|
"@whatwg-node/node-fetch": "^0.7.21",
|
||||||
"urlpattern-polyfill": "^10.0.0"
|
"urlpattern-polyfill": "^10.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -4035,9 +4036,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@graphql-tools/executor-http/node_modules/@whatwg-node/node-fetch": {
|
"node_modules/@graphql-tools/executor-http/node_modules/@whatwg-node/node-fetch": {
|
||||||
"version": "0.7.20",
|
"version": "0.7.21",
|
||||||
"resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.7.20.tgz",
|
"resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.7.21.tgz",
|
||||||
"integrity": "sha512-2LI5HkZcdAODb6umb7MUxkWYNUi3j6apgQvIzp+X3w3AVPdvVsshvjaMo4gwLeFtfs+cyMNOLTm9Hhm/TXq6DA==",
|
"integrity": "sha512-QC16IdsEyIW7kZd77aodrMO7zAoDyyqRCTLg+qG4wqtP4JV9AA+p7/lgqMdD29XyiYdVvIdFrfI9yh7B1QvRvw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -4222,13 +4223,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@graphql-tools/github-loader/node_modules/@whatwg-node/fetch": {
|
"node_modules/@graphql-tools/github-loader/node_modules/@whatwg-node/fetch": {
|
||||||
"version": "0.10.7",
|
"version": "0.10.8",
|
||||||
"resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.10.7.tgz",
|
"resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.10.8.tgz",
|
||||||
"integrity": "sha512-sL31zX8BqZovZc38ovBFmKEfao9AzZ/24sWSHKNhDhcnzIO/PYAX2xF6vYtgU9hinrEGlvScTTyKSMynHGdfEA==",
|
"integrity": "sha512-Rw9z3ctmeEj8QIB9MavkNJqekiu9usBCSMZa+uuAvM0lF3v70oQVCXNppMIqaV6OTZbdaHF1M2HLow58DEw+wg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@whatwg-node/node-fetch": "^0.7.19",
|
"@whatwg-node/node-fetch": "^0.7.21",
|
||||||
"urlpattern-polyfill": "^10.0.0"
|
"urlpattern-polyfill": "^10.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -4236,9 +4237,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@graphql-tools/github-loader/node_modules/@whatwg-node/node-fetch": {
|
"node_modules/@graphql-tools/github-loader/node_modules/@whatwg-node/node-fetch": {
|
||||||
"version": "0.7.20",
|
"version": "0.7.21",
|
||||||
"resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.7.20.tgz",
|
"resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.7.21.tgz",
|
||||||
"integrity": "sha512-2LI5HkZcdAODb6umb7MUxkWYNUi3j6apgQvIzp+X3w3AVPdvVsshvjaMo4gwLeFtfs+cyMNOLTm9Hhm/TXq6DA==",
|
"integrity": "sha512-QC16IdsEyIW7kZd77aodrMO7zAoDyyqRCTLg+qG4wqtP4JV9AA+p7/lgqMdD29XyiYdVvIdFrfI9yh7B1QvRvw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -4589,13 +4590,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@graphql-tools/prisma-loader/node_modules/@whatwg-node/fetch": {
|
"node_modules/@graphql-tools/prisma-loader/node_modules/@whatwg-node/fetch": {
|
||||||
"version": "0.10.7",
|
"version": "0.10.8",
|
||||||
"resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.10.7.tgz",
|
"resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.10.8.tgz",
|
||||||
"integrity": "sha512-sL31zX8BqZovZc38ovBFmKEfao9AzZ/24sWSHKNhDhcnzIO/PYAX2xF6vYtgU9hinrEGlvScTTyKSMynHGdfEA==",
|
"integrity": "sha512-Rw9z3ctmeEj8QIB9MavkNJqekiu9usBCSMZa+uuAvM0lF3v70oQVCXNppMIqaV6OTZbdaHF1M2HLow58DEw+wg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@whatwg-node/node-fetch": "^0.7.19",
|
"@whatwg-node/node-fetch": "^0.7.21",
|
||||||
"urlpattern-polyfill": "^10.0.0"
|
"urlpattern-polyfill": "^10.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -4603,9 +4604,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@graphql-tools/prisma-loader/node_modules/@whatwg-node/node-fetch": {
|
"node_modules/@graphql-tools/prisma-loader/node_modules/@whatwg-node/node-fetch": {
|
||||||
"version": "0.7.20",
|
"version": "0.7.21",
|
||||||
"resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.7.20.tgz",
|
"resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.7.21.tgz",
|
||||||
"integrity": "sha512-2LI5HkZcdAODb6umb7MUxkWYNUi3j6apgQvIzp+X3w3AVPdvVsshvjaMo4gwLeFtfs+cyMNOLTm9Hhm/TXq6DA==",
|
"integrity": "sha512-QC16IdsEyIW7kZd77aodrMO7zAoDyyqRCTLg+qG4wqtP4JV9AA+p7/lgqMdD29XyiYdVvIdFrfI9yh7B1QvRvw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -4733,13 +4734,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@graphql-tools/url-loader/node_modules/@whatwg-node/fetch": {
|
"node_modules/@graphql-tools/url-loader/node_modules/@whatwg-node/fetch": {
|
||||||
"version": "0.10.7",
|
"version": "0.10.8",
|
||||||
"resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.10.7.tgz",
|
"resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.10.8.tgz",
|
||||||
"integrity": "sha512-sL31zX8BqZovZc38ovBFmKEfao9AzZ/24sWSHKNhDhcnzIO/PYAX2xF6vYtgU9hinrEGlvScTTyKSMynHGdfEA==",
|
"integrity": "sha512-Rw9z3ctmeEj8QIB9MavkNJqekiu9usBCSMZa+uuAvM0lF3v70oQVCXNppMIqaV6OTZbdaHF1M2HLow58DEw+wg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@whatwg-node/node-fetch": "^0.7.19",
|
"@whatwg-node/node-fetch": "^0.7.21",
|
||||||
"urlpattern-polyfill": "^10.0.0"
|
"urlpattern-polyfill": "^10.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -4747,9 +4748,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@graphql-tools/url-loader/node_modules/@whatwg-node/node-fetch": {
|
"node_modules/@graphql-tools/url-loader/node_modules/@whatwg-node/node-fetch": {
|
||||||
"version": "0.7.20",
|
"version": "0.7.21",
|
||||||
"resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.7.20.tgz",
|
"resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.7.21.tgz",
|
||||||
"integrity": "sha512-2LI5HkZcdAODb6umb7MUxkWYNUi3j6apgQvIzp+X3w3AVPdvVsshvjaMo4gwLeFtfs+cyMNOLTm9Hhm/TXq6DA==",
|
"integrity": "sha512-QC16IdsEyIW7kZd77aodrMO7zAoDyyqRCTLg+qG4wqtP4JV9AA+p7/lgqMdD29XyiYdVvIdFrfI9yh7B1QvRvw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -8800,6 +8801,12 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/dayjs": {
|
||||||
|
"version": "1.11.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
|
||||||
|
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/debounce": {
|
"node_modules/debounce": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
|
||||||
|
|
@ -9389,9 +9396,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.25.4",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
|
||||||
"integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==",
|
"integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -9401,31 +9408,31 @@
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@esbuild/aix-ppc64": "0.25.4",
|
"@esbuild/aix-ppc64": "0.25.5",
|
||||||
"@esbuild/android-arm": "0.25.4",
|
"@esbuild/android-arm": "0.25.5",
|
||||||
"@esbuild/android-arm64": "0.25.4",
|
"@esbuild/android-arm64": "0.25.5",
|
||||||
"@esbuild/android-x64": "0.25.4",
|
"@esbuild/android-x64": "0.25.5",
|
||||||
"@esbuild/darwin-arm64": "0.25.4",
|
"@esbuild/darwin-arm64": "0.25.5",
|
||||||
"@esbuild/darwin-x64": "0.25.4",
|
"@esbuild/darwin-x64": "0.25.5",
|
||||||
"@esbuild/freebsd-arm64": "0.25.4",
|
"@esbuild/freebsd-arm64": "0.25.5",
|
||||||
"@esbuild/freebsd-x64": "0.25.4",
|
"@esbuild/freebsd-x64": "0.25.5",
|
||||||
"@esbuild/linux-arm": "0.25.4",
|
"@esbuild/linux-arm": "0.25.5",
|
||||||
"@esbuild/linux-arm64": "0.25.4",
|
"@esbuild/linux-arm64": "0.25.5",
|
||||||
"@esbuild/linux-ia32": "0.25.4",
|
"@esbuild/linux-ia32": "0.25.5",
|
||||||
"@esbuild/linux-loong64": "0.25.4",
|
"@esbuild/linux-loong64": "0.25.5",
|
||||||
"@esbuild/linux-mips64el": "0.25.4",
|
"@esbuild/linux-mips64el": "0.25.5",
|
||||||
"@esbuild/linux-ppc64": "0.25.4",
|
"@esbuild/linux-ppc64": "0.25.5",
|
||||||
"@esbuild/linux-riscv64": "0.25.4",
|
"@esbuild/linux-riscv64": "0.25.5",
|
||||||
"@esbuild/linux-s390x": "0.25.4",
|
"@esbuild/linux-s390x": "0.25.5",
|
||||||
"@esbuild/linux-x64": "0.25.4",
|
"@esbuild/linux-x64": "0.25.5",
|
||||||
"@esbuild/netbsd-arm64": "0.25.4",
|
"@esbuild/netbsd-arm64": "0.25.5",
|
||||||
"@esbuild/netbsd-x64": "0.25.4",
|
"@esbuild/netbsd-x64": "0.25.5",
|
||||||
"@esbuild/openbsd-arm64": "0.25.4",
|
"@esbuild/openbsd-arm64": "0.25.5",
|
||||||
"@esbuild/openbsd-x64": "0.25.4",
|
"@esbuild/openbsd-x64": "0.25.5",
|
||||||
"@esbuild/sunos-x64": "0.25.4",
|
"@esbuild/sunos-x64": "0.25.5",
|
||||||
"@esbuild/win32-arm64": "0.25.4",
|
"@esbuild/win32-arm64": "0.25.5",
|
||||||
"@esbuild/win32-ia32": "0.25.4",
|
"@esbuild/win32-ia32": "0.25.5",
|
||||||
"@esbuild/win32-x64": "0.25.4"
|
"@esbuild/win32-x64": "0.25.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/esbuild-register": {
|
"node_modules/esbuild-register": {
|
||||||
|
|
@ -11863,9 +11870,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jackspeak": {
|
"node_modules/jackspeak": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz",
|
||||||
"integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==",
|
"integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==",
|
||||||
"license": "BlueOak-1.0.0",
|
"license": "BlueOak-1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@isaacs/cliui": "^8.0.2"
|
"@isaacs/cliui": "^8.0.2"
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"cookie-parser": "^1.4.7",
|
"cookie-parser": "^1.4.7",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
"graphql": "^16.9.0",
|
"graphql": "^16.9.0",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue