From 303a2295169b87241186cd3830c72806a54a637f Mon Sep 17 00:00:00 2001 From: himarori Date: Sun, 6 Oct 2024 17:26:27 +0900 Subject: [PATCH 1/3] =?UTF-8?q?#140=20func-style=E3=81=AE=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Hosting/eslint.config.js | 2 +- Hosting/src/Auth.ts | 20 +++++----- Hosting/src/Data.ts | 41 ++++++++++----------- Hosting/src/ULID.ts | 8 ++-- Hosting/src/components/AuthContext.tsx | 4 +- Hosting/src/components/pages/Login.tsx | 4 +- Hosting/src/components/ui/DraggableList.tsx | 12 +++--- Hosting/src/firebase.ts | 4 +- 8 files changed, 46 insertions(+), 49 deletions(-) diff --git a/Hosting/eslint.config.js b/Hosting/eslint.config.js index 5dd0d76..3ed326d 100644 --- a/Hosting/eslint.config.js +++ b/Hosting/eslint.config.js @@ -45,7 +45,7 @@ export default tseslint.config( 'globalThis', ], // 関数の記述方法を関数式(アロー関数を含む)に統一 - 'func-style': ['warn', 'expression', { allowArrowFunctions: true }], + 'func-style': ['error', 'expression', { allowArrowFunctions: true }], 'arrow-body-style': ['error', 'as-needed'], // アロー関数の本体の記述方法を制限 }, }, diff --git a/Hosting/src/Auth.ts b/Hosting/src/Auth.ts index 1ce5b17..41b0c58 100644 --- a/Hosting/src/Auth.ts +++ b/Hosting/src/Auth.ts @@ -15,16 +15,16 @@ type providerTypes = 'google' | 'facebook' | 'email'; * @param email メールアドレス * @param pass パスワード */ -export async function loginWithEmail(email: string, pass: string) { +export const loginWithEmail = async (email: string, pass: string) => { const auth = getAuth(); return await signInWithEmailAndPassword(auth, email, pass); -} +}; /** * メール・パスワードによる新規登録 * @returns */ -export async function signupWithEmail(email: string, pass: string) { +export const signupWithEmail = async (email: string, pass: string) => { const auth = getAuth(); const userCredential = await createUserWithEmailAndPassword( auth, @@ -35,10 +35,10 @@ export async function signupWithEmail(email: string, pass: string) { // メールアドレス確認メールを送信する await sendEmailVerification(userCredential.user); return userCredential; -} +}; /** Googleログイン・新規登録 */ -export async function loginWith(providerType: providerTypes) { +export const loginWith = async (providerType: providerTypes) => { let provider; switch (providerType) { case 'google': @@ -57,21 +57,21 @@ export async function loginWith(providerType: providerTypes) { return await signInWithPopup(getAuth(), provider).catch((error: unknown) => { console.error(error); }); -} +}; /** * ログアウト */ -export async function signOut() { +export const signout = async () => { await getAuth().signOut(); -} +}; /** * メールアドレス確認メールを再送信する */ -export async function sendVerifyEmail() { +export const sendVerifyEmail = async () => { const user = getAuth().currentUser; if (user) { await sendEmailVerification(user); } -} +}; diff --git a/Hosting/src/Data.ts b/Hosting/src/Data.ts index d882947..e4fa102 100644 --- a/Hosting/src/Data.ts +++ b/Hosting/src/Data.ts @@ -37,11 +37,10 @@ const imageCompOptions: Options = { maxWidthOrHeight: 1000, }; -function getCreatorStorageUrl(userId: string) { - return `https://firebasestorage.googleapis.com/v0/b/gallery-found.appspot.com/o/creators%2F${userId}%2F`; -} +const getCreatorStorageUrl = (userId: string) => + `https://firebasestorage.googleapis.com/v0/b/gallery-found.appspot.com/o/creators%2F${userId}%2F`; -export async function getCreatorData(user: User) { +export const getCreatorData = async (user: User) => { const userId = user.uid; const creatorUrl = getCreatorStorageUrl(userId); const creator: Creator = { @@ -100,12 +99,12 @@ export async function getCreatorData(user: User) { console.debug('creator:', creator); return creator; -} +}; /** * 値の確定、DBへデータを送信する */ -export async function setCreatorData(user: User, data: Creator) { +export const setCreatorData = async (user: User, data: Creator) => { const userId = user.uid; // 画像のアップロード @@ -165,12 +164,12 @@ export async function setCreatorData(user: User, data: Creator) { // 処理完了 console.debug('complete setCreatorData'); -} +}; /** * tmpImageDataの画像をアップロード、URLを格納 */ -async function uploadImageData(user: User, images: ImageStatus[]) { +const uploadImageData = async (user: User, images: ImageStatus[]) => { const uploadImage = async (image: ImageStatus) => { // イメージの更新が無ければスキップ if (image.tmpImageData === '') { @@ -198,10 +197,10 @@ async function uploadImageData(user: User, images: ImageStatus[]) { const tasks = images.map(exhibit => uploadImage(exhibit)); await Promise.all(tasks); -} +}; /** すべての展示情報の取得 */ -export async function getAllExhibits() { +export const getAllExhibits = async () => { const creatorsSnap = await getDocs( collection(db, collectionNames.creators).withConverter(fbCreatorConverter), ); @@ -231,7 +230,7 @@ export async function getAllExhibits() { const resolvedExhibits = await Promise.all(exhibitsPromises); return resolvedExhibits.flat(); -} +}; export interface GalleryExhibits { gallery: Gallery; @@ -239,7 +238,7 @@ export interface GalleryExhibits { } /** ギャラリー情報と関連する展示の配列を取得 */ -export async function getGalleryExhibits() { +export const getGalleryExhibits = async () => { const galleries = await getGalleries(); const exhibits = await getAllExhibits(); @@ -256,10 +255,10 @@ export async function getGalleryExhibits() { }); return array; -} +}; /** ギャラリー情報の一覧を取得 */ -export async function getGalleries() { +export const getGalleries = async () => { const colRef = collection(db, collectionNames.galleries); const querySnap = await getDocs(colRef.withConverter(fbGalleryConverter)); @@ -268,20 +267,20 @@ export async function getGalleries() { const { latitude, longitude } = data.latLng.toJSON(); return { ...data, id: doc.id, latLng: { lat: latitude, lng: longitude } }; }); -} +}; /** ギャラリー情報を追加 */ -export async function addGallery(data: Gallery) { +export const addGallery = async (data: Gallery) => { const { lat, lng } = await getLatLngFromAddress(data.location); const { id, ...firebaseData } = { ...data, latLng: new GeoPoint(lat, lng) }; void id; const docRef = doc(db, collectionNames.galleries, getUlid()); await setDoc(docRef.withConverter(fbGalleryConverter), firebaseData); -} +}; /** 住所から緯度経度を取得する */ -async function getLatLngFromAddress(address: string) { +const getLatLngFromAddress = async (address: string) => { const geocoder = new google.maps.Geocoder(); const geocodingTask = new Promise( (resolve, reject) => @@ -306,14 +305,14 @@ async function getLatLngFromAddress(address: string) { ); return results[0].geometry.location.toJSON(); -} +}; /** 日付の期間の表示値を返す */ -export function getDatePeriodString(start: Date, end: Date) { +export const getDatePeriodString = (start: Date, end: Date) => { const startString = start.toLocaleDateString(); const endString = end.toLocaleDateString(); return `${startString} ~ ${endString}`; -} +}; /** 作家 */ export interface Creator { diff --git a/Hosting/src/ULID.ts b/Hosting/src/ULID.ts index fb818f5..42763e6 100644 --- a/Hosting/src/ULID.ts +++ b/Hosting/src/ULID.ts @@ -1,10 +1,8 @@ import { ulid, decodeTime } from 'ulidx'; -export function getUlid() { - return ulid(); -} +export const getUlid = () => ulid(); -export function toDate(ulid: string) { +export const toDate = (ulid: string) => { const unixTime = decodeTime(ulid); return new Date(unixTime); -} +}; diff --git a/Hosting/src/components/AuthContext.tsx b/Hosting/src/components/AuthContext.tsx index 85130a6..8e52fd9 100644 --- a/Hosting/src/components/AuthContext.tsx +++ b/Hosting/src/components/AuthContext.tsx @@ -18,9 +18,7 @@ const AuthContext = createContext({ loading: true, }); -export function useAuthContext() { - return useContext(AuthContext); -} +export const useAuthContext = () => useContext(AuthContext); export const AuthProvider = (props: { children: ReactNode }) => { const [user, setUser] = useState(null); diff --git a/Hosting/src/components/pages/Login.tsx b/Hosting/src/components/pages/Login.tsx index 21146d6..537126d 100644 --- a/Hosting/src/components/pages/Login.tsx +++ b/Hosting/src/components/pages/Login.tsx @@ -20,7 +20,7 @@ interface LoginState { isRegister: boolean; } -function isLoginState(value: unknown): value is LoginState { +const isLoginState = (value: unknown): value is LoginState => { if (typeof value !== 'object' || value === null) { return false; } @@ -29,7 +29,7 @@ function isLoginState(value: unknown): value is LoginState { isRegister: false, }; return typeof record.isRegister === typeof loginState.isRegister; -} +}; export const Login = () => { const { user, loading } = useAuthContext(); diff --git a/Hosting/src/components/ui/DraggableList.tsx b/Hosting/src/components/ui/DraggableList.tsx index a8fd53f..c34503c 100644 --- a/Hosting/src/components/ui/DraggableList.tsx +++ b/Hosting/src/components/ui/DraggableList.tsx @@ -27,9 +27,9 @@ interface DraggableListProps { renderItem: RenderItem; } -export function DraggableList( +export const DraggableList = ( props: DraggableListProps, -) { +) => { const { items, setItems, renderItem } = props; const [activeId, setActiveId] = useState(); @@ -70,14 +70,16 @@ export function DraggableList( )} ); -} +}; interface SortableItemProps { item: T; renderItem: RenderItem; } -function SortableItem(props: SortableItemProps) { +const SortableItem = ( + props: SortableItemProps, +) => { const { item, renderItem } = props; const { @@ -106,7 +108,7 @@ function SortableItem(props: SortableItemProps) { })} ); -} +}; type RenderItem = (item: T, sortableProps: SortableProps) => ReactNode; diff --git a/Hosting/src/firebase.ts b/Hosting/src/firebase.ts index 9a39915..1c0ea8d 100644 --- a/Hosting/src/firebase.ts +++ b/Hosting/src/firebase.ts @@ -38,14 +38,14 @@ const config = getRemoteConfig(app); config.settings.minimumFetchIntervalMillis = process.env.NODE_ENV === 'development' ? 60000 : 3600000; -export async function getConfig(): Promise { +export const getConfig = async (): Promise => { await fetchAndActivate(config); return { debugUserIds: JSON.parse( getValue(config, 'debug_user_ids').asString(), ) as string[], }; -} +}; export const fbCreatorConverter: FirestoreDataConverter = { toFirestore: modelObject => modelObject, From 0bad9f7c953088c41fb4a833ad80e85daab61314 Mon Sep 17 00:00:00 2001 From: suna <23271500+FltSv@users.noreply.github.com> Date: Sat, 19 Oct 2024 20:26:10 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=20#140=20react/jsx-no-bind=E3=81=AE?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Hosting/eslint.config.js | 2 +- Hosting/src/Auth.ts | 2 +- Hosting/src/components/pages/Login.tsx | 71 +- Hosting/src/components/pages/Map.tsx | 32 +- Hosting/src/components/pages/Mypage.tsx | 712 ++++++++++++-------- Hosting/src/components/pages/Verify.tsx | 54 +- Hosting/src/components/ui/DraggableList.tsx | 28 +- Hosting/src/components/ui/Header.tsx | 5 +- Hosting/src/components/ui/Popup.tsx | 14 +- 9 files changed, 567 insertions(+), 353 deletions(-) diff --git a/Hosting/eslint.config.js b/Hosting/eslint.config.js index 3ed326d..6184046 100644 --- a/Hosting/eslint.config.js +++ b/Hosting/eslint.config.js @@ -177,7 +177,7 @@ export default tseslint.config( ...reactPlugin.configs['jsx-runtime'].rules, 'react/prop-types': 'off', 'react/hook-use-state': 'error', // useStateの返り値の命名を統一 - 'react/jsx-no-bind': 'warn', // JSX内での関数記述を禁止し、renderごとの関数生成を防止 + 'react/jsx-no-bind': 'error', // JSX内での関数記述を禁止し、renderごとの関数生成を防止 'react/jsx-boolean-value': 'error', // boolean型のPropsの渡し方を統一 'react/jsx-curly-brace-presence': 'error', // 不要な中括弧を禁止 'react/jsx-no-useless-fragment': 'error', // 不要なReact Fragmentの使用を禁止 diff --git a/Hosting/src/Auth.ts b/Hosting/src/Auth.ts index 41b0c58..e100da7 100644 --- a/Hosting/src/Auth.ts +++ b/Hosting/src/Auth.ts @@ -62,7 +62,7 @@ export const loginWith = async (providerType: providerTypes) => { /** * ログアウト */ -export const signout = async () => { +export const signOut = async () => { await getAuth().signOut(); }; diff --git a/Hosting/src/components/pages/Login.tsx b/Hosting/src/components/pages/Login.tsx index 537126d..4924b60 100644 --- a/Hosting/src/components/pages/Login.tsx +++ b/Hosting/src/components/pages/Login.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { FormEvent, useCallback, useState } from 'react'; import { useForm, SubmitHandler } from 'react-hook-form'; import { useNavigate, useLocation, Navigate, Link } from 'react-router-dom'; import { Divider, Checkbox } from '@mui/joy'; @@ -45,6 +45,47 @@ export const Login = () => { formState: { errors }, } = useForm(); + const isRegister = isLoginState(location.state) && location.state.isRegister; + + const onValid: SubmitHandler = useCallback( + async data => { + setIsSubmitting(true); + try { + if (isRegister) { + // 新規登録 + await signupWithEmail(data.mail, data.password); + navigate('/sendverify'); + } else { + // ログイン + await loginWithEmail(data.mail, data.password); + navigate('/mypage'); + } + } catch (error) { + console.error(error); + if (error instanceof FirebaseError) { + setLoginErrorMsg(error.message); + } + setIsSubmitting(false); + } + }, + [isRegister, navigate], + ); + + const onSubmit = useCallback( + (e: FormEvent) => { + handleSubmit(onValid)(e); + }, + [handleSubmit, onValid], + ); + + const loginWithGoogle = useCallback(() => { + loginWith('google'); + }, []); + + const loginWithFacebook = useCallback(() => { + loginWith('facebook'); + }, []); + if (loading) { return

