mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-24 20:36:40 +07:00
added notes sidebar
This commit is contained in:
parent
fe3db608d6
commit
cedf0adcfa
14 changed files with 376 additions and 153 deletions
|
@ -1,8 +1,20 @@
|
|||
import { ApiGetNotesRequest, ApiGetNotesResponse } from '~/api/notes/types';
|
||||
import { URLS } from '~/constants/urls';
|
||||
import { api, cleanResult } from '~/utils/api';
|
||||
import {
|
||||
ApiGetNotesRequest,
|
||||
ApiGetNotesResponse,
|
||||
ApiPostNoteRequest,
|
||||
ApiPostNoteResponse,
|
||||
} from "~/api/notes/types";
|
||||
import { URLS } from "~/constants/urls";
|
||||
import { api, cleanResult } from "~/utils/api";
|
||||
|
||||
export const apiGetNotes = ({ limit, offset, search }: ApiGetNotesRequest) =>
|
||||
api
|
||||
.get<ApiGetNotesResponse>(URLS.NOTES, { params: { limit, offset, search } })
|
||||
.then(cleanResult);
|
||||
|
||||
export const apiPostNote = ({ text }: ApiPostNoteRequest) =>
|
||||
api
|
||||
.post<ApiPostNoteResponse>(URLS.NOTES, {
|
||||
text,
|
||||
})
|
||||
.then(cleanResult);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Note } from '~/types/notes';
|
||||
import { Note } from "~/types/notes";
|
||||
|
||||
export interface ApiGetNotesRequest {
|
||||
limit: number;
|
||||
|
@ -10,3 +10,9 @@ export interface ApiGetNotesResponse {
|
|||
list: Note[];
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
export interface ApiPostNoteRequest {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface ApiPostNoteResponse extends Note {}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import React, { VFC } from 'react';
|
||||
import React, { VFC } from "react";
|
||||
|
||||
import { Card } from '~/components/containers/Card';
|
||||
import { Markdown } from '~/components/containers/Markdown';
|
||||
import { Padder } from '~/components/containers/Padder';
|
||||
import { NoteMenu } from '~/components/notes/NoteMenu';
|
||||
import { formatText, getPrettyDate } from '~/utils/dom';
|
||||
import { Card } from "~/components/containers/Card";
|
||||
import { Markdown } from "~/components/containers/Markdown";
|
||||
import { Padder } from "~/components/containers/Padder";
|
||||
import { NoteMenu } from "~/components/notes/NoteMenu";
|
||||
import { formatText, getPrettyDate } from "~/utils/dom";
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
import styles from "./styles.module.scss";
|
||||
|
||||
interface NoteCardProps {
|
||||
content: string;
|
||||
|
@ -17,7 +17,10 @@ const NoteCard: VFC<NoteCardProps> = ({ content, createdAt }) => (
|
|||
<Card className={styles.note}>
|
||||
<Padder>
|
||||
<NoteMenu onEdit={console.log} onDelete={console.log} />
|
||||
<Markdown className={styles.wrap} dangerouslySetInnerHTML={{ __html: formatText(content) }} />
|
||||
<Markdown
|
||||
className={styles.wrap}
|
||||
dangerouslySetInnerHTML={{ __html: formatText(content) }}
|
||||
/>
|
||||
</Padder>
|
||||
|
||||
<Padder className={styles.footer}>{getPrettyDate(createdAt)}</Padder>
|
||||
|
|
|
@ -6,10 +6,6 @@
|
|||
word-break: break-word;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
|
||||
& > * {
|
||||
@include row_shadow;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
|
|
97
src/components/notes/NoteCreationForm/index.tsx
Normal file
97
src/components/notes/NoteCreationForm/index.tsx
Normal file
|
@ -0,0 +1,97 @@
|
|||
import React, { FC, useCallback, useState } from "react";
|
||||
|
||||
import { FormikConfig, useFormik } from "formik";
|
||||
import { object, string, Asserts } from "yup";
|
||||
|
||||
import { Card } from "~/components/containers/Card";
|
||||
import { Filler } from "~/components/containers/Filler";
|
||||
import { Group } from "~/components/containers/Group";
|
||||
import { Button } from "~/components/input/Button";
|
||||
import { Textarea } from "~/components/input/Textarea";
|
||||
import { useRandomPhrase } from "~/constants/phrases";
|
||||
import { Note } from "~/types/notes";
|
||||
import { getErrorMessage } from "~/utils/errors/getErrorMessage";
|
||||
import { showErrorToast } from "~/utils/errors/showToast";
|
||||
|
||||
import styles from "./styles.module.scss";
|
||||
|
||||
interface NoteCreationFormProps {
|
||||
onSubmit: (text: string, callback: (note: Note) => void) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
const validationSchema = object({
|
||||
text: string().required("Напишите что-нибудь"),
|
||||
});
|
||||
|
||||
type Values = Asserts<typeof validationSchema>;
|
||||
|
||||
const NoteCreationForm: FC<NoteCreationFormProps> = ({
|
||||
onSubmit,
|
||||
onCancel,
|
||||
}) => {
|
||||
const placeholder = useRandomPhrase("SIMPLE");
|
||||
|
||||
const submit = useCallback<FormikConfig<Values>["onSubmit"]>(
|
||||
async ({ text }, { resetForm, setSubmitting, setErrors }) => {
|
||||
try {
|
||||
await onSubmit(text, () => resetForm());
|
||||
} catch (error) {
|
||||
const message = getErrorMessage(error);
|
||||
if (message) {
|
||||
setErrors({ text: message });
|
||||
return;
|
||||
}
|
||||
|
||||
showErrorToast(error);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
},
|
||||
[onSubmit],
|
||||
);
|
||||
|
||||
const {
|
||||
values,
|
||||
errors,
|
||||
handleChange,
|
||||
handleSubmit,
|
||||
touched,
|
||||
handleBlur,
|
||||
} = useFormik<Values>({
|
||||
initialValues: { text: "" },
|
||||
validationSchema,
|
||||
onSubmit: submit,
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Card className={styles.card}>
|
||||
<div className={styles.row}>
|
||||
<Textarea
|
||||
handler={handleChange("text")}
|
||||
value={values.text}
|
||||
error={touched.text ? errors.text : undefined}
|
||||
onBlur={handleBlur("text")}
|
||||
placeholder={placeholder}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Group horizontal className={styles.row}>
|
||||
<Filler />
|
||||
|
||||
<Button size="mini" type="button" color="link" onClick={onCancel}>
|
||||
Отмена
|
||||
</Button>
|
||||
|
||||
<Button size="mini" type="submit" color="gray">
|
||||
ОК
|
||||
</Button>
|
||||
</Group>
|
||||
</Card>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export { NoteCreationForm };
|
11
src/components/notes/NoteCreationForm/styles.module.scss
Normal file
11
src/components/notes/NoteCreationForm/styles.module.scss
Normal file
|
@ -0,0 +1,11 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
.card {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.row {
|
||||
@include row_shadow;
|
||||
|
||||
padding: $gap / 2;
|
||||
}
|
|
@ -1,10 +1,8 @@
|
|||
import React, { VFC } from 'react';
|
||||
import { VFC } from "react";
|
||||
|
||||
import { useStackContext } from '~/components/sidebar/SidebarStack';
|
||||
import { SidebarStackCard } from '~/components/sidebar/SidebarStackCard';
|
||||
import { SettingsNotes } from '~/containers/settings/SettingsNotes';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
import { useStackContext } from "~/components/sidebar/SidebarStack";
|
||||
import { SidebarStackCard } from "~/components/sidebar/SidebarStackCard";
|
||||
import { SettingsNotes } from "~/containers/settings/SettingsNotes";
|
||||
|
||||
interface ProfileSidebarNotesProps {}
|
||||
|
||||
|
@ -12,10 +10,13 @@ const ProfileSidebarNotes: VFC<ProfileSidebarNotesProps> = () => {
|
|||
const { closeAllTabs } = useStackContext();
|
||||
|
||||
return (
|
||||
<SidebarStackCard width={800} headerFeature="back" title="Заметки" onBackPress={closeAllTabs}>
|
||||
<div className={styles.scroller}>
|
||||
<SettingsNotes />
|
||||
</div>
|
||||
<SidebarStackCard
|
||||
width={480}
|
||||
headerFeature="back"
|
||||
title="Заметки"
|
||||
onBackPress={closeAllTabs}
|
||||
>
|
||||
<SettingsNotes />
|
||||
</SidebarStackCard>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
import React, { useCallback, VFC } from 'react';
|
||||
import React, { useCallback, VFC } from "react";
|
||||
|
||||
import classNames from 'classnames';
|
||||
import classNames from "classnames";
|
||||
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import { MenuButton, MenuItemWithIcon } from '~/components/menu';
|
||||
import { VerticalMenu } from '~/components/menu/VerticalMenu';
|
||||
import { useStackContext } from '~/components/sidebar/SidebarStack';
|
||||
import { ProfileSidebarHead } from '~/containers/profile/ProfileSidebarHead';
|
||||
import { ProfileStats } from '~/containers/profile/ProfileStats';
|
||||
import { useAuth } from '~/hooks/auth/useAuth';
|
||||
import markdown from '~/styles/common/markdown.module.scss';
|
||||
import { Filler } from "~/components/containers/Filler";
|
||||
import { Group } from "~/components/containers/Group";
|
||||
import { Button } from "~/components/input/Button";
|
||||
import { Icon } from "~/components/input/Icon";
|
||||
import { MenuButton, MenuItemWithIcon } from "~/components/menu";
|
||||
import { VerticalMenu } from "~/components/menu/VerticalMenu";
|
||||
import { useStackContext } from "~/components/sidebar/SidebarStack";
|
||||
import { ProfileSidebarHead } from "~/containers/profile/ProfileSidebarHead";
|
||||
import { ProfileStats } from "~/containers/profile/ProfileStats";
|
||||
import { useAuth } from "~/hooks/auth/useAuth";
|
||||
import markdown from "~/styles/common/markdown.module.scss";
|
||||
|
||||
import { ProfileSidebarLogoutButton } from '../ProfileSidebarLogoutButton';
|
||||
import { ProfileSidebarLogoutButton } from "../ProfileSidebarLogoutButton";
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
import styles from "./styles.module.scss";
|
||||
|
||||
interface ProfileSidebarMenuProps {
|
||||
onClose: () => void;
|
||||
|
@ -40,7 +40,13 @@ const ProfileSidebarMenu: VFC<ProfileSidebarMenuProps> = ({ onClose }) => {
|
|||
<Filler className={classNames(markdown.wrapper, styles.text)}>
|
||||
<Group>
|
||||
<VerticalMenu className={styles.menu}>
|
||||
<VerticalMenu.Item onClick={() => setActiveTab(0)}>Настройки</VerticalMenu.Item>
|
||||
<VerticalMenu.Item onClick={() => setActiveTab(0)}>
|
||||
Настройки
|
||||
</VerticalMenu.Item>
|
||||
|
||||
<VerticalMenu.Item onClick={() => setActiveTab(1)}>
|
||||
Заметки
|
||||
</VerticalMenu.Item>
|
||||
</VerticalMenu>
|
||||
|
||||
<div className={styles.stats}>
|
||||
|
@ -51,7 +57,7 @@ const ProfileSidebarMenu: VFC<ProfileSidebarMenuProps> = ({ onClose }) => {
|
|||
|
||||
<Group className={styles.buttons} horizontal>
|
||||
<Filler />
|
||||
<ProfileSidebarLogoutButton onLogout={onLogout}/>
|
||||
<ProfileSidebarLogoutButton onLogout={onLogout} />
|
||||
</Group>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,56 +1,69 @@
|
|||
import React, { useState, VFC } from 'react';
|
||||
import { FC, useState, VFC } from "react";
|
||||
|
||||
import { Card } from '~/components/containers/Card';
|
||||
import { Columns } from '~/components/containers/Columns';
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Padder } from '~/components/containers/Padder';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import { InputText } from '~/components/input/InputText';
|
||||
import { Textarea } from '~/components/input/Textarea';
|
||||
import { HorizontalMenu } from '~/components/menu/HorizontalMenu';
|
||||
import { NoteCard } from '~/components/notes/NoteCard';
|
||||
import { useGetNotes } from '~/hooks/notes/useGetNotes';
|
||||
import { Filler } from "~/components/containers/Filler";
|
||||
import { Group } from "~/components/containers/Group";
|
||||
import { Button } from "~/components/input/Button";
|
||||
import { NoteCard } from "~/components/notes/NoteCard";
|
||||
import { NoteCreationForm } from "~/components/notes/NoteCreationForm";
|
||||
import { NoteProvider, useNotesContext } from "~/utils/providers/NoteProvider";
|
||||
|
||||
import styles from "./styles.module.scss";
|
||||
|
||||
interface SettingsNotesProps {}
|
||||
|
||||
const SettingsNotes: VFC<SettingsNotesProps> = () => {
|
||||
const [text, setText] = useState('');
|
||||
const { notes } = useGetNotes('');
|
||||
const List = () => {
|
||||
const { notes } = useNotesContext();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Padder>
|
||||
<Group horizontal>
|
||||
<HorizontalMenu>
|
||||
<HorizontalMenu.Item active>Новые</HorizontalMenu.Item>
|
||||
<HorizontalMenu.Item>Старые</HorizontalMenu.Item>
|
||||
</HorizontalMenu>
|
||||
<>
|
||||
{notes.map(note => (
|
||||
<NoteCard
|
||||
key={note.id}
|
||||
content={note.content}
|
||||
createdAt={note.created_at}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
<Filler />
|
||||
const Form: FC<{ onCancel: () => void }> = ({ onCancel }) => {
|
||||
const { submit } = useNotesContext();
|
||||
return <NoteCreationForm onSubmit={submit} onCancel={onCancel} />;
|
||||
};
|
||||
|
||||
<InputText suffix={<Icon icon="search" size={24} />} />
|
||||
</Group>
|
||||
</Padder>
|
||||
const SettingsNotes: VFC<SettingsNotesProps> = () => {
|
||||
const [formIsShown, setFormIsShown] = useState(false);
|
||||
|
||||
<Columns>
|
||||
<Card>
|
||||
<Group>
|
||||
<Textarea handler={setText} value={text} />
|
||||
|
||||
<Group horizontal>
|
||||
return (
|
||||
<NoteProvider>
|
||||
<div className={styles.grid}>
|
||||
<div className={styles.head}>
|
||||
{formIsShown ? (
|
||||
<Form onCancel={() => setFormIsShown(false)} />
|
||||
) : (
|
||||
<Group className={styles.showForm} horizontal>
|
||||
<Filler />
|
||||
<Button size="mini">Добавить</Button>
|
||||
<Button
|
||||
onClick={() => setFormIsShown(true)}
|
||||
size="mini"
|
||||
iconRight="plus"
|
||||
color="secondary"
|
||||
>
|
||||
Добавить
|
||||
</Button>
|
||||
</Group>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.list}>
|
||||
<Group>
|
||||
<Group>
|
||||
<List />
|
||||
</Group>
|
||||
</Group>
|
||||
</Card>
|
||||
|
||||
{notes.map(note => (
|
||||
<NoteCard key={note.id} content={note.content} createdAt={note.created_at} />
|
||||
))}
|
||||
</Columns>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NoteProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
z-index: 4;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.head {
|
||||
@include row_shadow;
|
||||
|
||||
width: 100%;
|
||||
padding: $gap;
|
||||
}
|
||||
|
||||
.list {
|
||||
@include row_shadow;
|
||||
|
||||
overflow-y: auto;
|
||||
flex: 1 1;
|
||||
overflow: auto;
|
||||
padding: 10px;
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import useSWRInfinite, { SWRInfiniteKeyLoader } from 'swr/infinite';
|
||||
import { apiGetNotes } from '~/api/notes';
|
||||
import { ApiGetNotesRequest } from '~/api/notes/types';
|
||||
import { useAuth } from '~/hooks/auth/useAuth';
|
||||
import { GetLabNodesRequest, ILabNode } from '~/types/lab';
|
||||
import { flatten, uniqBy } from '~/utils/ramda';
|
||||
|
||||
const DEFAULT_COUNT = 20;
|
||||
|
||||
const getKey: (isUser: boolean, search: string) => SWRInfiniteKeyLoader = (isUser, search) => (
|
||||
index,
|
||||
prev: ILabNode[]
|
||||
) => {
|
||||
if (!isUser) return null;
|
||||
if (index > 0 && (!prev?.length || prev.length < 20)) return null;
|
||||
|
||||
const props: GetLabNodesRequest = {
|
||||
limit: DEFAULT_COUNT,
|
||||
offset: index * DEFAULT_COUNT,
|
||||
search: search || '',
|
||||
};
|
||||
|
||||
return JSON.stringify(props);
|
||||
};
|
||||
|
||||
const parseKey = (key: string): ApiGetNotesRequest => {
|
||||
try {
|
||||
return JSON.parse(key);
|
||||
} catch (error) {
|
||||
return { limit: DEFAULT_COUNT, offset: 0, search: '' };
|
||||
}
|
||||
};
|
||||
|
||||
export const useGetNotes = (search: string) => {
|
||||
const { isUser } = useAuth();
|
||||
|
||||
const { data, isValidating, size, setSize, mutate } = useSWRInfinite(
|
||||
getKey(isUser, search),
|
||||
async (key: string) => {
|
||||
const result = await apiGetNotes(parseKey(key));
|
||||
return result.list;
|
||||
},
|
||||
{
|
||||
dedupingInterval: 300,
|
||||
}
|
||||
);
|
||||
|
||||
const notes = useMemo(() => uniqBy(n => n.id, flatten(data || [])), [data]);
|
||||
const hasMore = (data?.[size - 1]?.length || 0) >= 1;
|
||||
const loadMore = useCallback(() => setSize(size + 1), [setSize, size]);
|
||||
|
||||
return { notes, hasMore, loadMore, isLoading: !data && isValidating };
|
||||
};
|
81
src/hooks/notes/useNotes.ts
Normal file
81
src/hooks/notes/useNotes.ts
Normal file
|
@ -0,0 +1,81 @@
|
|||
import { useCallback, useMemo, useState } from "react";
|
||||
|
||||
import useSWRInfinite from "swr/infinite";
|
||||
import { SWRInfiniteKeyLoader } from "swr/infinite";
|
||||
|
||||
import { apiGetNotes, apiPostNote } from "~/api/notes";
|
||||
import { ApiGetNotesRequest } from "~/api/notes/types";
|
||||
import { useAuth } from "~/hooks/auth/useAuth";
|
||||
import { GetLabNodesRequest, ILabNode } from "~/types/lab";
|
||||
import { Note } from "~/types/notes";
|
||||
import { showErrorToast } from "~/utils/errors/showToast";
|
||||
import { flatten, uniqBy } from "~/utils/ramda";
|
||||
|
||||
const DEFAULT_COUNT = 20;
|
||||
|
||||
const getKey: (isUser: boolean, search: string) => SWRInfiniteKeyLoader = (
|
||||
isUser,
|
||||
search,
|
||||
) => (index, prev: ILabNode[]) => {
|
||||
if (!isUser) return null;
|
||||
if (index > 0 && (!prev?.length || prev.length < 20)) return null;
|
||||
|
||||
const props: GetLabNodesRequest = {
|
||||
limit: DEFAULT_COUNT,
|
||||
offset: index * DEFAULT_COUNT,
|
||||
search: search || "",
|
||||
};
|
||||
|
||||
return JSON.stringify(props);
|
||||
};
|
||||
|
||||
const parseKey = (key: string): ApiGetNotesRequest => {
|
||||
try {
|
||||
return JSON.parse(key);
|
||||
} catch (error) {
|
||||
return { limit: DEFAULT_COUNT, offset: 0, search: "" };
|
||||
}
|
||||
};
|
||||
|
||||
export const useNotes = (search: string) => {
|
||||
const { isUser } = useAuth();
|
||||
|
||||
const { data, isValidating, size, setSize, mutate } = useSWRInfinite(
|
||||
getKey(isUser, search),
|
||||
async (key: string) => {
|
||||
const result = await apiGetNotes(parseKey(key));
|
||||
return result.list;
|
||||
},
|
||||
{
|
||||
dedupingInterval: 300,
|
||||
},
|
||||
);
|
||||
|
||||
const submit = useCallback(
|
||||
async (text: string, onSuccess: (note: Note) => void) => {
|
||||
const result = await apiPostNote({ text });
|
||||
|
||||
if (data) {
|
||||
mutate(data?.map((it, index) => (index === 0 ? [result, ...it] : it)));
|
||||
}
|
||||
|
||||
onSuccess(result);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const notes = useMemo(() => uniqBy(n => n.id, flatten(data || [])), [data]);
|
||||
const hasMore = (data?.[size - 1]?.length || 0) >= 1;
|
||||
const loadMore = useCallback(() => setSize(size + 1), [setSize, size]);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
notes,
|
||||
hasMore,
|
||||
loadMore,
|
||||
isLoading: !data && isValidating,
|
||||
submit,
|
||||
}),
|
||||
[notes, hasMore, loadMore, data, isValidating, submit],
|
||||
);
|
||||
};
|
|
@ -1,5 +1,7 @@
|
|||
import { ERRORS } from '~/constants/errors';
|
||||
import { IUser } from '~/types/auth';
|
||||
import { Context } from "react";
|
||||
|
||||
import { ERRORS } from "~/constants/errors";
|
||||
import { IUser } from "~/types/auth";
|
||||
|
||||
export interface ITag {
|
||||
ID: number;
|
||||
|
@ -16,10 +18,11 @@ export interface ITag {
|
|||
export type IIcon = string;
|
||||
|
||||
export type ValueOf<T> = T[keyof T];
|
||||
export type ContextValue<T> = T extends Context<infer U> ? U : never;
|
||||
|
||||
export type UUID = string;
|
||||
|
||||
export type IUploadType = 'image' | 'text' | 'audio' | 'video' | 'other';
|
||||
export type IUploadType = "image" | "text" | "audio" | "video" | "other";
|
||||
|
||||
export interface IFile {
|
||||
id: number;
|
||||
|
@ -52,17 +55,21 @@ export interface IFile {
|
|||
}
|
||||
|
||||
export interface IBlockText {
|
||||
type: 'text';
|
||||
type: "text";
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface IBlockEmbed {
|
||||
type: 'video';
|
||||
type: "video";
|
||||
url: string;
|
||||
}
|
||||
|
||||
export type IBlock = IBlockText | IBlockEmbed;
|
||||
export type FlowDisplayVariant = 'single' | 'vertical' | 'horizontal' | 'quadro';
|
||||
export type FlowDisplayVariant =
|
||||
| "single"
|
||||
| "vertical"
|
||||
| "horizontal"
|
||||
| "quadro";
|
||||
export interface FlowDisplay {
|
||||
display: FlowDisplayVariant;
|
||||
show_description: boolean;
|
||||
|
@ -102,7 +109,7 @@ export interface INode {
|
|||
|
||||
export type IFlowNode = Pick<
|
||||
INode,
|
||||
'id' | 'flow' | 'description' | 'title' | 'thumbnail' | 'created_at'
|
||||
"id" | "flow" | "description" | "title" | "thumbnail" | "created_at"
|
||||
>;
|
||||
|
||||
export interface IComment {
|
||||
|
@ -116,7 +123,7 @@ export interface IComment {
|
|||
deleted_at?: string;
|
||||
}
|
||||
|
||||
export type IMessage = Omit<IComment, 'user' | 'node'> & {
|
||||
export type IMessage = Omit<IComment, "user" | "node"> & {
|
||||
from: IUser;
|
||||
to: IUser;
|
||||
};
|
||||
|
@ -125,7 +132,7 @@ export interface ICommentGroup {
|
|||
user: IUser;
|
||||
comments: IComment[];
|
||||
distancesInDays: number[];
|
||||
ids: IComment['id'][];
|
||||
ids: IComment["id"][];
|
||||
hasNew: boolean;
|
||||
}
|
||||
|
||||
|
@ -133,19 +140,19 @@ export type IUploadProgressHandler = (progress: ProgressEvent) => void;
|
|||
export type IError = ValueOf<typeof ERRORS>;
|
||||
|
||||
export const NOTIFICATION_TYPES = {
|
||||
message: 'message',
|
||||
comment: 'comment',
|
||||
node: 'node',
|
||||
message: "message",
|
||||
comment: "comment",
|
||||
node: "node",
|
||||
};
|
||||
|
||||
export type IMessageNotification = {
|
||||
type: typeof NOTIFICATION_TYPES['message'];
|
||||
type: typeof NOTIFICATION_TYPES["message"];
|
||||
content: Partial<IMessage>;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
export type ICommentNotification = {
|
||||
type: typeof NOTIFICATION_TYPES['comment'];
|
||||
type: typeof NOTIFICATION_TYPES["comment"];
|
||||
content: Partial<IComment>;
|
||||
created_at: string;
|
||||
};
|
||||
|
|
19
src/utils/providers/NoteProvider.tsx
Normal file
19
src/utils/providers/NoteProvider.tsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { createContext, FC, useContext } from "react";
|
||||
|
||||
import { useNotes } from "~/hooks/notes/useNotes";
|
||||
|
||||
const NoteContext = createContext<ReturnType<typeof useNotes>>({
|
||||
notes: [],
|
||||
hasMore: false,
|
||||
loadMore: async () => Promise.resolve(undefined),
|
||||
isLoading: false,
|
||||
submit: () => Promise.resolve(),
|
||||
});
|
||||
|
||||
export const NoteProvider: FC = ({ children }) => {
|
||||
const notes = useNotes("");
|
||||
|
||||
return <NoteContext.Provider value={notes}>{children}</NoteContext.Provider>;
|
||||
};
|
||||
|
||||
export const useNotesContext = () => useContext(NoteContext);
|
Loading…
Add table
Add a link
Reference in a new issue