mirror of
https://github.com/ershisan99/flashcards-example-project.git
synced 2025-12-16 20:59:27 +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 { ControlledCheckbox, ControlledTextField, Dialog, DialogProps } from '@/components'
|
||||
import { Button, ControlledCheckbox, ControlledTextField, Dialog, DialogProps } from '@/components'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { z } from 'zod'
|
||||
|
||||
@@ -13,33 +14,83 @@ const newDeckSchema = z.object({
|
||||
|
||||
type FormValues = z.infer<typeof newDeckSchema>
|
||||
|
||||
type Props = Pick<DialogProps, 'onCancel' | 'onOpenChange' | 'open'> & {
|
||||
defaultValues?: FormValues
|
||||
onConfirm: (data: FormValues) => void
|
||||
}
|
||||
type Props = {
|
||||
defaultValues?: { cover?: null | string } & FormValues
|
||||
onConfirm: (data: { cover?: File | null } & FormValues) => void
|
||||
} & Pick<DialogProps, 'onCancel' | 'onOpenChange' | 'open'>
|
||||
|
||||
export const DeckDialog = ({
|
||||
defaultValues = { isPrivate: false, name: '' },
|
||||
onCancel,
|
||||
onConfirm,
|
||||
...dialogProps
|
||||
}: 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>({
|
||||
defaultValues,
|
||||
resolver: zodResolver(newDeckSchema),
|
||||
})
|
||||
const onSubmit = handleSubmit(data => {
|
||||
onConfirm(data)
|
||||
onConfirm({ ...data, cover })
|
||||
dialogProps.onOpenChange?.(false)
|
||||
reset()
|
||||
})
|
||||
|
||||
const handleCancel = () => {
|
||||
reset()
|
||||
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 (
|
||||
<Dialog {...dialogProps} onCancel={handleCancel} onConfirm={onSubmit} title={'Create new deck'}>
|
||||
<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'} />
|
||||
<ControlledCheckbox
|
||||
control={control}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import { Edit2Outline, PlayCircleOutline, TrashOutline } from '@/assets'
|
||||
import Logo from '@/assets/icons/person-outline'
|
||||
import {
|
||||
Button,
|
||||
Column,
|
||||
@@ -18,6 +19,10 @@ import { formatDate } from '@/utils'
|
||||
import s from './decks-table.module.scss'
|
||||
|
||||
const columns: Column[] = [
|
||||
{
|
||||
key: 'cover',
|
||||
title: 'Cover',
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
title: 'Name',
|
||||
@@ -66,6 +71,10 @@ export const DecksTable = ({
|
||||
<TableBody>
|
||||
{decks?.map(deck => (
|
||||
<TableRow key={deck.id}>
|
||||
<TableCell>
|
||||
{deck.cover && <img alt={'cover'} src={deck.cover} width={'30px'} />}
|
||||
{!deck.cover && <Logo />}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography as={Link} to={`/decks/${deck.id}`} variant={'body2'}>
|
||||
{deck.name}
|
||||
|
||||
@@ -47,6 +47,7 @@ export const DecksPage = () => {
|
||||
const { currentData: decksCurrentData, data: decksData } = useGetDecksQuery({
|
||||
authorId,
|
||||
currentPage,
|
||||
itemsPerPage: 3,
|
||||
maxCardsCount,
|
||||
minCardsCount,
|
||||
name: search,
|
||||
|
||||
@@ -14,11 +14,26 @@ const decksService = flashcardsApi.injectEndpoints({
|
||||
endpoints: builder => ({
|
||||
createDeck: builder.mutation<DeckResponse, CreateDeckArgs>({
|
||||
invalidatesTags: ['Decks'],
|
||||
query: body => ({
|
||||
body,
|
||||
method: 'POST',
|
||||
url: `v1/decks`,
|
||||
}),
|
||||
query: body => {
|
||||
const { cover, isPrivate, name } = body
|
||||
|
||||
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 }>({
|
||||
invalidatesTags: ['Decks'],
|
||||
@@ -44,11 +59,28 @@ const decksService = flashcardsApi.injectEndpoints({
|
||||
}),
|
||||
updateDeck: builder.mutation<DeckResponse, UpdateDeckArgs>({
|
||||
invalidatesTags: ['Decks'],
|
||||
query: ({ id, ...body }) => ({
|
||||
body,
|
||||
method: 'PATCH',
|
||||
url: `v1/decks/${id}`,
|
||||
}),
|
||||
query: ({ cover, id, isPrivate, name }) => {
|
||||
const formData = new FormData()
|
||||
|
||||
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 = {
|
||||
cover?: string
|
||||
cover?: File | null
|
||||
isPrivate?: boolean
|
||||
name: string
|
||||
}
|
||||
|
||||
export type UpdateDeckArgs = Partial<CreateDeckArgs> & { id: Deck['id'] }
|
||||
export type UpdateDeckArgs = { id: Deck['id'] } & Partial<CreateDeckArgs>
|
||||
|
||||
export type Tab = 'all' | 'my'
|
||||
|
||||
@@ -55,8 +55,6 @@ export const baseQueryWithReauth: BaseQueryFn<
|
||||
extraOptions
|
||||
)) as any
|
||||
|
||||
console.log('refreshResult', refreshResult)
|
||||
|
||||
if (refreshResult.data) {
|
||||
localStorage.setItem('accessToken', refreshResult.data.accessToken)
|
||||
localStorage.setItem('refreshToken', refreshResult.data.refreshToken)
|
||||
|
||||
Reference in New Issue
Block a user