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));