diff --git a/src/apis/ticket/mutations.ts b/src/apis/ticket/mutations.ts index 754b5dd..fb1de31 100644 --- a/src/apis/ticket/mutations.ts +++ b/src/apis/ticket/mutations.ts @@ -28,4 +28,14 @@ export const ticketMutations = { }, }) }, + + deleteTicket: () => { + return useMutation({ + mutationFn: ({ ticketId }: { ticketId: number }) => + api().deletePost(ticketId), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ticketKeys.ticketList.all() }) + }, + }) + }, } diff --git a/src/app/(auth)/add-ticket/schema.ts b/src/app/(auth)/add-ticket/schema.ts index e35ce44..a17522a 100644 --- a/src/app/(auth)/add-ticket/schema.ts +++ b/src/app/(auth)/add-ticket/schema.ts @@ -7,10 +7,10 @@ export const schema = z.object({ /** * 관람좌석 */ - floor: z.string().min(1), - zone: z.string().min(1), - col: z.string().min(1), - seatNumber: z.string().min(1), + floor: z.string().optional(), + zone: z.string().optional(), + col: z.string().optional(), + seatNumber: z.string().optional(), /** * 관람 일정 diff --git a/src/app/(auth)/add-ticket/step4/index.tsx b/src/app/(auth)/add-ticket/step4/index.tsx index 8b41135..c0bd203 100644 --- a/src/app/(auth)/add-ticket/step4/index.tsx +++ b/src/app/(auth)/add-ticket/step4/index.tsx @@ -1,9 +1,3 @@ -import { zodResolver } from '@hookform/resolvers/zod' -import * as ImagePicker from 'expo-image-picker' -import { useLocalSearchParams, useRouter } from 'expo-router' -import { overlay } from 'overlay-kit' -import { useForm } from 'react-hook-form' -import { Image } from 'react-native' import { ticketMutations } from '@/apis/ticket/mutations' import { BottomSheet } from '@/components/BottomSheet' import { Button } from '@/components/Button' @@ -17,6 +11,12 @@ import { toast } from '@/components/Toaster' import { useUser } from '@/providers/user.provider' import { cn } from '@/utils/cn' import { uploadImage } from '@/utils/upload-image' +import { zodResolver } from '@hookform/resolvers/zod' +import * as ImagePicker from 'expo-image-picker' +import { useLocalSearchParams, useRouter } from 'expo-router' +import { overlay } from 'overlay-kit' +import { useForm } from 'react-hook-form' +import { Image } from 'react-native' import AddTicketHeader from '../components/AddTicketHeader' import { type FormType, schema } from '../schema' @@ -105,7 +105,9 @@ export default function Step4() { col: data.col, number: data.seatNumber, actor_ids: data.actors.map((actor) => actor.id), - ticket_image_url: `/${data.dynamicTicketImageUrl}`, + ticket_image_url: data.noTicketUpload + ? undefined + : `/${data.dynamicTicketImageUrl}`, }) toast.show('티켓을 등록했어요.') diff --git a/src/app/(auth)/ticket-detail/[id]/index.tsx b/src/app/(auth)/ticket-detail/[id]/index.tsx index 9714fcc..84445bf 100644 --- a/src/app/(auth)/ticket-detail/[id]/index.tsx +++ b/src/app/(auth)/ticket-detail/[id]/index.tsx @@ -1,14 +1,3 @@ -import { zodResolver } from '@hookform/resolvers/zod' -import { useQuery, useSuspenseQuery } from '@tanstack/react-query' -import dayjs from 'dayjs' -import * as ImagePicker from 'expo-image-picker' -import { useLocalSearchParams } from 'expo-router' -import { overlay } from 'overlay-kit' -import { useEffect, useState } from 'react' -import { Controller, useForm } from 'react-hook-form' -import { Image, TextInput } from 'react-native' -import { FlatList } from 'react-native-gesture-handler' -import { imageMutations } from '@/apis/image/mutations' import { ticketMutations } from '@/apis/ticket/mutations' import { ticketQueries } from '@/apis/ticket/queries' import { BottomSheet } from '@/components/BottomSheet' @@ -16,6 +5,7 @@ import { Calendar } from '@/components/Calendar' import { Icon } from '@/components/common/icons/Icon' import { Col, Flex, Row } from '@/components/common/ui/Flex' import { Screen } from '@/components/common/ui/Screen' +import { Spacing } from '@/components/common/ui/Spacing' import { Text } from '@/components/common/ui/Text' import { Dialog } from '@/components/Dialog' import { Dropdown } from '@/components/Dropdown' @@ -27,16 +17,28 @@ import { toast } from '@/components/Toaster' import { useDebounce } from '@/hooks/useDebounce' import { cn } from '@/utils/cn' import { uploadImage } from '@/utils/upload-image' +import { zodResolver } from '@hookform/resolvers/zod' +import { useQuery, useSuspenseQuery } from '@tanstack/react-query' +import dayjs from 'dayjs' +import * as ImagePicker from 'expo-image-picker' +import { router, useLocalSearchParams } from 'expo-router' +import { overlay } from 'overlay-kit' +import { useRef, useState } from 'react' +import { Controller, useForm } from 'react-hook-form' +import { Image, type LayoutChangeEvent, TextInput } from 'react-native' +import { FlatList, type ScrollView } from 'react-native-gesture-handler' import { type FormType, schema } from '../../add-ticket/schema' export default function TicketDetailScreen() { const { id } = useLocalSearchParams() - + const scrollRef = useRef(null) + const actorSectionY = useRef(0) const { mutate: updateTicket } = ticketMutations.updateTicket() const [actorKeyword, setActorKeyword] = useState('') const { data } = useSuspenseQuery(ticketQueries.getTicketDetail(Number(id))) + const { mutate: deleteTicket } = ticketMutations.deleteTicket() const { data: actors } = useQuery( ticketQueries.searchActors({ @@ -44,8 +46,6 @@ export default function TicketDetailScreen() { }), ) - const { mutate: getTicketImage } = imageMutations.getViewImage() - const form = useForm({ resolver: zodResolver(schema), defaultValues: { @@ -73,19 +73,6 @@ export default function TicketDetailScreen() { const [isEdit, setIsEdit] = useState(false) - useEffect(() => { - if (!data?.ticket_image_url) { - form.setValue('ticketImageUrl', undefined) - return - } - const match = data?.ticket_image_url?.match(/dynamic\/[\w-]+\.\w+/)?.[0] - - getTicketImage( - { file_path: match }, - { onSuccess: (data) => form.setValue('ticketImageUrl', data?.url ?? '') }, - ) - }, [data?.ticket_image_url, getTicketImage]) - const onDelete = async () => { overlay.open(({ isOpen, close }) => ( { + close() + deleteTicket( + { + ticketId: Number(id), + }, + { + onSuccess: () => { + toast.show('관람 내역을 삭제했어요.') + router.back() + }, + }, + ) + }} /> )) } @@ -118,9 +119,9 @@ export default function TicketDetailScreen() { onTicketImageUpload() }} > - + - 갤러리에서 사진 변경하기 + 갤러리에서 변경하기 {type === 'EDIT' && ( @@ -132,7 +133,7 @@ export default function TicketDetailScreen() { onClose() }} > - + 사진 삭제하기 @@ -185,6 +186,7 @@ export default function TicketDetailScreen() { {isEdit ? ( @@ -393,10 +395,24 @@ export default function TicketDetailScreen() { 티켓 사진 - {isEdit ? ( + {form.watch('ticketImageUrl') && ( + + )} + {!isEdit && !form.watch('ticketImageUrl') && ( + {`티켓 사진을 업로드하고\n후기를 작성해보세요!`} + )} + {isEdit && ( { if (form.watch('ticketImageUrl')) { handleTicketSheet('EDIT') @@ -410,21 +426,15 @@ export default function TicketDetailScreen() { {form.watch('ticketImageUrl') ? '사진 변경' : '사진 추가'} - ) : form.watch('ticketImageUrl') ? ( - - ) : ( - {`티켓 사진을 업로드하고\n후기를 작성해보세요!`} )} - + { + actorSectionY.current = event.nativeEvent.layout.y + }} + > 배우 @@ -436,6 +446,12 @@ export default function TicketDetailScreen() { onDelete={() => { setActorKeyword('') }} + onPress={() => { + scrollRef.current?.scrollTo({ + y: actorSectionY.current, + animated: true, + }) + }} placeholder="추가할 배우 검색하기" /> )} @@ -533,6 +549,7 @@ export default function TicketDetailScreen() { )} + ) } diff --git a/src/app/login/index.tsx b/src/app/login/index.tsx index ca778f3..8fb91fd 100644 --- a/src/app/login/index.tsx +++ b/src/app/login/index.tsx @@ -1,8 +1,3 @@ -import type { UserSignupReqProvider } from 'api' -import { router } from 'expo-router' -import * as WebBrowser from 'expo-web-browser' -import { Image, View } from 'react-native' -import { useSafeAreaInsets } from 'react-native-safe-area-context' import { userQueries } from '@/apis/user/queries' import { Col } from '@/components/common/ui/Flex' import { Text, type TextProps } from '@/components/common/ui/Text' @@ -11,6 +6,11 @@ import { TERMS_AND_PRIVACY } from '@/constants/login' import { queryClient } from '@/lib/query-client' import { saveToken } from '@/lib/storage' import { useAuth } from '@/providers/user.provider' +import type { UserSignupReqProvider } from 'api' +import { router } from 'expo-router' +import * as WebBrowser from 'expo-web-browser' +import { Image, View } from 'react-native' +import { useSafeAreaInsets } from 'react-native-safe-area-context' import LoginButton from './components/LoginButton' import LogoText from './components/LogoText' @@ -53,11 +53,11 @@ export default function Index() { if (token) { await saveToken('accessToken', token) - await sync() + sync() if (isInitialized === 'true') { router.replace('/') } else { - router.replace('/login/profile-setup') + router.push('/login/profile-setup') } } } diff --git a/src/components/common/icons/svgs/Checkbox.tsx b/src/components/common/icons/svgs/Checkbox.tsx index 6d91490..da31165 100644 --- a/src/components/common/icons/svgs/Checkbox.tsx +++ b/src/components/common/icons/svgs/Checkbox.tsx @@ -12,7 +12,7 @@ const Checkbox = (props: SvgProps, ref: Ref) => ( {...props} > ) => ( {...props} > ) => ( ) => ( {...props} > }>) { const insets = useSafeAreaInsets() @@ -32,7 +34,10 @@ export function Screen({ > {header} {scrollable ? ( - + {children} ) : (