mirror of
https://github.com/ershisan99/flashcards-example-project.git
synced 2025-12-17 05:09:29 +00:00
3 - file final
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
|
import { ChangeEvent, useEffect, useState } from 'react'
|
||||||
import { useForm } from 'react-hook-form'
|
import { useForm } from 'react-hook-form'
|
||||||
|
|
||||||
import { ControlledCheckbox, ControlledTextField, Dialog, DialogProps } from '@/components'
|
import { Button, ControlledCheckbox, ControlledTextField, Dialog, DialogProps } from '@/components'
|
||||||
import { zodResolver } from '@hookform/resolvers/zod'
|
import { zodResolver } from '@hookform/resolvers/zod'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
@@ -13,33 +14,83 @@ const newDeckSchema = z.object({
|
|||||||
|
|
||||||
type FormValues = z.infer<typeof newDeckSchema>
|
type FormValues = z.infer<typeof newDeckSchema>
|
||||||
|
|
||||||
type Props = Pick<DialogProps, 'onCancel' | 'onOpenChange' | 'open'> & {
|
type Props = {
|
||||||
defaultValues?: FormValues
|
defaultValues?: { cover?: null | string } & FormValues
|
||||||
onConfirm: (data: FormValues) => void
|
onConfirm: (data: { cover?: File | null } & FormValues) => void
|
||||||
}
|
} & Pick<DialogProps, 'onCancel' | 'onOpenChange' | 'open'>
|
||||||
|
|
||||||
export const DeckDialog = ({
|
export const DeckDialog = ({
|
||||||
defaultValues = { isPrivate: false, name: '' },
|
defaultValues = { isPrivate: false, name: '' },
|
||||||
onCancel,
|
onCancel,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
...dialogProps
|
...dialogProps
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const [cover, setCover] = useState<File | null>(null)
|
||||||
|
const [preview, setPreview] = useState<null | string>('')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (defaultValues?.cover) {
|
||||||
|
setPreview(defaultValues?.cover)
|
||||||
|
}
|
||||||
|
}, [defaultValues?.cover])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (cover) {
|
||||||
|
// создать ссылку на файл
|
||||||
|
const newPreview = URL.createObjectURL(cover)
|
||||||
|
|
||||||
|
// зачищаем старое превью чтобы не хранилось в памяти
|
||||||
|
if (preview) {
|
||||||
|
URL.revokeObjectURL(preview)
|
||||||
|
}
|
||||||
|
setPreview(newPreview)
|
||||||
|
|
||||||
|
// зачищаем новое превью чтобы не хранилось в памяти
|
||||||
|
return () => URL.revokeObjectURL(newPreview)
|
||||||
|
}
|
||||||
|
}, [cover])
|
||||||
|
|
||||||
const { control, handleSubmit, reset } = useForm<FormValues>({
|
const { control, handleSubmit, reset } = useForm<FormValues>({
|
||||||
defaultValues,
|
defaultValues,
|
||||||
resolver: zodResolver(newDeckSchema),
|
resolver: zodResolver(newDeckSchema),
|
||||||
})
|
})
|
||||||
const onSubmit = handleSubmit(data => {
|
const onSubmit = handleSubmit(data => {
|
||||||
onConfirm(data)
|
onConfirm({ ...data, cover })
|
||||||
dialogProps.onOpenChange?.(false)
|
dialogProps.onOpenChange?.(false)
|
||||||
reset()
|
reset()
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
reset()
|
reset()
|
||||||
onCancel?.()
|
onCancel?.()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uploadHandler = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (e.target.files && e.target.files.length) {
|
||||||
|
const file = e.target.files[0]
|
||||||
|
|
||||||
|
setCover(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeCoverHandler = () => {
|
||||||
|
setCover(null)
|
||||||
|
setPreview(null)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog {...dialogProps} onCancel={handleCancel} onConfirm={onSubmit} title={'Create new deck'}>
|
<Dialog {...dialogProps} onCancel={handleCancel} onConfirm={onSubmit} title={'Create new deck'}>
|
||||||
<form className={s.content} onSubmit={onSubmit}>
|
<form className={s.content} onSubmit={onSubmit}>
|
||||||
|
{preview && <img alt={'cover'} src={preview} width={'50px'} />}
|
||||||
|
|
||||||
|
<input accept={'image/*'} onChange={uploadHandler} type={'file'} />
|
||||||
|
|
||||||
|
{preview && (
|
||||||
|
<Button onClick={removeCoverHandler} type={'button'}>
|
||||||
|
Remove cover
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
<ControlledTextField control={control} label={'Deck name'} name={'name'} />
|
<ControlledTextField control={control} label={'Deck name'} name={'name'} />
|
||||||
<ControlledCheckbox
|
<ControlledCheckbox
|
||||||
control={control}
|
control={control}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
import { Edit2Outline, PlayCircleOutline, TrashOutline } from '@/assets'
|
import { Edit2Outline, PlayCircleOutline, TrashOutline } from '@/assets'
|
||||||
|
import Logo from '@/assets/icons/person-outline'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Column,
|
Column,
|
||||||
@@ -18,6 +19,10 @@ import { formatDate } from '@/utils'
|
|||||||
import s from './decks-table.module.scss'
|
import s from './decks-table.module.scss'
|
||||||
|
|
||||||
const columns: Column[] = [
|
const columns: Column[] = [
|
||||||
|
{
|
||||||
|
key: 'cover',
|
||||||
|
title: 'Cover',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'name',
|
key: 'name',
|
||||||
title: 'Name',
|
title: 'Name',
|
||||||
@@ -66,6 +71,10 @@ export const DecksTable = ({
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
{decks?.map(deck => (
|
{decks?.map(deck => (
|
||||||
<TableRow key={deck.id}>
|
<TableRow key={deck.id}>
|
||||||
|
<TableCell>
|
||||||
|
{deck.cover && <img alt={'cover'} src={deck.cover} width={'30px'} />}
|
||||||
|
{!deck.cover && <Logo />}
|
||||||
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Typography as={Link} to={`/decks/${deck.id}`} variant={'body2'}>
|
<Typography as={Link} to={`/decks/${deck.id}`} variant={'body2'}>
|
||||||
{deck.name}
|
{deck.name}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export const DecksPage = () => {
|
|||||||
const { currentData: decksCurrentData, data: decksData } = useGetDecksQuery({
|
const { currentData: decksCurrentData, data: decksData } = useGetDecksQuery({
|
||||||
authorId,
|
authorId,
|
||||||
currentPage,
|
currentPage,
|
||||||
|
itemsPerPage: 3,
|
||||||
maxCardsCount,
|
maxCardsCount,
|
||||||
minCardsCount,
|
minCardsCount,
|
||||||
name: search,
|
name: search,
|
||||||
|
|||||||
@@ -14,11 +14,26 @@ const decksService = flashcardsApi.injectEndpoints({
|
|||||||
endpoints: builder => ({
|
endpoints: builder => ({
|
||||||
createDeck: builder.mutation<DeckResponse, CreateDeckArgs>({
|
createDeck: builder.mutation<DeckResponse, CreateDeckArgs>({
|
||||||
invalidatesTags: ['Decks'],
|
invalidatesTags: ['Decks'],
|
||||||
query: body => ({
|
query: body => {
|
||||||
body,
|
const { cover, isPrivate, name } = body
|
||||||
method: 'POST',
|
|
||||||
url: `v1/decks`,
|
const formData = new FormData()
|
||||||
}),
|
|
||||||
|
formData.append('name', name)
|
||||||
|
|
||||||
|
if (isPrivate) {
|
||||||
|
formData.append('isPrivate', isPrivate.toString())
|
||||||
|
}
|
||||||
|
if (cover) {
|
||||||
|
formData.append('cover', cover)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
body: formData,
|
||||||
|
method: 'POST',
|
||||||
|
url: `v1/decks`,
|
||||||
|
}
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
deleteDeck: builder.mutation<void, { id: string }>({
|
deleteDeck: builder.mutation<void, { id: string }>({
|
||||||
invalidatesTags: ['Decks'],
|
invalidatesTags: ['Decks'],
|
||||||
@@ -44,11 +59,28 @@ const decksService = flashcardsApi.injectEndpoints({
|
|||||||
}),
|
}),
|
||||||
updateDeck: builder.mutation<DeckResponse, UpdateDeckArgs>({
|
updateDeck: builder.mutation<DeckResponse, UpdateDeckArgs>({
|
||||||
invalidatesTags: ['Decks'],
|
invalidatesTags: ['Decks'],
|
||||||
query: ({ id, ...body }) => ({
|
query: ({ cover, id, isPrivate, name }) => {
|
||||||
body,
|
const formData = new FormData()
|
||||||
method: 'PATCH',
|
|
||||||
url: `v1/decks/${id}`,
|
if (name) {
|
||||||
}),
|
formData.append('name', name)
|
||||||
|
}
|
||||||
|
if (isPrivate) {
|
||||||
|
formData.append('isPrivate', isPrivate.toString())
|
||||||
|
}
|
||||||
|
if (cover) {
|
||||||
|
formData.append('cover', cover)
|
||||||
|
} else if (cover === null) {
|
||||||
|
// небходимо для зануления картинки
|
||||||
|
formData.append('cover', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
body: formData,
|
||||||
|
method: 'PATCH',
|
||||||
|
url: `v1/decks/${id}`,
|
||||||
|
}
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -64,11 +64,11 @@ export type GetDecksArgs = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type CreateDeckArgs = {
|
export type CreateDeckArgs = {
|
||||||
cover?: string
|
cover?: File | null
|
||||||
isPrivate?: boolean
|
isPrivate?: boolean
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UpdateDeckArgs = Partial<CreateDeckArgs> & { id: Deck['id'] }
|
export type UpdateDeckArgs = { id: Deck['id'] } & Partial<CreateDeckArgs>
|
||||||
|
|
||||||
export type Tab = 'all' | 'my'
|
export type Tab = 'all' | 'my'
|
||||||
|
|||||||
@@ -55,8 +55,6 @@ export const baseQueryWithReauth: BaseQueryFn<
|
|||||||
extraOptions
|
extraOptions
|
||||||
)) as any
|
)) as any
|
||||||
|
|
||||||
console.log('refreshResult', refreshResult)
|
|
||||||
|
|
||||||
if (refreshResult.data) {
|
if (refreshResult.data) {
|
||||||
localStorage.setItem('accessToken', refreshResult.data.accessToken)
|
localStorage.setItem('accessToken', refreshResult.data.accessToken)
|
||||||
localStorage.setItem('refreshToken', refreshResult.data.refreshToken)
|
localStorage.setItem('refreshToken', refreshResult.data.refreshToken)
|
||||||
|
|||||||
Reference in New Issue
Block a user