Now loading...

; } @@ -54,38 +95,16 @@ export const Login = () => { return ; } - const isRegister = isLoginState(location.state) && location.state.isRegister; const actionText = isRegister ? 'sign up' : 'rogin'; const visiblePwd = watch('visiblePwd', false); - const onValid: SubmitHandler = async data => { - setIsSubmitting(true); - try { - if (isRegister) { - // 新規登録 - await signupWithEmail(data.mail, data.password); - navigate('/sendverify'); - } else { - // ログイン - await loginWithEmail(data.mail, data.password); - navigate('/mypage'); - } - } catch (error) { - console.error(error); - if (error instanceof FirebaseError) { - setLoginErrorMsg(error.message); - } - setIsSubmitting(false); - } - }; - const reqMessage = 'このフィールドは入力必須です。'; return (
void handleSubmit(onValid)(e)}> + onSubmit={onSubmit}>
{
{/* 法人化が必要そうなので非表示 */} diff --git a/Hosting/src/components/pages/Map.tsx b/Hosting/src/components/pages/Map.tsx index d42f8ee..3556d1d 100644 --- a/Hosting/src/components/pages/Map.tsx +++ b/Hosting/src/components/pages/Map.tsx @@ -23,15 +23,29 @@ const TOKYO_POS = { const TODAY = new Date(); -export const Map = () => ( -
- } - renderProcessView={() =>

現在位置取得中…

} - renderSuccessView={coords => } - /> -
-); +export const Map = () => { + const renderErrorView = useCallback( + (error: GeolocationPositionError) => , + [], + ); + + const renderProcessView = useCallback(() =>

現在位置取得中…

, []); + + const renderSuccessView = useCallback( + (coords: GeolocationCoordinates) => , + [], + ); + + return ( +
+ +
+ ); +}; interface MapViewProps { coords?: GeolocationCoordinates; diff --git a/Hosting/src/components/pages/Mypage.tsx b/Hosting/src/components/pages/Mypage.tsx index 6be0e3c..8deb8f3 100644 --- a/Hosting/src/components/pages/Mypage.tsx +++ b/Hosting/src/components/pages/Mypage.tsx @@ -1,5 +1,21 @@ -import { Dispatch, SetStateAction, useEffect, useState } from 'react'; -import { useForm, SubmitHandler, Controller } from 'react-hook-form'; +import { + ChangeEvent, + Dispatch, + FormEvent, + KeyboardEvent, + MouseEventHandler, + SetStateAction, + SyntheticEvent, + useCallback, + useEffect, + useState, +} from 'react'; +import { + useForm, + SubmitHandler, + Controller, + ControllerRenderProps, +} from 'react-hook-form'; import { Button as MuiJoyButton, IconButton, @@ -7,7 +23,7 @@ import { Input, Textarea, } from '@mui/joy'; -import { Autocomplete } from '@mui/material'; +import { Autocomplete, AutocompleteRenderInputParams } from '@mui/material'; import { FaCheck, FaPen, FaPlus, FaTimes } from 'react-icons/fa'; import { RiDraggable } from 'react-icons/ri'; import { useAuthContext } from 'components/AuthContext'; @@ -61,10 +77,8 @@ export const Mypage = () => { formState: { errors }, } = useForm(); - if (loading) { - //todo ローディングコンポーネントに置き換え - return

Now loading...

; - } + // - - - - - - - - + // SNSリンク関係の処理 const isValidUrl = (url: string) => { if (!url) return true; @@ -77,41 +91,232 @@ export const Mypage = () => { } }; - const onAddLink = () => { + /** Linkが検証されていれば、Linkを追加 */ + const onAddLink = useCallback(() => { if (creator === undefined) return; if (addLinkError) return; const links = [...creator.links, addLink]; setCreator({ ...creator, links }); setAddLink(''); - }; + }, [creator, addLink, addLinkError]); - const onValid: SubmitHandler = async data => { - // 一時データの結合 - data.links = creator?.links ?? []; - data.products = creator?.products ?? []; - data.exhibits = creator?.exhibits ?? []; + /** Linkの削除 */ + const handleRemoveLink = useCallback( + (link: string) => () => { + if (creator === undefined) return; - if (user === null) { - return; - } + const links = creator.links.filter(x => x !== link); + setCreator({ ...creator, links }); + }, + [creator], + ); - // ローディングの表示 - setIsSubmitting(true); + /** Linkの入力値変更時に、入力値の更新と検証を行う */ + const onChangeLinkInput = useCallback((e: ChangeEvent) => { + const input = e.target.value; + setAddLink(input); + setAddLinkError(!isValidUrl(input)); + }, []); - // 情報の送信 - console.debug('submit: ', data); - await setCreatorData(user, data); + const onKeyDownLinkInput = useCallback( + (e: KeyboardEvent) => { + if (e.key === 'Enter') { + e.preventDefault(); + onAddLink(); + } + }, + [onAddLink], + ); - // リロード - window.location.reload(); - }; + // - - - - - - - - + // 発表作品 + + /** + * 選択された各ファイルに対して `Product` オブジェクトを作成し、 + * `creator.Products` を更新 + */ + const onChangeProductFileInput = useCallback( + (e: ChangeEvent) => { + if (creator === undefined) return; + + const files = e.currentTarget.files; + if (files === null) return; + if (files.length === 0) return; + + for (const file of Array.from(files)) { + const url = URL.createObjectURL(file); + const product: Product = { + id: getUlid(), + title: '', + detail: '', + tmpImageData: url, + srcImage: '', + imageUrl: '', + }; + creator.products.push(product); + } + + setCreator({ ...creator, products: creator.products }); + }, + [creator], + ); + + /** + * `DraggableList` で並び替え後のアイテムを設定するコールバック関数 + */ + const setDraggableProducts = useCallback( + (items: Product[]) => { + if (creator === undefined) return; + + const newProducts = items + .map(item => creator.products.find(product => product.id === item.id)) + .filter(x => x !== undefined); + + setCreator({ ...creator, products: newProducts }); + }, + [creator], + ); + + /** 作品の削除 */ + const onDeleteRenderProduct = useCallback( + (product: Product) => { + if (creator === undefined) return; + + const newProducts = creator.products.filter(x => x.id !== product.id); + setCreator({ ...creator, products: newProducts }); + }, + [creator], + ); + + /** 作品の編集画面の表示 */ + const onEditRenderProduct = useCallback((product: Product) => { + setEditProduct(product); + setVisibleProductPopup(true); + }, []); + + const renderProductItem = useCallback( + (product: Product, props: SortableProps) => ( + + ), + [onDeleteRenderProduct, onEditRenderProduct], + ); + + // - - - - - - - - + // 展示 + + /** 展示追加 */ + const onClickAddExhibit = useCallback(() => { + setEditExhibit(undefined); + setVisibleExhibitPopup(true); + }, []); + + /** 展示削除 */ + const onDeleteExhibit = useCallback( + (exhibit: Exhibit) => { + if (creator === undefined) return; + + const newExhibits = creator.exhibits.filter(x => x.id !== exhibit.id); + setCreator({ ...creator, exhibits: newExhibits }); + }, + [creator], + ); + + /** 展示編集画面の表示 */ + const onEditExhibit = useCallback((exhibit: Exhibit) => { + setEditExhibit(exhibit); + setVisibleExhibitPopup(true); + }, []); + + // - - - - - - - - + // Popup関連 + + const onSubmitProductPopup = useCallback( + (newValue: Product) => { + if (creator === undefined) return; + if (editProduct === undefined) return; + + const newProducts = creator.products.map(product => + product.id === editProduct.id ? newValue : product, + ); + + setCreator({ ...creator, products: newProducts }); + setVisibleProductPopup(false); + }, + [creator, editProduct], + ); + + const onSubmitExhibitPopup = useCallback( + (newValue: Exhibit) => { + if (creator === undefined) return; + + if (editExhibit === undefined) { + // 追加 + const newExhibits = [...creator.exhibits, newValue]; + setCreator({ ...creator, exhibits: newExhibits }); + } else { + // 編集 + const newExhibits = creator.exhibits.map(exhibit => + exhibit.id === editExhibit.id ? newValue : exhibit, + ); + setCreator({ ...creator, exhibits: newExhibits }); + } + + setVisibleExhibitPopup(false); + }, + [creator, editExhibit], + ); + + // - - - - - - - - + // フォーム全体の検証、送信 + + const onValid: SubmitHandler = useCallback( + async data => { + // 一時データの結合 + data.links = creator?.links ?? []; + data.products = creator?.products ?? []; + data.exhibits = creator?.exhibits ?? []; + + if (user === null) { + return; + } + + // ローディングの表示 + setIsSubmitting(true); + + // 情報の送信 + console.debug('submit: ', data); + await setCreatorData(user, data); + + // リロード + window.location.reload(); + }, + [creator, user], + ); + + const onSubmit = useCallback( + (e: FormEvent) => { + handleSubmit(onValid)(e); + }, + [handleSubmit, onValid], + ); + + if (loading) { + //todo ローディングコンポーネントに置き換え + return

Now loading...

; + } return ( <> void handleSubmit(onValid)(e)}> + onSubmit={onSubmit}>

My Page

{

SNSリンク

- {creator?.links.map(link => ( -
- - - {link} - - { - const links = creator.links.filter(x => x !== link); - setCreator({ ...creator, links }); - }} - size="sm" - variant="plain"> - - - -
- ))} + {creator?.links.map(link => { + const removeLink = handleRemoveLink(link); + + return ( +
+ + + {link} + + + + + +
+ ); + })} { } - onChange={e => { - const input = e.target.value; - setAddLink(input); - setAddLinkError(!isValidUrl(input)); - }} - onKeyDown={e => { - if (e.key === 'Enter') { - e.preventDefault(); - onAddLink(); - } - }} + onChange={onChangeLinkInput} + onKeyDown={onKeyDownLinkInput} placeholder="https://..." sx={{ borderColor: 'black', @@ -203,61 +400,15 @@ export const Mypage = () => { accept="image/*" className="min-w-fit" multiple - onChange={e => { - if (creator === undefined) return; - - const files = e.currentTarget.files; - if (files === null) return; - if (files.length === 0) return; - - for (const file of Array.from(files)) { - const url = URL.createObjectURL(file); - const product: Product = { - id: getUlid(), - title: '', - detail: '', - tmpImageData: url, - srcImage: '', - imageUrl: '', - }; - creator.products.push(product); - } - - setCreator({ ...creator, products: creator.products }); - }} + onChange={onChangeProductFileInput} />
{creator && ( ( - { - const newProducts = creator.products.filter( - x => x.id !== product.id, - ); - - setCreator({ ...creator, products: newProducts }); - }} - onEdit={() => { - setEditProduct(product); - setVisibleProductPopup(true); - }} - sortableProps={props} - /> - )} - setItems={items => { - const newProducts = items - .map(item => - creator.products.find(product => product.id === item.id), - ) - .filter(x => x !== undefined); - - setCreator({ ...creator, products: newProducts }); - }} + renderItem={renderProductItem} + setItems={setDraggableProducts} /> )}
@@ -267,10 +418,7 @@ export const Mypage = () => {

展示登録

@@ -281,17 +429,8 @@ export const Mypage = () => { { - const newExhibits = creator.exhibits.filter( - x => x.id !== exhibit.id, - ); - - setCreator({ ...creator, exhibits: newExhibits }); - }} - onEdit={() => { - setEditExhibit(exhibit); - setVisibleExhibitPopup(true); - }} + onDelete={onDeleteExhibit} + onEdit={onEditExhibit} /> ))} @@ -309,19 +448,7 @@ export const Mypage = () => { {/* 発表作品 */} {editProduct && ( { - if (creator === undefined) { - return; - } - - const editIndex = creator.products.indexOf(editProduct); - if (editIndex !== -1) { - creator.products[editIndex] = newValue; - } - - setCreator(creator); - setVisibleProductPopup(false); - }} + onSubmit={onSubmitProductPopup} product={editProduct} setVisible={setVisibleProductPopup} visible={visibleProductPopup} @@ -330,28 +457,7 @@ export const Mypage = () => { {/* 展示登録 */} - { - if (creator === undefined) { - return; - } - - if (editExhibit === undefined) { - // 追加 - creator.exhibits.push(newValue); - } else { - // 編集 - const editIndex = creator.exhibits.indexOf(editExhibit); - if (editIndex !== -1) { - creator.exhibits[editIndex] = newValue; - } - } - - setCreator(creator); - setVisibleExhibitPopup(false); - }} - /> + ); @@ -367,6 +473,14 @@ interface ProductCellProps { const ProductCell = (props: ProductCellProps) => { const { data, onEdit, onDelete, sortableProps } = props; + const onEditClick = useCallback(() => { + onEdit(data); + }, [data, onEdit]); + + const onDeleteClick = useCallback(() => { + onDelete(data); + }, [data, onDelete]); + return (
@@ -385,9 +499,7 @@ const ProductCell = (props: ProductCellProps) => {
{ - onEdit(data); - }} + onClick={onEditClick} size="sm" sx={{ borderRadius: 9999 }} variant="soft"> @@ -395,9 +507,7 @@ const ProductCell = (props: ProductCellProps) => { { - onDelete(data); - }} + onClick={onDeleteClick} size="sm" sx={{ borderRadius: 9999 }} variant="soft"> @@ -417,6 +527,14 @@ interface ExhibitRowProps { const ExhibitRow = (props: ExhibitRowProps) => { const { data, onEdit, onDelete } = props; + const onEditClick = useCallback(() => { + onEdit(data); + }, [data, onEdit]); + + const onDeleteClick = useCallback(() => { + onDelete(data); + }, [data, onDelete]); + return ( @@ -436,9 +554,7 @@ const ExhibitRow = (props: ExhibitRowProps) => {
{ - onEdit(data); - }} + onClick={onEditClick} size="sm" variant="plain"> @@ -446,9 +562,7 @@ const ExhibitRow = (props: ExhibitRowProps) => { { - onDelete(data); - }} + onClick={onDeleteClick} size="sm" variant="plain"> @@ -477,23 +591,30 @@ const ProductPopup = (props: ProductPopupProps) => { reset(product); }, [product, reset]); - const onValid: SubmitHandler = data => { - const submitData: Product = { - ...product, - title: data.title, - detail: data.detail, - }; + const onValid: SubmitHandler = useCallback( + data => { + const submitData: Product = { + ...product, + title: data.title, + detail: data.detail, + }; + + onSubmit(submitData); + }, + [onSubmit, product], + ); - onSubmit(submitData); - }; + const onSubmitForm = useCallback( + (e: FormEvent) => { + e.preventDefault(); + handleSubmit(onValid)(e); + }, + [handleSubmit, onValid], + ); return ( - { - e.preventDefault(); - void handleSubmit(onValid)(e); - }}> +

作品情報編集

@@ -551,13 +672,16 @@ const ExhibitForm = (props: ExhibitFormProps) => { formState: { errors }, } = useForm({ defaultValues: exhibit }); - const fetchGalleries = () => - void (async () => { - const galleries = await getGalleries(); - setGalleries(galleries); - })(); + const fetchGalleries = useCallback( + () => + void (async () => { + const galleries = await getGalleries(); + setGalleries(galleries); + })(), + [], + ); - useEffect(fetchGalleries, []); + useEffect(fetchGalleries, [fetchGalleries]); const requireMsg = '1文字以上の入力が必要です。'; const invalidDateMsg = '有効な日付を入力してください。'; @@ -573,35 +697,105 @@ const ExhibitForm = (props: ExhibitFormProps) => { const matchGallery = galleries?.find(x => x.name === location); const isMatchGallery = matchGallery !== undefined; - const onValid: SubmitHandler = data => { - if (!isMatchGallery) { - setError('location', { - message: 'ギャラリー情報の指定または入力が必要です。', - }); - return; - } + type LocationFieldProps = ControllerRenderProps< + ExhibitFormValues, + 'location' + >; + + const handleLocationChange = useCallback( + (field: LocationFieldProps) => + (event: SyntheticEvent, value: string | null) => { + field.onChange(value); + }, + [], + ); - const submitData: Exhibit = { - id: exhibit?.id ?? getUlid(), - title: data.title, - location: data.location, - galleryId: matchGallery.id, - startDate: new Date(data.startDateString + 'T00:00:00'), - endDate: new Date(data.endDateString + 'T23:59:59'), - srcImage: data.srcImage, - tmpImageData: tmpImage, - imageUrl: exhibit?.imageUrl ?? '', - }; - - onSubmit(submitData); - }; + const handleRenderInput = useCallback((field: LocationFieldProps) => { + const renderAutocompleteInput = (params: AutocompleteRenderInputParams) => ( + { + field.onChange(e); + params.inputProps.onChange?.(e); + }, + }, + }} + sx={{ borderColor: 'black' }} + /> + ); + return renderAutocompleteInput; + }, []); + + const renderLocation = useCallback( + ({ field }: { field: LocationFieldProps }) => { + const onChange = handleLocationChange(field); + const renderInput = handleRenderInput(field); + + return ( + x.name) ?? []} + renderInput={renderInput} + value={field.value || null} + /> + ); + }, + [galleries, handleLocationChange, handleRenderInput], + ); + + const onChangeLocation = useCallback(() => { + fetchGalleries(); + setError('location', {}); + }, [setError, fetchGalleries]); + + const onValid: SubmitHandler = useCallback( + data => { + if (!isMatchGallery) { + setError('location', { + message: 'ギャラリー情報の指定または入力が必要です。', + }); + return; + } + + const submitData: Exhibit = { + id: exhibit?.id ?? getUlid(), + title: data.title, + location: data.location, + galleryId: matchGallery.id, + startDate: new Date(data.startDateString + 'T00:00:00'), + endDate: new Date(data.endDateString + 'T23:59:59'), + srcImage: data.srcImage, + tmpImageData: tmpImage, + imageUrl: exhibit?.imageUrl ?? '', + }; + + onSubmit(submitData); + }, + [ + exhibit?.id, + exhibit?.imageUrl, + isMatchGallery, + matchGallery?.id, + onSubmit, + setError, + tmpImage, + ], + ); + + const onSubmitForm = useCallback( + (e: FormEvent) => { + e.preventDefault(); + handleSubmit(onValid)(e); + }, + [handleSubmit, onValid], + ); return ( - { - e.preventDefault(); - void handleSubmit(onValid)(e); - }}> +

{isAdd ? '展示登録' : '展示修正'}

@@ -639,31 +833,7 @@ const ExhibitForm = (props: ExhibitFormProps) => { ( - { - field.onChange(value); - }} - options={galleries?.map(x => x.name) ?? []} - renderInput={params => ( - { - field.onChange(e); - params.inputProps.onChange?.(e); - }, - }, - }} - sx={{ borderColor: 'black' }} - /> - )} - value={field.value || null} - /> - )} + render={renderLocation} rules={{ required: requireMsg }} />

{errors.location?.message}

@@ -675,13 +845,7 @@ const ExhibitForm = (props: ExhibitFormProps) => {
) : ( - { - fetchGalleries(); - setError('location', {}); - }} - /> + )}
{ formState: { errors, isSubmitting }, } = useForm({ defaultValues: { name: props.newName } as Gallery }); - const onValid: SubmitHandler = async data => { - if (!props.newName) { - setError('name', { message: '場所(ギャラリー名称)が未入力です。' }); - return; - } - - try { - await addGallery({ ...data, name: props.newName }); - } catch (error) { - console.error('error: ', error); - setError('name', { message: '入力された住所が見つかりませんでした。' }); - return; - } + const onValid: SubmitHandler = useCallback( + async data => { + if (!props.newName) { + setError('name', { message: '場所(ギャラリー名称)が未入力です。' }); + return; + } + + try { + await addGallery({ ...data, name: props.newName }); + } catch (error) { + console.error('error: ', error); + setError('name', { message: '入力された住所が見つかりませんでした。' }); + return; + } + + props.onChange(); + }, + [props, setError], + ); - props.onChange(); - }; + const onClick: MouseEventHandler = useCallback( + e => { + clearErrors('name'); + void handleSubmit(onValid)(e); + }, + [clearErrors, handleSubmit, onValid], + ); return ( @@ -777,10 +952,7 @@ const NoGalleryInfo = (props: NoGalleryProps) => { className="w-fit" color="neutral" loading={isSubmitting} - onClick={e => { - clearErrors('name'); - void handleSubmit(onValid)(e); - }} + onClick={onClick} size="sm" startDecorator={} variant="soft"> diff --git a/Hosting/src/components/pages/Verify.tsx b/Hosting/src/components/pages/Verify.tsx index 329b570..047a79d 100644 --- a/Hosting/src/components/pages/Verify.tsx +++ b/Hosting/src/components/pages/Verify.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useCallback, useState } from 'react'; import { sendVerifyEmail } from 'src/Auth'; const divStyle = 'mx-auto flex w-full max-w-xl flex-col items-center'; @@ -7,6 +7,10 @@ const h1Style = 'my-2 md:my-6 text-2xl md:text-3xl font-bold text-black'; export const SendVerify = () => { const [isResent, setIsResent] = useState(false); + const onResend = useCallback(() => { + setIsResent(true); + }, [setIsResent]); + return isResent ? ( ) : ( @@ -24,11 +28,8 @@ export const SendVerify = () => {

受信フォルダ内に確認メールが見当たらない場合は、迷惑メールフォルダもご確認ください。

- { - setIsResent(true); - }} - /> + +
); }; @@ -36,6 +37,10 @@ export const SendVerify = () => { export const NoVerify = () => { const [isResent, setIsResent] = useState(false); + const onResend = useCallback(() => { + setIsResent(true); + }, [setIsResent]); + return isResent ? ( ) : ( @@ -47,11 +52,7 @@ export const NoVerify = () => {

受信フォルダ内に確認メールが見当たらない場合は、迷惑メールフォルダもご確認ください。

- { - setIsResent(true); - }} - /> +
); }; @@ -69,19 +70,20 @@ export const ReSent = () => (
); -const ReSendLink = (props: { setter: () => void }) => ( - - void (async () => { - // 確認メールを再送信 - await sendVerifyEmail(); +const ReSendLink = (props: { setter: () => void }) => { + const onClick = useCallback(() => { + (async () => { + // 確認メールを再送信 + await sendVerifyEmail(); - // ReSentページに遷移 - props.setter(); - })() - }> - 確認メールを再送信する - -); + // ReSentページに遷移 + props.setter(); + })(); + }, [props]); + + return ( + + 確認メールを再送信する + + ); +}; diff --git a/Hosting/src/components/ui/DraggableList.tsx b/Hosting/src/components/ui/DraggableList.tsx index c34503c..6ad23bc 100644 --- a/Hosting/src/components/ui/DraggableList.tsx +++ b/Hosting/src/components/ui/DraggableList.tsx @@ -1,4 +1,4 @@ -import { ReactNode, useState } from 'react'; +import { ReactNode, useCallback, useState } from 'react'; import { DndContext, closestCenter, @@ -35,18 +35,24 @@ export const DraggableList = ( const [activeId, setActiveId] = useState(); const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor)); - const onDragStart = ({ active }: DragStartEvent) => { - setActiveId(active.id); - }; + const onDragStart = useCallback( + ({ active }: DragStartEvent) => { + setActiveId(active.id); + }, + [setActiveId], + ); - const onDragEnd = ({ active, over }: DragEndEvent) => { - if (active.id !== over?.id) { - const oldIndex = items.findIndex(item => item.id === active.id); - const newIndex = items.findIndex(item => item.id === over?.id); + const onDragEnd = useCallback( + ({ active, over }: DragEndEvent) => { + if (active.id !== over?.id) { + const oldIndex = items.findIndex(item => item.id === active.id); + const newIndex = items.findIndex(item => item.id === over?.id); - setItems(arrayMove(items, oldIndex, newIndex)); - } - }; + setItems(arrayMove(items, oldIndex, newIndex)); + } + }, + [items, setItems], + ); const overlayItem = items.find(item => item.id === activeId); diff --git a/Hosting/src/components/ui/Header.tsx b/Hosting/src/components/ui/Header.tsx index f41106c..34aeacc 100644 --- a/Hosting/src/components/ui/Header.tsx +++ b/Hosting/src/components/ui/Header.tsx @@ -3,6 +3,7 @@ import { Menu, MenuButton, MenuItem, Dropdown, IconButton } from '@mui/joy'; import { FaBars } from 'react-icons/fa'; import { signOut } from '../../Auth'; import { useAuthContext } from '../AuthContext'; +import { useCallback } from 'react'; const Header = () => { const styles = @@ -19,9 +20,9 @@ const Header = () => { const visibleMenu = visibleLogin || visibleMypage || visibleLogout; - const onSignOut = () => { + const onSignOut = useCallback(() => { void signOut(); - }; + }, []); return (
diff --git a/Hosting/src/components/ui/Popup.tsx b/Hosting/src/components/ui/Popup.tsx index cf8907c..3f24c11 100644 --- a/Hosting/src/components/ui/Popup.tsx +++ b/Hosting/src/components/ui/Popup.tsx @@ -1,4 +1,4 @@ -import { ReactNode, Dispatch, SetStateAction } from 'react'; +import { ReactNode, Dispatch, SetStateAction, useCallback } from 'react'; import { Modal, ModalDialog, ModalOverflow } from '@mui/joy'; import { FaXmark } from 'react-icons/fa6'; @@ -11,11 +11,13 @@ interface PopupProps { export const Popup = (props: PopupProps) => { const { visible, setVisible, children } = props; + const onClose = useCallback(() => { + setVisible(false); + }, [setVisible]); + return ( { - setVisible(false); - }} + onClose={onClose} open={visible} sx={{ display: 'flex', @@ -26,9 +28,7 @@ export const Popup = (props: PopupProps) => { {children} From dedb8f275e5646930d387c4983141153c6d83ad8 Mon Sep 17 00:00:00 2001 From: suna <23271500+FltSv@users.noreply.github.com> Date: Sun, 20 Oct 2024 11:17:14 +0900 Subject: [PATCH 3/3] =?UTF-8?q?#140=20functional/immutable-data=E3=81=AE?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Hosting/eslint.config.js | 3 +- Hosting/src/Auth.ts | 21 +++--- Hosting/src/Data.ts | 85 +++++++++++++++---------- Hosting/src/components/pages/Mypage.tsx | 47 +++++++------- Hosting/src/firebase.ts | 8 +-- 5 files changed, 90 insertions(+), 74 deletions(-) diff --git a/Hosting/eslint.config.js b/Hosting/eslint.config.js index 6184046..258703a 100644 --- a/Hosting/eslint.config.js +++ b/Hosting/eslint.config.js @@ -140,7 +140,7 @@ export default tseslint.config( }, ], 'functional/immutable-data': [ - 'warn', // オブジェクトの変更を警告 + 'error', // オブジェクトの変更を警告 { ignoreImmediateMutation: true, // 変数に代入する前の即時変更を許可 ignoreClasses: true, // クラスの変更を許可 @@ -149,6 +149,7 @@ export default tseslint.config( 'window.location.href', ], ignoreAccessorPattern: [ + 'window.**', // windowオブジェクトの変更を許可 '**.current.**', // React.useRefのcurrentプロパティへの変更を許可 '**.displayName', // React componentのdisplayNameプロパティへの変更を許可 '**.scrollTop', // スクロール位置の変更を許可 diff --git a/Hosting/src/Auth.ts b/Hosting/src/Auth.ts index e100da7..ca3b0f7 100644 --- a/Hosting/src/Auth.ts +++ b/Hosting/src/Auth.ts @@ -39,19 +39,18 @@ export const signupWithEmail = async (email: string, pass: string) => { /** Googleログイン・新規登録 */ export const loginWith = async (providerType: providerTypes) => { - let provider; - switch (providerType) { - case 'google': - provider = new GoogleAuthProvider(); - break; + const provider = (() => { + switch (providerType) { + case 'google': + return new GoogleAuthProvider(); - case 'facebook': - provider = new FacebookAuthProvider(); - break; + case 'facebook': + return new FacebookAuthProvider(); - default: - throw new Error('Invalid provider type'); - } + default: + throw new Error('Invalid provider type'); + } + })(); // ポップアップでログイン return await signInWithPopup(getAuth(), provider).catch((error: unknown) => { diff --git a/Hosting/src/Data.ts b/Hosting/src/Data.ts index e4fa102..da513c2 100644 --- a/Hosting/src/Data.ts +++ b/Hosting/src/Data.ts @@ -43,13 +43,6 @@ const getCreatorStorageUrl = (userId: string) => export const getCreatorData = async (user: User) => { const userId = user.uid; const creatorUrl = getCreatorStorageUrl(userId); - const creator: Creator = { - name: '', - profile: '', - links: [], - products: [], - exhibits: [], - }; // Firestoreからユーザーデータを取得 const docRef = doc(db, collectionNames.creators, userId).withConverter( @@ -60,20 +53,28 @@ export const getCreatorData = async (user: User) => { // 作家情報が存在しているか if (!docSnap.exists()) { // 存在しない場合、情報は空のままで登録を促す - return creator; + const empty: Creator = { + name: '', + profile: '', + links: [], + products: [], + exhibits: [], + }; + + return empty; } // ドキュメントが存在する場合、詳細を取得 const data = docSnap.data(); console.debug('docSnap.data:', data); - creator.name = data.name ?? ''; - creator.profile = data.profile ?? ''; - creator.links = data.links ?? []; + const name = data.name ?? ''; + const profile = data.profile ?? ''; + const links = data.links ?? []; // 発表作品 const fbProducts = data.products ?? []; - creator.products = fbProducts.map(x => ({ + const products = fbProducts.map(x => ({ id: x.id, title: x.title ?? '', detail: x.detail ?? '', @@ -85,7 +86,7 @@ export const getCreatorData = async (user: User) => { // 展示登録 const fbExhibits = data.exhibits ?? []; const today = new Date(); - creator.exhibits = fbExhibits.map(x => ({ + const exhibits = fbExhibits.map(x => ({ id: x.id, title: x.title, location: x.location, @@ -97,6 +98,14 @@ export const getCreatorData = async (user: User) => { tmpImageData: '', })); + const creator: Creator = { + name: name, + profile: profile, + links: links, + products: products, + exhibits: exhibits, + }; + console.debug('creator:', creator); return creator; }; @@ -108,8 +117,8 @@ export const setCreatorData = async (user: User, data: Creator) => { const userId = user.uid; // 画像のアップロード - await uploadImageData(user, data.products); - await uploadImageData(user, data.exhibits); + const products = await uploadImageData(user, data.products); + const exhibits = await uploadImageData(user, data.exhibits); // DB更新 const docRef = doc(db, collectionNames.creators, userId).withConverter( @@ -119,13 +128,13 @@ export const setCreatorData = async (user: User, data: Creator) => { name: data.name, profile: data.profile, links: data.links, - products: data.products.map(x => ({ + products: products.map(x => ({ id: x.id, title: x.title, detail: x.detail, image: x.srcImage, })), - exhibits: data.exhibits.map(x => ({ + exhibits: exhibits.map(x => ({ id: x.id, title: x.title, location: x.location, @@ -138,7 +147,7 @@ export const setCreatorData = async (user: User, data: Creator) => { // 使用されていない画像の削除 // 使用中の画像 - const usingImages = [...data.products, ...data.exhibits].map( + const usingImages = [...products, ...exhibits].map( (x: ImageStatus) => x.srcImage.split('?')[0], ); @@ -169,11 +178,14 @@ export const setCreatorData = async (user: User, data: Creator) => { /** * tmpImageDataの画像をアップロード、URLを格納 */ -const uploadImageData = async (user: User, images: ImageStatus[]) => { - const uploadImage = async (image: ImageStatus) => { +const uploadImageData = async ( + user: User, + images: T[], +): Promise => { + const uploadImage = async (image: T) => { // イメージの更新が無ければスキップ if (image.tmpImageData === '') { - return; + return image; } // blobURL→blobオブジェクトへ変換 @@ -192,11 +204,13 @@ const uploadImageData = async (user: User, images: ImageStatus[]) => { const url = await getDownloadURL(result.ref); const name = result.metadata.name; - image.srcImage = url.match(`${name}.*`)?.[0] ?? ''; + const srcImage = url.match(`${name}.*`)?.[0] ?? ''; + const newImage: T = { ...image, srcImage: srcImage }; + return newImage; }; - const tasks = images.map(exhibit => uploadImage(exhibit)); - await Promise.all(tasks); + const tasks = images.map(uploadImage); + return await Promise.all(tasks); }; /** すべての展示情報の取得 */ @@ -242,17 +256,18 @@ export const getGalleryExhibits = async () => { const galleries = await getGalleries(); const exhibits = await getAllExhibits(); - const array: GalleryExhibits[] = []; - - Map.groupBy(exhibits, x => x.location).forEach((value, key) => { - const gallery = galleries.find(x => x.name === key); - if (gallery === undefined) return; - - array.push({ - gallery: gallery, - exhibits: value, - }); - }); + const groupedExhibits = Map.groupBy(exhibits, x => x.location); + + const array = Array.from(groupedExhibits.entries()) + .map(([key, value]) => { + const gallery = galleries.find(x => x.name === key); + if (gallery === undefined) return null; + return { + gallery: gallery, + exhibits: value, + }; + }) + .filter((x): x is GalleryExhibits => x !== null); return array; }; diff --git a/Hosting/src/components/pages/Mypage.tsx b/Hosting/src/components/pages/Mypage.tsx index 8deb8f3..21d7118 100644 --- a/Hosting/src/components/pages/Mypage.tsx +++ b/Hosting/src/components/pages/Mypage.tsx @@ -144,20 +144,19 @@ export const Mypage = () => { if (files === null) return; if (files.length === 0) return; - for (const file of Array.from(files)) { - const url = URL.createObjectURL(file); - const product: Product = { - id: getUlid(), - title: '', - detail: '', - tmpImageData: url, - srcImage: '', - imageUrl: '', - }; - creator.products.push(product); - } - - setCreator({ ...creator, products: creator.products }); + const newProducts: Product[] = Array.from(files).map(file => ({ + id: getUlid(), + title: '', + detail: '', + tmpImageData: URL.createObjectURL(file), + srcImage: '', + imageUrl: '', + })); + + setCreator({ + ...creator, + products: [...creator.products, ...newProducts], + }); }, [creator], ); @@ -278,21 +277,23 @@ export const Mypage = () => { const onValid: SubmitHandler = useCallback( async data => { - // 一時データの結合 - data.links = creator?.links ?? []; - data.products = creator?.products ?? []; - data.exhibits = creator?.exhibits ?? []; + if (user === null) return; + if (creator === undefined) return; - if (user === null) { - return; - } + // 一時データの結合 + const submitData = { + ...data, + links: creator.links, + products: creator.products, + exhibits: creator.exhibits, + }; // ローディングの表示 setIsSubmitting(true); // 情報の送信 - console.debug('submit: ', data); - await setCreatorData(user, data); + console.debug('submit: ', submitData); + await setCreatorData(user, submitData); // リロード window.location.reload(); diff --git a/Hosting/src/firebase.ts b/Hosting/src/firebase.ts index 1c0ea8d..0a15b5a 100644 --- a/Hosting/src/firebase.ts +++ b/Hosting/src/firebase.ts @@ -34,15 +34,15 @@ export const db = getFirestore(app); // Remote Config // 最小フェッチ時間: dev1分、prod1時間 -const config = getRemoteConfig(app); -config.settings.minimumFetchIntervalMillis = +const mut_config = getRemoteConfig(app); +mut_config.settings.minimumFetchIntervalMillis = process.env.NODE_ENV === 'development' ? 60000 : 3600000; export const getConfig = async (): Promise => { - await fetchAndActivate(config); + await fetchAndActivate(mut_config); return { debugUserIds: JSON.parse( - getValue(config, 'debug_user_ids').asString(), + getValue(mut_config, 'debug_user_ids').asString(), ) as string[], }; };