github.com/oinume/lekcije@v0.0.0-20231017100347-5b4c5eb6ab24/frontend/src/pages/SettingPage.tsx (about) 1 import React, {useState} from 'react'; 2 import {useQueryClient} from '@tanstack/react-query'; 3 import {toast} from 'react-toastify'; 4 import {Loader} from '../components/Loader'; 5 import {EmailForm} from '../components/setting/EmailForm'; 6 import {NotificationTimeSpanForm} from '../components/setting/NotificationTimeSpanForm'; 7 import {PageTitle} from '../components/PageTitle'; 8 import {NotificationTimeSpanModel} from '../models/NotificatonTimeSpan'; 9 import type { 10 GetViewerWithNotificationTimeSpansQuery, NotificationTimeSpan, 11 } from '../graphql/generated'; 12 import { 13 useGetViewerWithNotificationTimeSpansQuery, useUpdateNotificationTimeSpansMutation, useUpdateViewerMutation, 14 } from '../graphql/generated'; 15 import type {GraphQLError} from '../http/graphql'; 16 import {createGraphQLClient, toMessage} from '../http/graphql'; 17 import {ToastContainer} from '../components/ToastContainer'; 18 19 export const SettingPage: React.FC = () => { 20 const [emailState, setEmailState] = useState<string | undefined>(undefined); 21 const [notificationTimeSpansState, setNotificationTimeSpansState] = useState<NotificationTimeSpanModel[] | undefined>( 22 undefined, 23 ); 24 25 // https://tanstack.com/query/v4/docs/guides/mutations 26 const queryClient = useQueryClient(); 27 const graphqlClient = createGraphQLClient(); 28 const updateViewerMutation = useUpdateViewerMutation<GraphQLError>(graphqlClient, { 29 async onSuccess() { 30 await queryClient.invalidateQueries(useGetViewerWithNotificationTimeSpansQuery.getKey()); 31 toast.success('メールアドレスを更新しました!'); 32 }, 33 async onError(error) { 34 console.error(error.response); 35 toast.error(toMessage(error)); 36 }, 37 }); 38 39 const updateNotificationTimeSpansMutation = useUpdateNotificationTimeSpansMutation<GraphQLError>(graphqlClient, { 40 async onSuccess() { 41 await queryClient.invalidateQueries(useGetViewerWithNotificationTimeSpansQuery.getKey()); 42 toast.success('レッスン希望時間帯を更新しました!'); 43 }, 44 async onError(error) { 45 console.error(error.response); 46 toast.error(toMessage(error)); 47 }, 48 }); 49 50 const queryResult = useGetViewerWithNotificationTimeSpansQuery<GetViewerWithNotificationTimeSpansQuery, GraphQLError>(graphqlClient, undefined, { 51 onError(error) { 52 toast.error(toMessage(error, 'データの取得に失敗しました')); 53 }, 54 }); 55 if (queryResult.isLoading) { 56 // TODO: Loaderコンポーネントの子供にフォームのコンポーネントをセットして、フォームは出すようにする 57 return ( 58 <Loader isLoading={queryResult.isLoading}/> 59 ); 60 } 61 62 if (queryResult.error) { 63 return ( 64 <> 65 <ToastContainer closeOnClick={false}/> 66 <PageTitle>設定</PageTitle> 67 </> 68 ); 69 } 70 71 const email = emailState ?? queryResult.data.viewer.email; 72 const notificationTimeSpans = notificationTimeSpansState ?? toModels(queryResult.data.viewer.notificationTimeSpans); 73 74 const handleAddTimeSpan = () => { 75 const maxTimeSpans = 3; 76 if (notificationTimeSpans.length >= maxTimeSpans) { 77 return; 78 } 79 80 setNotificationTimeSpansState([...notificationTimeSpans, new NotificationTimeSpanModel(0, 0, 0, 0)]); 81 }; 82 83 const handleDeleteTimeSpan = (index: number) => { 84 const timeSpans = [...notificationTimeSpans]; 85 if (index >= timeSpans.length) { 86 return; 87 } 88 89 timeSpans.splice(index, 1); 90 setNotificationTimeSpansState(timeSpans); 91 }; 92 93 const handleOnChangeTimeSpan = (name: string, index: number, value: number) => { 94 const timeSpans = [...notificationTimeSpans]; 95 timeSpans[index][name as keyof NotificationTimeSpan] = value; 96 setNotificationTimeSpansState(timeSpans); 97 }; 98 99 const handleUpdateTimeSpan = () => { 100 const timeSpans: NotificationTimeSpanModel[] = []; 101 for (const timeSpan of notificationTimeSpans) { 102 for (const [k, v] of Object.entries<NotificationTimeSpan>(timeSpan)) { 103 timeSpan[k as keyof NotificationTimeSpan] = Number(v); 104 } 105 106 if (NotificationTimeSpanModel.fromObject(timeSpan).isZero()) { // `timeSpan` is object somehow... 107 // Ignore zero value 108 continue; 109 } 110 111 timeSpans.push(timeSpan); 112 } 113 114 setNotificationTimeSpansState(timeSpans); 115 // Console.log('BEFORE updateMeNotificationTimeSpanMutation.mutate()'); 116 updateNotificationTimeSpansMutation.mutate({ 117 input: {timeSpans}, 118 }); 119 }; 120 121 return ( 122 <div> 123 <ToastContainer closeOnClick={false}/> 124 <PageTitle>設定</PageTitle> 125 <EmailForm 126 email={email} 127 handleOnChange={event => { 128 setEmailState(event.currentTarget.value); 129 }} 130 handleUpdateEmail={(em): boolean => { 131 let success = true; 132 updateViewerMutation.mutate({input: {email: em}}, { 133 onError() { 134 success = false; 135 }, 136 }); 137 return success; 138 }} 139 /> 140 <div className="mb-3"/> 141 <NotificationTimeSpanForm 142 handleAdd={handleAddTimeSpan} 143 handleDelete={handleDeleteTimeSpan} 144 handleUpdate={handleUpdateTimeSpan} 145 handleOnChange={handleOnChangeTimeSpan} 146 timeSpans={notificationTimeSpans} 147 /> 148 </div> 149 ); 150 }; 151 152 const toModels = (timeSpans: NotificationTimeSpan[]): NotificationTimeSpanModel[] => timeSpans.map<NotificationTimeSpanModel>(o => NotificationTimeSpanModel.fromObject(o));