1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-25 04:46:40 +07:00

Отрефакторил бэк, исправил ошибки (#138)

* fixed paths to match refactored backend

* fixed some paths according to new backend

* fixed auth urls for new endpoints

* fixed urls

* fixed error handling

* fixes

* fixed error handling on user form

* fixed error handling on oauth

* using fallback: true on node pages

* type button for comment attach buttons

* fixed return types of social delete

* changed the way we upload user avatars
This commit is contained in:
muerwre 2022-09-16 14:53:52 +07:00 committed by GitHub
parent 1745cc636d
commit 080d59858c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 544 additions and 420 deletions

View file

@ -1,7 +1,7 @@
import React, { FC } from 'react';
import { Avatar } from '~/components/common/Avatar';
import { MenuButton } from '~/components/menu';
import { MenuButton } from '~/components/menu/MenuButton';
import { ProfileQuickInfo } from '~/containers/profile/ProfileQuickInfo';
import { IUser } from '~/types/auth';
import { path } from '~/utils/ramda';
@ -16,7 +16,11 @@ const CommentAvatar: FC<Props> = ({ user, className }) => {
<MenuButton
position="auto"
icon={
<Avatar url={path(['photo', 'url'], user)} username={user.username} className={className} />
<Avatar
url={path(['photo', 'url'], user)}
username={user.username}
className={className}
/>
}
>
<ProfileQuickInfo user={user} />

View file

@ -10,26 +10,32 @@ interface IProps {
const CommentFormAttachButtons: FC<IProps> = ({ onUpload }) => {
const onInputChange = useCallback(
event => {
(event) => {
event.preventDefault();
const files = Array.from(event.target?.files as File[]).filter((file: File) =>
COMMENT_FILE_TYPES.includes(file.type)
const files = Array.from(event.target?.files as File[]).filter(
(file: File) => COMMENT_FILE_TYPES.includes(file.type),
);
if (!files || !files.length) return;
onUpload(files);
},
[onUpload]
[onUpload],
);
return (
<ButtonGroup>
<Button iconLeft="photo" size="small" color="gray" iconOnly>
<Button iconLeft="photo" size="small" color="gray" iconOnly type="button">
<input type="file" onInput={onInputChange} multiple accept="image/*" />
</Button>
<Button iconRight="audio" size="small" color="gray" iconOnly>
<Button
iconRight="audio"
size="small"
color="gray"
iconOnly
type="button"
>
<input type="file" onInput={onInputChange} multiple accept="audio/*" />
</Button>
</ButtonGroup>

View file

@ -1,6 +1,7 @@
import React, { FC, useCallback } from 'react';
import { SortableAudioGrid, SortableImageGrid } from '~/components/sortable';
import { SortableAudioGrid } from '~/components/sortable/SortableAudioGrid';
import { SortableImageGrid } from '~/components/sortable/SortableImageGrid';
import { COMMENT_FILE_TYPES } from '~/constants/uploads';
import { useFileDropZone } from '~/hooks';
import { IFile } from '~/types';
@ -27,34 +28,36 @@ const CommentFormAttaches: FC = () => {
const onImageMove = useCallback(
(newFiles: IFile[]) => {
setFiles([...filesAudios, ...newFiles.filter(it => it)]);
setFiles([...filesAudios, ...newFiles.filter((it) => it)]);
},
[setFiles, filesImages, filesAudios]
[setFiles, filesImages, filesAudios],
);
const onAudioMove = useCallback(
(newFiles: IFile[]) => {
setFiles([...filesImages, ...newFiles]);
},
[setFiles, filesImages, filesAudios]
[setFiles, filesImages, filesAudios],
);
const onFileDelete = useCallback(
(fileId: IFile['id']) => {
setFiles(files.filter(file => file.id !== fileId));
setFiles(files.filter((file) => file.id !== fileId));
},
[files, setFiles]
[files, setFiles],
);
const onAudioTitleChange = useCallback(
(fileId: IFile['id'], title: string) => {
setFiles(
files.map(file =>
file.id === fileId ? { ...file, metadata: { ...file.metadata, title } } : file
)
files.map((file) =>
file.id === fileId
? { ...file, metadata: { ...file.metadata, title } }
: file,
),
);
},
[files, setFiles]
[files, setFiles],
);
if (!hasAttaches) return null;

View file

@ -1,6 +1,6 @@
import React, { FC, useCallback } from 'react';
import { SortableAudioGrid } from '~/components/sortable';
import { SortableAudioGrid } from '~/components/sortable/SortableAudioGrid';
import { UploadStatus } from '~/store/uploader/UploaderStore';
import { IFile } from '~/types';
@ -15,25 +15,27 @@ const AudioGrid: FC<IProps> = ({ files, setFiles, locked }) => {
(newFiles: IFile[]) => {
setFiles(newFiles);
},
[setFiles, files]
[setFiles, files],
);
const onDrop = useCallback(
(remove_id: IFile['id']) => {
setFiles(files.filter(file => file && file.id !== remove_id));
setFiles(files.filter((file) => file && file.id !== remove_id));
},
[setFiles, files]
[setFiles, files],
);
const onTitleChange = useCallback(
(changeId: IFile['id'], title: string) => {
setFiles(
files.map(file =>
file && file.id === changeId ? { ...file, metadata: { ...file.metadata, title } } : file
)
files.map((file) =>
file && file.id === changeId
? { ...file, metadata: { ...file.metadata, title } }
: file,
),
);
},
[setFiles, files]
[setFiles, files],
);
return (

View file

@ -1,6 +1,6 @@
import React, { FC, useCallback } from 'react';
import { SortableImageGrid } from '~/components/sortable';
import { SortableImageGrid } from '~/components/sortable/SortableImageGrid';
import { useWindowSize } from '~/hooks/dom/useWindowSize';
import { UploadStatus } from '~/store/uploader/UploaderStore';
import { IFile } from '~/types';
@ -16,14 +16,14 @@ const ImageGrid: FC<IProps> = ({ files, setFiles, locked }) => {
const onMove = useCallback(
(newFiles: IFile[]) => {
setFiles(newFiles.filter(it => it));
setFiles(newFiles.filter((it) => it));
},
[setFiles, files],
);
const onDrop = useCallback(
(id: IFile['id']) => {
setFiles(files.filter(file => file && file.id !== id));
setFiles(files.filter((file) => file && file.id !== id));
},
[setFiles, files],
);

View file

@ -1,5 +0,0 @@
export * from './VerticalMenu';
export * from './HorizontalMenu';
export * from './MenuButton';
export * from './MenuItemWithIcon';
export * from './SeparatedMenu';

View file

@ -1,38 +0,0 @@
import React, { FC } from 'react';
import { Filler } from '~/components/containers/Filler';
import { Group } from '~/components/containers/Group';
import styles from './styles.module.scss';
interface IProps {
title: string;
description?: string;
icon: string;
}
const MenuButton: FC<IProps> = ({
title,
icon,
description,
}) => (
<div
className={styles.button}
>
<Group horizontal>
<div className={styles.icon}>
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24">
<path fill="none" d="M0 0h24v24H0V0z" />
<path d="M22 9.24l-7.19-.62L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zM12 15.4l-3.76 2.27 1-4.28-3.32-2.88 4.38-.38L12 6.1l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.4z" />
</svg>
</div>
<Filler>
<div className={styles.title}>{title}</div>
{ description && <div className={styles.description}>{description}</div> }
</Filler>
</Group>
</div>
);
export { MenuButton };

View file

@ -1,29 +0,0 @@
@import 'src/styles/variables';
.button {
fill: white;
height: 48px;
display: flex;
align-items: center;
}
.icon {
flex: 0 0 38px;
align-items: center;
justify-content: center;
display: flex;
}
.title {
font: $font_16_semibold;
text-transform: uppercase;
margin-bottom: 2px;
white-space: nowrap;
flex: 1;
}
.description {
font: $font_12_regular;
color: $gray_75;
white-space: nowrap;
}

View file

@ -3,7 +3,9 @@ import React, { VFC } from 'react';
import Tippy from '@tippyjs/react';
import { Icon } from '~/components/input/Icon';
import { MenuButton, MenuItemWithIcon, SeparatedMenu } from '~/components/menu';
import { MenuButton } from '~/components/menu/MenuButton';
import { MenuItemWithIcon } from '~/components/menu/MenuItemWithIcon';
import { SeparatedMenu } from '~/components/menu/SeparatedMenu';
import { useWindowSize } from '~/hooks/dom/useWindowSize';
import styles from './styles.module.scss';

View file

@ -3,7 +3,7 @@ import React, { memo, VFC } from 'react';
import classNames from 'classnames';
import { Icon } from '~/components/input/Icon';
import { SeparatedMenu } from '~/components/menu';
import { SeparatedMenu } from '~/components/menu/SeparatedMenu';
import { NodeEditMenu } from '~/components/node/NodeEditMenu';
import { Placeholder } from '~/components/placeholders/Placeholder';
import { getPrettyDate } from '~/utils/dom';
@ -35,7 +35,6 @@ interface IProps {
const NodeTitle: VFC<IProps> = memo(
({
id,
title,
username,
createdAt,
@ -69,7 +68,9 @@ const NodeTitle: VFC<IProps> = memo(
{isLoading ? (
<Placeholder width="100px" />
) : (
`~${username.toLocaleLowerCase()}, ${getPrettyDate(createdAt)}`
`~${username.toLocaleLowerCase()}, ${getPrettyDate(
createdAt,
)}`
)}
</aside>
)}
@ -90,7 +91,9 @@ const NodeTitle: VFC<IProps> = memo(
{canLike && (
<div
className={classNames(styles.button, styles.like, { [styles.is_liked]: isLiked })}
className={classNames(styles.button, styles.like, {
[styles.is_liked]: isLiked,
})}
>
{isLiked ? (
<Icon icon="heart_full" size={24} onClick={onLike} />
@ -107,7 +110,7 @@ const NodeTitle: VFC<IProps> = memo(
</div>
</div>
);
}
},
);
export { NodeTitle };

View file

@ -5,7 +5,7 @@ import { rectSortingStrategy, SortableContext } from '@dnd-kit/sortable';
import classNames from 'classnames';
import { DragOverlayItem } from '~/components/sortable/DragOverlayItem';
import { useSortableActions } from '~/hooks/sortable';
import { useSortableActions } from '~/hooks/sortable/useSortableActions';
import { DivProps } from '~/utils/types';
import { SortableItem } from '../SortableItem';
@ -16,7 +16,7 @@ interface SortableGridProps<
ItemRendererProps extends {},
LockedRendererProps extends {},
Item extends {},
Locked extends {}
Locked extends {},
> {
items: Item[];
locked: Locked[];
@ -44,18 +44,18 @@ const SortableGrid = <RIP, RLP, I, L>({
onSortEnd,
size,
}: SortableGridProps<RIP, RLP, I, L>) => {
const { sensors, onDragEnd, onDragStart, draggingItem, ids } = useSortableActions(
items,
getID,
onSortEnd
);
const { sensors, onDragEnd, onDragStart, draggingItem, ids } =
useSortableActions(items, getID, onSortEnd);
const gridStyle = useMemo<DivProps['style']>(
() =>
size
? { gridTemplateColumns: size && `repeat(auto-fill, minmax(${size}px, 1fr))` }
? {
gridTemplateColumns:
size && `repeat(auto-fill, minmax(${size}px, 1fr))`,
}
: undefined,
[size]
[size],
);
return (
@ -67,30 +67,35 @@ const SortableGrid = <RIP, RLP, I, L>({
>
<SortableContext items={ids} strategy={rectSortingStrategy}>
<div className={classNames(styles.grid, className)} style={gridStyle}>
{items.map(item => (
{items.map((item) => (
<SortableItem
key={getID(item)}
id={getID(item)}
className={
draggingItem && getID(item) === getID(draggingItem) ? styles.dragging : undefined
draggingItem && getID(item) === getID(draggingItem)
? styles.dragging
: undefined
}
>
{createElement(renderItem, { ...renderItemProps, item })}
</SortableItem>
))}
{locked.map(item =>
{locked.map((item) =>
createElement(renderLocked, {
...renderLockedProps,
locked: item,
key: getLockedID(item),
})
}),
)}
<DragOverlay>
{draggingItem ? (
<DragOverlayItem>
{createElement(renderItem, { ...renderItemProps, item: draggingItem })}
{createElement(renderItem, {
...renderItemProps,
item: draggingItem,
})}
</DragOverlayItem>
) : null}
</DragOverlay>

View file

@ -1,12 +1,15 @@
import React, { createElement, FC } from 'react';
import { closestCenter, DndContext, DragOverlay } from '@dnd-kit/core';
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
import {
SortableContext,
verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import classNames from 'classnames';
import { DragOverlayItem } from '~/components/sortable/DragOverlayItem';
import { SortableItem } from '~/components/sortable/SortableItem';
import { useSortableActions } from '~/hooks/sortable';
import { useSortableActions } from '~/hooks/sortable/useSortableActions';
import styles from './styles.module.scss';
@ -14,7 +17,7 @@ interface SortableListProps<
RenderItemProps extends {},
RenderLockedProps extends {},
Item extends {},
Locked extends {}
Locked extends {},
> {
items: Item[];
locked: Locked[];
@ -40,11 +43,8 @@ const SortableList = <RIP, RLP, I, L>({
renderLockedProps,
onSortEnd,
}: SortableListProps<RIP, RLP, I, L>) => {
const { sensors, onDragEnd, onDragStart, draggingItem, ids } = useSortableActions(
items,
getID,
onSortEnd
);
const { sensors, onDragEnd, onDragStart, draggingItem, ids } =
useSortableActions(items, getID, onSortEnd);
return (
<DndContext
@ -55,30 +55,39 @@ const SortableList = <RIP, RLP, I, L>({
>
<SortableContext items={ids} strategy={verticalListSortingStrategy}>
<div className={classNames(styles.grid, className)}>
{items.map(item => (
{items.map((item) => (
<SortableItem
key={getID(item)}
id={getID(item)}
className={
draggingItem && getID(item) === getID(draggingItem) ? styles.dragging : undefined
draggingItem && getID(item) === getID(draggingItem)
? styles.dragging
: undefined
}
>
{createElement(renderItem, { ...renderItemProps, item, key: getID(item) })}
{createElement(renderItem, {
...renderItemProps,
item,
key: getID(item),
})}
</SortableItem>
))}
{locked.map(item =>
{locked.map((item) =>
createElement(renderLocked, {
...renderLockedProps,
locked: item,
key: getLockedID(item),
})
}),
)}
<DragOverlay>
{draggingItem ? (
<DragOverlayItem>
{createElement(renderItem, { ...renderItemProps, item: draggingItem })}
{createElement(renderItem, {
...renderItemProps,
item: draggingItem,
})}
</DragOverlayItem>
) : null}
</DragOverlay>

View file

@ -1,2 +0,0 @@
export * from './SortableImageGrid';
export * from './SortableAudioGrid';