;\n showDeleteButton?: boolean;\n}) => {\n const {\n isError,\n _private: { setFile, validateArg, preview, setPreview },\n } = videoFileDropzoneState;\n\n const onDrop = useCallback(\n (acceptedFiles: File[]) => {\n const file = acceptedFiles[0] as File;\n const result = URL.createObjectURL(file);\n setFile(file);\n validateArg(file);\n setPreview(result);\n },\n [setFile, setPreview, validateArg],\n );\n\n const deletePreview = () => {\n setFile(undefined);\n validateArg(undefined);\n setPreview(\"\");\n };\n\n return (\n <>\n {preview ? (\n \n \n {showDeleteButton && (\n \n 動画を変更\n \n )}\n
\n ) : (\n \n \n {isError && {`${label}は必須です`}}\n \n )}\n >\n );\n};\n","import React from \"react\";\nimport {\n Checkbox as ChakuraCheckbox,\n CheckboxProps,\n forwardRef,\n} from \"@chakra-ui/react\";\n\nconst Checkbox = forwardRef(\n ({ children, ...props }, ref) => {\n return (\n \n {children}\n \n );\n },\n);\n\nexport default Checkbox;\n","import { DirectUpload } from \"@rails/activestorage\";\n\n// https://railsguides.jp/active_storage_overview.html#ライブラリやフレームワークとの統合\nclass DirectUploader {\n #upload: DirectUpload;\n #setProgress?: (n: number) => void;\n\n constructor(\n file: File,\n url: string,\n setProgress?: (progress: number) => void,\n ) {\n this.#upload = new DirectUpload(file, url, this);\n this.#setProgress = setProgress;\n // 大きいファイルだとアップロード処理の開始自体に時間がかかり、何も動作していないように見えてしまうのを防ぐため\n if (this.#setProgress) {\n this.#setProgress(0.1);\n }\n }\n\n async upload(): Promise<{ blob: { signed_id: string }; error: Error }> {\n return new Promise((resolve) => {\n this.#upload.create((error, blob) => {\n resolve({ blob, error });\n });\n });\n }\n\n directUploadWillStoreFileWithXHR(request: XMLHttpRequest) {\n request.upload.addEventListener(\"progress\", (event) =>\n this.directUploadDidProgress(event),\n );\n }\n\n directUploadDidProgress(event: ProgressEvent) {\n const percentComplete = (event.loaded / event.total) * 100;\n if (this.#setProgress) {\n this.#setProgress(percentComplete);\n }\n }\n}\n\nexport const uploadFile = async ({\n file,\n setProgress,\n}: {\n file: File;\n setProgress?: (progress: number) => void;\n}) => {\n const onBeforeUnload = (e: Event) => {\n e.preventDefault();\n };\n window.addEventListener(\"beforeunload\", onBeforeUnload);\n const result = await new DirectUploader(\n file,\n \"/rails/active_storage/direct_uploads\",\n setProgress,\n ).upload();\n window.removeEventListener(\"beforeunload\", onBeforeUnload);\n\n return result;\n};\n\nexport const uploadFiles = async ({ files }: { files: File[] }) => {\n const promises = files.map((file) => uploadFile({ file }));\n\n return await Promise.all(promises)\n};\n","import {\n Box,\n CheckboxGroup,\n Container,\n Flex,\n Heading,\n Progress,\n Stack,\n Text,\n} from \"@chakra-ui/react\";\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport React, { useState } from \"react\";\nimport { Controller, useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport { Button } from \"../../shared/components/atoms\";\nimport Background from \"../../shared/components/atoms/Background\";\nimport {\n ImageFileDropzone,\n useImageFileDropzoneState,\n} from \"../../shared/components/atoms/ImageFileDropzone\";\nimport ShadowCard from \"../../shared/components/atoms/ShadowCard\";\nimport {\n VideoFileDropzone,\n useVideoFileDropzoneState,\n} from \"../../shared/components/atoms/VideoFileDropzone\";\nimport {\n FormLabel,\n Input,\n InputError,\n Textarea,\n} from \"../../shared/components/atoms/form\";\nimport Checkbox from \"../../shared/components/atoms/form/Checkbox\";\nimport { SharedTag } from \"../../../shared/lib/types\";\nimport { uploadFile } from \"../../../shared/lib/uploadFile\";\nimport useFlash from \"../../shared/lib/useFlash\";\nconst schema = () =>\n yup.object({\n title: yup.string().trim().max(60).required().label(\"タイトル\"),\n description: yup.string().trim().max(1000).required().label(\"説明\"),\n viewable_datetime: yup\n .object()\n .required()\n .shape({\n started_at: yup.string().ensure().trim(),\n ended_at: yup\n .string()\n .trim()\n .ensure()\n .when(\"started_at\", ([started_at], schema) => {\n return schema.test(\n \"is-gt-started-at\",\n \"終了日時は開始日時よりも後の日時を入力してください\",\n (ended_at) => {\n return (\n started_at === \"\" ||\n ended_at === \"\" ||\n new Date(started_at) < new Date(ended_at)\n );\n },\n );\n }),\n }),\n tag_ids: yup\n .array(yup.string().required().trim())\n .min(1, \"タグは必須です\")\n .required(),\n instructor_info: yup.object().shape({\n name: yup.string().trim().max(30).required().label(\"講師名\"),\n introduction: yup.string().trim().max(1000).required().label(\"講師紹介\"),\n user_url: yup.string().trim().url(),\n }),\n html_meta_description: yup\n .string()\n .ensure()\n .trim()\n .max(150)\n .label(\"meta content\"),\n published: yup.bool().required(),\n comment_enabled: yup.bool().required(),\n });\n\ntype SchemaType = yup.InferType>;\nexport type RequestType = {\n study_group: Pick<\n SchemaType,\n | \"title\"\n | \"description\"\n | \"tag_ids\"\n | \"html_meta_description\"\n > & {\n comment_enabled: string;\n study_group_published_status: string;\n video_file: undefined | string;\n thumbnail: undefined | string;\n };\n viewable_datetime: SchemaType[\"viewable_datetime\"];\n instructor_info: SchemaType[\"instructor_info\"] & {\n instructor_image: undefined | string;\n };\n};\n\ntype FormType = SchemaType & {\n video_file_url?: string;\n thumbnail_url: string;\n instructor_info: { image_url: string };\n};\n\nconst StudyGroupForm = ({\n defaultValues,\n onSubmit,\n tags,\n title,\n submitButtonLabel,\n}: {\n defaultValues: FormType;\n onSubmit: (data: RequestType) => Promise;\n tags: SharedTag[];\n title: string;\n submitButtonLabel: string;\n}) => {\n const {\n handleSubmit,\n control,\n formState: { isSubmitting, errors },\n watch,\n trigger,\n } = useForm({\n defaultValues,\n resolver: yupResolver(schema()),\n });\n\n const videoFileFieldState = useVideoFileDropzoneState({\n required: true,\n firstPreview: defaultValues.video_file_url ?? \"\",\n });\n const thumbnailFieldState = useImageFileDropzoneState({\n required: true,\n firstPreview: defaultValues.thumbnail_url,\n });\n const instructorImageFieldState = useImageFileDropzoneState({\n required: true,\n firstPreview: defaultValues.instructor_info.image_url,\n });\n\n const validateFiles = () => {\n let result = true;\n result = thumbnailFieldState.validate() && result;\n result = videoFileFieldState.validate() && result;\n result = instructorImageFieldState.validate() && result;\n return result;\n };\n\n const [progress, setProgress] = useState(0);\n const showFlash = useFlash();\n\n const _onSubmit = async (data: SchemaType) => {\n if (!validateFiles()) {\n return;\n }\n\n const requestData: RequestType = {\n study_group: {\n title: data.title,\n description: data.description,\n tag_ids: data.tag_ids,\n study_group_published_status: data.published\n ? \"published\"\n : \"unpublished\",\n comment_enabled: data.comment_enabled ? \"1\" : \"0\",\n html_meta_description: data.html_meta_description,\n thumbnail: undefined,\n video_file: undefined,\n },\n viewable_datetime: data.viewable_datetime,\n instructor_info: {\n ...data.instructor_info,\n instructor_image: undefined,\n },\n };\n\n if (thumbnailFieldState.file) {\n const { blob } = await uploadFile({\n file: thumbnailFieldState.file,\n });\n requestData.study_group.thumbnail = blob.signed_id;\n }\n\n if (instructorImageFieldState.file) {\n const { blob } = await uploadFile({\n file: instructorImageFieldState.file,\n });\n requestData.instructor_info.instructor_image = blob.signed_id;\n }\n\n if (videoFileFieldState.file) {\n const { blob, error } = await uploadFile({\n file: videoFileFieldState.file,\n setProgress,\n });\n\n if (error) {\n showFlash({ error: \"エラーが発生しました\" });\n } else {\n requestData.study_group.video_file = blob.signed_id;\n }\n }\n\n await onSubmit(requestData);\n };\n\n const [previousStartedAt, setPreviousStartedAt] = useState(\"\");\n const [previousEndedAt, setPreviousEndedAt] = useState(\"\");\n\n const startedAt = watch(\"viewable_datetime.started_at\");\n const endedAt = watch(\"viewable_datetime.ended_at\");\n\n if (previousStartedAt !== startedAt || previousEndedAt !== endedAt) {\n void trigger(\"viewable_datetime.ended_at\");\n setPreviousStartedAt(startedAt);\n setPreviousEndedAt(endedAt);\n }\n\n const isSubmittingOrFileUploading =\n isSubmitting || (progress !== 0 && progress !== 100);\n\n return (\n \n \n \n \n \n \n {title}\n \n \n \n \n 基本情報\n \n (\n \n )}\n />\n (\n \n )}\n />\n \n 視聴可能日時\n \n \n 開始\n (\n \n )}\n />\n \n \n 終了\n (\n \n )}\n />\n \n \n \n ※開始時が未入力の場合は、公開と同時に視聴可能になります\n \n {errors.viewable_datetime?.ended_at && (\n \n {errors.viewable_datetime.ended_at.message}\n \n )}\n \n \n タグ\n (\n <>\n \n \n {tags.map(({ id, name }) => (\n \n {name}\n \n ))}\n \n \n {error?.message != null && (\n {error?.message}\n )}\n >\n )}\n />\n \n \n サムネイル\n \n \n \n 勉強会動画ファイル\n \n \n \n \n \n 講師情報\n \n (\n \n )}\n />\n (\n \n )}\n />\n \n 講師画像\n \n \n (\n \n )}\n />\n (\n \n )}\n />\n (\n \n コメントを有効にする\n \n )}\n />\n (\n \n 公開する\n \n )}\n />\n \n \n \n \n {progress !== 0 && (\n \n )}\n \n \n \n \n \n \n );\n};\n\nexport default StudyGroupForm;\n","import React from \"react\";\nimport { studyGroupPath, studyGroupsPath } from \"../../../routes\";\nimport { Flash, SharedTag } from \"../../shared/lib/types\";\nimport Footer from \"../shared/components/atoms/Footer\";\nimport Header from \"../shared/components/atoms/Header\";\nimport Application from \"../shared/components/layouts/Application\";\nimport { SharedApprovedCurrentUser } from \"../shared/lib/types\";\nimport useRequest from \"../shared/lib/useRequest\";\nimport StudyGroupForm, { RequestType } from \"./components/StudyGroupForm\";\n\nconst StudyGroupsNew = ({\n tags,\n flash,\n currentUser,\n}: {\n tags: SharedTag[];\n flash: Flash;\n currentUser: SharedApprovedCurrentUser;\n}) => {\n const request = useRequest();\n const onSubmit = async (formData: RequestType) => {\n const res = await request(studyGroupsPath(), \"POST\", formData);\n if (res.ok) {\n const json = await res.json();\n location.href = studyGroupPath(json.code);\n }\n };\n\n return (\n \n \n \n \n \n );\n};\n\nexport default StudyGroupsNew;\n","import dayjs from \"dayjs\";\nimport React from \"react\";\nimport { studyGroupPath } from \"../../../routes\";\nimport Footer from \"../shared/components/atoms/Footer\";\nimport Header from \"../shared/components/atoms/Header\";\nimport Application from \"../shared/components/layouts/Application\";\nimport useRequest from \"../shared/lib/useRequest\";\nimport StudyGroupForm, { RequestType } from \"./components/StudyGroupForm\";\nimport useFlash from \"../shared/lib/useFlash\";\nimport { SharedApprovedCurrentUser } from \"../shared/lib/types\";\nimport { Flash, SharedTag } from \"../../shared/lib/types\";\nimport { StudyGroupForEdit } from \"./lib/types\";\n\nconst StudyGroupsEdit = ({\n studyGroup,\n tags,\n flash,\n currentUser,\n}: {\n studyGroup: StudyGroupForEdit;\n tags: SharedTag[];\n flash: Flash;\n currentUser: SharedApprovedCurrentUser;\n}) => {\n const request = useRequest();\n const showFlash = useFlash();\n const onSubmit = async (formData: RequestType) => {\n const res = await request(studyGroupPath(studyGroup.code), \"PUT\", formData);\n if (res.ok) {\n location.href = studyGroupPath(studyGroup.code);\n } else if (res.status === 404) {\n showFlash({\n error: (\n <>\n 勉強会が見つかりませんでした。\n
\n ページをリロードしてください。\n >\n ),\n });\n }\n };\n\n return (\n \n \n tag.id.toString()),\n }}\n tags={tags}\n title=\"勉強会更新\"\n submitButtonLabel=\"勉強会を更新\"\n onSubmit={onSubmit}\n />\n \n \n );\n};\n\nexport default StudyGroupsEdit;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst ChevronLeftIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default ChevronLeftIcon;\n","import * as React from \"react\";\nimport usePagination from \"@mui/material/usePagination\";\nimport { Flex, Text, useBreakpointValue } from \"@chakra-ui/react\";\nimport Button from \"./Button\";\nimport { IconButton } from \"@chakra-ui/react\";\nimport ChevronLeftIcon from \"../icons/ChevronLeftIcon\";\nimport ChevronRightIcon from \"../icons/ChevronRightIcon\";\n\nconst Pagination = ({\n pageCount,\n currentPage,\n onClickPage,\n}: {\n pageCount: number;\n currentPage: number;\n onClickPage: (page: number) => void;\n}) => {\n const siblingCount = useBreakpointValue({ base: 0, sm: 1 });\n\n const { items } = usePagination({\n count: pageCount,\n page: currentPage,\n siblingCount,\n });\n\n return (\n \n );\n};\n\nexport default Pagination;\n","import React from \"react\";\nimport { Pagy } from \"../../../../shared/lib/types\";\nimport Pagination from \"./Pagination\";\n\nconst PagyPagination = ({ pagy }: { pagy: Pagy }) => {\n return (\n {\n const params = new URLSearchParams(location.search);\n params.set(\"page\", page.toString());\n location.search = params.toString();\n }}\n />\n );\n};\n\nexport default PagyPagination;\n","import { Box } from \"@chakra-ui/react\";\nimport React from \"react\";\n\nconst ServiceCaution = () => {\n return (\n \n 本サービスにおいて、個別の投稿内容やコメント内容等はあくまでも投稿者の個人的な見解となり、その内容の正確性を弊社が保証するものではありません。当該意見に依拠する際は、自己責任において利用するようにしてください。\n \n );\n};\n\nexport default ServiceCaution;\n","import { Tab, TabIndicator, TabList, Tabs } from \"@chakra-ui/react\";\nimport React, { useEffect, useState } from \"react\";\n\nconst PostOrderLinks = () => {\n const [currentIndex, setCurrentIndex] = useState();\n useEffect(() => {\n const search = new URLSearchParams(location.search);\n switch (search.get(\"order\")) {\n case null:\n case undefined:\n case \"\":\n setCurrentIndex(0);\n break;\n case \"comments\":\n setCurrentIndex(1);\n break;\n case \"views\":\n setCurrentIndex(2);\n break;\n }\n }, []);\n\n return (\n {}}>\n \n {[\n { label: \"新着順\", order: \"\" },\n { label: \"コメント数順\", order: \"comments\" },\n { label: \"閲覧数順\", order: \"views\" },\n ].map(({ label, order }) => {\n const href = new URL(location.href)\n href.searchParams.delete(\"order\")\n href.searchParams.delete(\"page\")\n href.searchParams.append(\"order\", order)\n return \n {label}\n \n })}\n \n \n \n );\n};\n\nexport default PostOrderLinks;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst SearchIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default SearchIcon;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst ArrowDropUpIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default ArrowDropUpIcon;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst ArrowDropDownIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default ArrowDropDownIcon;\n","import React from \"react\";\nimport { Box, Flex } from \"@chakra-ui/react\";\n\nconst CurrentSearchCondition = ({\n watchTags,\n watchOnlyBookmarked,\n tags,\n}: {\n watchTags: string[];\n watchOnlyBookmarked: string;\n tags: Array<{ id: number; name: string }>;\n}) => {\n const conditions = watchTags.map(\n (watchTagId) => tags.find((tag) => tag.id.toString() === watchTagId)?.name,\n );\n\n if (watchOnlyBookmarked === \"1\") {\n conditions.push(\"ブックマークした投稿のみ表示\");\n }\n\n return (\n <>\n {conditions.length > 0 && (\n \n \n 選択中の条件:\n \n {conditions.map((condition, index) => (\n \n {condition}\n {index !== conditions.length - 1 && \", \"}\n \n ))}\n \n \n \n )}\n >\n );\n};\n\nexport default CurrentSearchCondition;\n","import React, { useEffect, useState } from \"react\";\nimport {\n Box,\n CheckboxGroup,\n Flex,\n InputRightElement,\n Stack,\n Text,\n} from \"@chakra-ui/react\";\nimport { FormLabel, Input } from \"../../shared/components/atoms/form\";\nimport Checkbox from \"../../shared/components/atoms/form/Checkbox\";\nimport Button from \"../../shared/components/atoms/Button\";\nimport { Controller, useForm } from \"react-hook-form\";\nimport SearchIcon from \"../../shared/components/icons/SearchIcon\";\nimport ArrowDropUpIcon from \"../../shared/components/icons/ArrowDropUpIcon\";\nimport ArrowDropDownIcon from \"../../shared/components/icons/ArrowDropDownIcon\";\nimport CurrentSearchCondition from \"./CurrentSearchCondition\";\n\ntype SearchFormProps = {\n tags: Array<{ id: number; name: string }>;\n};\n\nconst SearchForm = ({ tags }: SearchFormProps) => {\n const { control, handleSubmit, setValue, watch } = useForm<{\n keyword: string;\n tags: string[];\n only_bookmarked: string;\n }>({\n defaultValues: {\n keyword: \"\",\n tags: [],\n only_bookmarked: \"0\",\n },\n });\n\n useEffect(() => {\n const params = new URLSearchParams(location.search);\n setValue(\"keyword\", params.get(\"q[title_or_content]\") ?? \"\");\n setValue(\"tags\", params.getAll(\"q[tags_id_eq_any][]\") ?? []);\n setValue(\"only_bookmarked\", params.get(\"only_bookmarked\") ?? \"0\");\n }, [setValue]);\n\n const onSubmit = (data: {\n keyword: string;\n tags: string[];\n only_bookmarked: string;\n }) => {\n const url = new URL(location.href)\n url.searchParams.delete(\"q[title_or_content]\")\n url.searchParams.delete(\"q[tags_id_eq_any][]\")\n url.searchParams.delete(\"only_bookmarked\")\n url.searchParams.delete(\"search\")\n url.searchParams.delete(\"page\")\n\n url.searchParams.append(\"q[title_or_content]\", data.keyword)\n url.searchParams.append(\"only_bookmarked\", data.only_bookmarked)\n\n data.tags.forEach((tag) => {\n url.searchParams.append(\"q[tags_id_eq_any][]\", tag)\n });\n url.searchParams.append(\"search\", \"1\")\n\n location.href = url.toString();\n };\n\n const [isDetailOpen, setIsDetailOpen] = useState(false);\n\n const [watchTags, watchOnlyBookmarked] = watch([\"tags\", \"only_bookmarked\"]);\n\n return (\n \n (\n (\n \n \n \n \n \n )}\n {...field}\n />\n )}\n />\n : }\n iconSpacing={0.5}\n borderRadius={2}\n py={1}\n pl={1}\n pr={2}\n onClick={() => setIsDetailOpen((prev) => !prev)}\n >\n \n 詳細検索\n \n \n {isDetailOpen && (\n \n \n \n 詳細検索\n setIsDetailOpen(false)}\n >\n 閉じる\n \n \n \n タグ\n \n (\n \n {tags.map(({ id, name }) => (\n \n {name}\n \n ))}\n \n )}\n />\n \n \n (\n \n ブックマーク\n \n {\n field.onChange(e.target.checked ? \"1\" : \"0\");\n }}\n >\n ブックマーク済みの投稿のみ表示\n \n \n \n )}\n />\n )\n \n \n \n \n \n )}\n \n \n );\n};\n\nexport default SearchForm;\n","import { ButtonGroup } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport { Button } from \"../../shared/components/atoms\";\nimport { SharedCurrentUser } from \"../../shared/lib/types\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport { currentPostThemePath } from \"../../../../routes\";\nimport useFlash from \"../../shared/lib/useFlash\";\n\nconst ChangeThemeButtons = ({\n currentUser,\n}: {\n currentUser: SharedCurrentUser;\n}) => {\n const theme =\n currentUser?.current_post_theme_name ??\n localStorage.getItem(\"current_post_theme_name\") ??\n \"detail\";\n\n const compactButtonVariant = theme === \"compact\" ? \"solid\" : \"outline\";\n const detailButtonVariant = theme === \"detail\" ? \"solid\" : \"outline\";\n const request = useRequest();\n const showFlash = useFlash();\n\n const createButtonOnClick = (themeName: string) => {\n if (theme === themeName) return undefined;\n\n if (currentUser != null) {\n return async () => {\n const res = await request(currentPostThemePath(), \"PUT\", {\n name: themeName,\n });\n if (res.ok) {\n location.reload();\n } else {\n showFlash({ error: \"もう一度お試しください\" });\n }\n };\n } else {\n return () => {\n localStorage.setItem(\"current_post_theme_name\", themeName);\n location.reload();\n };\n }\n };\n\n return (\n \n \n \n \n );\n};\n\nexport default ChangeThemeButtons;\n","import {\n Box,\n BoxProps,\n CloseButton,\n Flex,\n Grid,\n GridItem,\n Image,\n Show,\n} from \"@chakra-ui/react\";\nimport React, { useEffect, useState } from \"react\";\nimport dummyPng from \"../assets/dummy.png\";\n\nconst EventBanner = (props?: BoxProps) => {\n const [isShow, setIsShow] = useState(false);\n\n const closeButtonStyle = {\n border: \"2px solid #41AA92\",\n bgColor: \"white\",\n color: \"primary\",\n };\n\n useEffect(() => {\n const showEventModal =\n (window.sessionStorage.getItem(\"show-event-popup\") ?? \"1\") == \"1\";\n setIsShow(showEventModal);\n }, []);\n\n return (\n <>\n {isShow && (\n \n \n \n {\n setIsShow(false);\n window.sessionStorage.setItem(\"show-event-popup\", \"0\");\n e.stopPropagation();\n }}\n />\n \n \n \n \n \n \n \n \n \n )}\n >\n );\n};\n\nexport default EventBanner;\n","import { Box, Container, Flex, Show, Stack } from \"@chakra-ui/react\";\nimport React, { useContext, useEffect, useRef, useState } from \"react\";\nimport { newPostPath } from \"../../../routes\";\nimport { FlipperContext } from \"../../shared/lib/FlipperContext\";\nimport { Flash, Pagy, SharedTag } from \"../../shared/lib/types\";\nimport Background from \"../shared/components/atoms/Background\";\nimport Footer from \"../shared/components/atoms/Footer\";\nimport Header from \"../shared/components/atoms/Header\";\nimport HelpMessage from \"../shared/components/atoms/HelpMessage\";\nimport PagyPagination from \"../shared/components/atoms/PagyPagination\";\nimport PostSummaryCard from \"../shared/components/atoms/PostSummaryCard\";\nimport ServiceCaution from \"../shared/components/atoms/ServiceCaution\";\nimport AddIcon from \"../shared/components/icons/AddIcon\";\nimport Application from \"../shared/components/layouts/Application\";\nimport { SharedCurrentUser } from \"../shared/lib/types\";\nimport PostOrderLinks from \"./components/PostsOrderLinks\";\nimport SearchForm from \"./components/SearchForm\";\nimport ChangeThemeButtons from \"./components/ChangeThemeButtons\";\nimport { Posts } from \"./lib/types\";\nimport EventBanner from \"./components/EventBanner\";\nimport bannerSp from \"./assets/dummy-sp.png\";\n\nconst PostsIndex = ({\n posts,\n tags,\n flash,\n pagy,\n currentUser,\n}: {\n posts: Posts;\n tags: SharedTag[];\n flash: Flash;\n pagy: Pagy;\n currentUser: SharedCurrentUser;\n}) => {\n const [keyword, setKeyword] = useState(\"\");\n\n useEffect(() => {\n const params = new URLSearchParams(location.search);\n setKeyword(params.get(\"q[title_or_content]\") ?? \"\");\n }, []);\n\n const ref = useRef(null);\n const flipper = useContext(FlipperContext);\n\n useEffect(() => {\n const url = new URL(location.href);\n let newDesignValue = null;\n if (currentUser != null) {\n newDesignValue =\n currentUser.current_post_theme_name === \"compact\" ? \"1\" : \"2\";\n } else {\n newDesignValue =\n localStorage.getItem(\"current_post_theme_name\") === \"compact\"\n ? \"1\"\n : \"2\";\n }\n url.searchParams.set(\"new_design\", newDesignValue);\n\n window.history.replaceState(null, \"\", url.toString());\n }, [currentUser, flipper]);\n\n useEffect(() => {\n const params = new URLSearchParams(location.search);\n if (params.get(\"search\") == \"1\" && ref.current) {\n ref.current.scrollIntoView({ block: \"start\" });\n window.scrollTo(0, window.scrollY - 84);\n }\n }, [flipper]);\n\n return (\n \n \n {flipper.event_feature && (\n \n \n \n \n \n )}\n \n \n \n \n \n \n Q&A投稿方法\n \n \n \n \n \n \n \n \n \n \n \n \n {posts.map((post) => (\n \n ))}\n \n\n \n \n \n \n \n \n \n {flipper.event_feature && }\n \n \n \n \n \n 1}\n float=\"right\"\n >\n \n \n \n \n \n \n \n \n );\n};\n\nexport default PostsIndex;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst ExpandMoreIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default ExpandMoreIcon;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst DescriptionIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default DescriptionIcon;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst DownloadIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default DownloadIcon;\n","import {\n As,\n CloseButton,\n HStack,\n List,\n ListItem,\n Text,\n} from \"@chakra-ui/react\";\nimport React from \"react\";\nimport DescriptionIcon from \"../icons/DescriptionIcon\";\nimport DownloadIcon from \"../icons/DownloadIcon\";\nimport { HelperNotImageFile } from \"../../../../shared/lib/types\";\n\nconst FilesWithoutImage = ({\n files,\n downloadable = true,\n onRemove = undefined,\n}: {\n files: Array;\n downloadable?: boolean;\n onRemove?: (index: number) => void;\n}) => {\n if (files.length === 0) return;\n\n const listItemProps = (file: HelperNotImageFile | File) =>\n downloadable\n ? {\n as: \"a\" as As,\n href: (file as HelperNotImageFile).url,\n download: file.name,\n }\n : {};\n\n return (\n \n {files.map((file, idx) => (\n \n \n \n \n {file.name}\n \n {downloadable && }\n {onRemove && onRemove(idx)} />}\n \n \n ))}\n
\n );\n};\n\nexport default FilesWithoutImage;\n","import {\n Box,\n CloseButton,\n Flex,\n Grid,\n Image,\n Modal,\n ModalContent,\n ModalOverlay,\n useDisclosure,\n} from \"@chakra-ui/react\";\nimport React, { ReactNode, useEffect, useState } from \"react\";\nimport { Swiper, SwiperSlide } from \"swiper/react\";\nimport { Swiper as TSwiper } from \"swiper/types\";\nimport \"swiper/css\";\nimport ChevronLeftIcon from \"../icons/ChevronLeftIcon\";\nimport ChevronRightIcon from \"../icons/ChevronRightIcon\";\n\nconst SliderButton = ({\n onClick,\n children,\n}: {\n onClick: () => void;\n children: ReactNode;\n}) => {\n return (\n \n {children}\n \n );\n};\n\nconst ImagesWithSlider = ({\n urls,\n onRemove = undefined,\n}: {\n urls: string[];\n onRemove?: (idx: number) => void;\n}) => {\n const { isOpen: isOpenSlider, onOpen, onClose } = useDisclosure();\n const [initialIndex, setInitialIndex] = useState();\n\n const onOpenSlider = (index: number) => {\n setInitialIndex(index);\n onOpen();\n };\n\n const onCloseSlider = () => {\n onClose();\n setInitialIndex(undefined);\n };\n\n const [swiper, setSwiper] = useState();\n const [activeIndex, setActiveIndex] = useState(0);\n\n useEffect(() => {\n if (swiper != null && initialIndex != null) {\n swiper.slideTo(initialIndex, 0);\n }\n }, [initialIndex, swiper]);\n\n const PrevButton = () => {\n return (\n \n {activeIndex > 0 && (\n swiper?.slidePrev()}>\n \n \n )}\n \n );\n };\n\n const NextButton = () => {\n return (\n \n {activeIndex < urls.length - 1 && (\n swiper?.slideNext()}>\n \n \n )}\n \n );\n };\n\n if (urls.length == 0) return <>>;\n\n return (\n <>\n \n {urls.map((url, idx) => (\n \n {onRemove && (\n onRemove(idx)}\n />\n )}\n onOpenSlider(idx)}\n objectFit=\"cover\"\n loading=\"lazy\"\n aspectRatio={1 / 1}\n />\n \n ))}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n 閉じる\n \n \n \n setSwiper(s)}\n onSlideChange={(s) => setActiveIndex(s.activeIndex)}\n >\n {urls.map((url) => (\n \n \n \n \n \n ))}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n >\n );\n};\n\nexport default ImagesWithSlider;\n","import { Box } from \"@chakra-ui/react\";\nimport React, { useRef } from \"react\";\n\nconst FileUploadButton = ({\n onChange,\n children,\n multiple = false,\n name,\n}: {\n onChange: (e: File[]) => void;\n children: React.ReactNode;\n multiple?: boolean;\n name: string;\n}) => {\n const ref = useRef(null);\n\n return (\n \n );\n};\n\nexport default FileUploadButton;\n","import React, { useCallback, useState } from \"react\";\nimport useFlash from \"../../public/shared/lib/useFlash\";\nimport FilesWithoutImage from \"../../public/shared/components/atoms/FilesWithoutImage\";\nimport ImagesWithSlider from \"../../public/shared/components/atoms/ImagesWithSlider\";\nimport { Box } from \"@chakra-ui/react\";\nimport { HelperImageFile, HelperNotImageFile } from \"../lib/types\";\n\nexport type FileType = File | HelperImageFile | HelperNotImageFile;\nexport type UploadFileType = Array;\n\nexport const useUploadFiles = (\n defaultImageFiles: Array<{\n url: string;\n signed_id: string;\n }> = [],\n defaultFilesWithoutImage: Array<{\n name: string;\n url: string;\n signed_id: string;\n }> = [],\n maxLength: number = 5,\n onChange?: (files: FileType[]) => void,\n) => {\n const [isChange, setIsChange] = useState(false);\n const [files, setFiles] = useState>([\n ...defaultImageFiles.map((file) => {\n return { ...file, is_image: true };\n }),\n ...defaultFilesWithoutImage.map((file) => {\n return { ...file, is_image: false };\n }),\n ]);\n const flash = useFlash();\n\n const addFiles = useCallback(\n (addedFiles: File[]) =>\n setFiles((prev) => {\n if ([...prev, ...addedFiles].length > maxLength) {\n flash({\n error: `ファイルは${maxLength}つ以下になるように選択してください`,\n });\n return prev;\n }\n if (\n addedFiles.some(\n (uploadFile) =>\n uploadFile.size != null && uploadFile.size >= 5 * 1000 * 1000,\n )\n ) {\n flash({\n error: \"ファイルサイズの上限は5MBです\",\n });\n return prev;\n }\n setIsChange(true);\n const result = [...prev, ...addedFiles];\n onChange?.(result);\n return result;\n }),\n [flash, maxLength, onChange],\n );\n\n const removeFile = (removedFile: FileType) => {\n setIsChange(true);\n setFiles((prev) => {\n const result = prev.filter((file) => file !== removedFile);\n onChange?.(result);\n return result;\n });\n };\n\n const clear = () => {\n setIsChange(false);\n onChange?.([]);\n setFiles([]);\n };\n\n return [files, addFiles, removeFile, clear, isChange] as [\n typeof files,\n typeof addFiles,\n typeof removeFile,\n typeof clear,\n typeof isChange,\n ];\n};\n\nconst isImage = (file: FileType & { is_image?: boolean }) =>\n file.is_image ||\n [\"image/jpeg\", \"image/png\", \"image/gif\"].includes((file as File).type);\n\nexport const filterImageFiles = (uploadFiles: FileType[]) => {\n return uploadFiles.filter((file) => isImage(file)) as Array<\n File | HelperImageFile\n >;\n};\n\nexport const filterFilesWithoutImage = (uploadFiles: FileType[]) => {\n return uploadFiles.filter((file) => !isImage(file)) as Array<\n File | HelperNotImageFile\n >;\n};\n\nconst UploadFiles = ({\n uploadFiles,\n removeFile = undefined,\n}: {\n uploadFiles: FileType[];\n removeFile?: (file: FileType) => void;\n}) => {\n const imageFiles = filterImageFiles(uploadFiles);\n const filesWithoutImage = filterFilesWithoutImage(uploadFiles);\n\n return (\n <>\n {filesWithoutImage.length !== 0 && (\n {\n removeFile(filesWithoutImage[idx]);\n }\n : undefined\n }\n />\n )}\n {imageFiles.length !== 0 && (\n \n \n (file as HelperImageFile).url ??\n URL.createObjectURL(file as File),\n )}\n onRemove={\n removeFile\n ? (idx) => {\n removeFile(imageFiles[idx]);\n }\n : undefined\n }\n />\n \n )}\n >\n );\n};\n\nexport default UploadFiles;\n","import * as yup from \"yup\";\n\nexport const anonymousSchema = (showAnonymousField: boolean) =>\n yup\n .string()\n .when([], {\n is: () => showAnonymousField,\n then: (schema) => schema.required(),\n })\n .label(\"質問者の表示名\");\n","import {\n Box,\n Flex,\n Grid,\n HStack,\n Modal,\n ModalBody,\n ModalCloseButton,\n ModalContent,\n ModalFooter,\n ModalHeader,\n ModalOverlay,\n Popover,\n PopoverArrow,\n PopoverBody,\n PopoverContent,\n PopoverTrigger,\n Show,\n Stack,\n Text,\n} from \"@chakra-ui/react\";\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport React, { ReactNode, useContext } from \"react\";\nimport { Controller, useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport { Button } from \"../../shared/components/atoms\";\nimport { Textarea } from \"../../shared/components/atoms/form\";\nimport { SharedCurrentUser } from \"../../shared/lib/types\";\nimport FileUploadButton from \"../../../shared/components/FileUploadButton\";\nimport PostResourceAvatar from \"../../shared/components/atoms/PostResourceAvatar\";\nimport PostResourceDisplayName from \"../../shared/components/atoms/PostResourceDisplayName\";\nimport AddIcon from \"../../shared/components/icons/AddIcon\";\nimport UploadFiles, {\n useUploadFiles,\n} from \"../../../shared/components/UploadFiles\";\nimport { anonymousSchema } from \"../lib/schema\";\nimport { HelperImageFile, HelperNotImageFile } from \"../../../shared/lib/types\";\nimport HelpMessage from \"../../shared/components/atoms/HelpMessage\";\nimport { uploadFile } from \"../../../shared/lib/uploadFile\";\nimport { FlipperContext } from \"../../../shared/lib/FlipperContext\";\n\nexport const useCommentForm = (\n props: Omit,\n) => {\n return useCommentOrReplyForm({ ...props, name: \"post_comment\" });\n};\n\nexport const CommentFormFields = (\n props: Omit,\n) => {\n const flipper = useContext(FlipperContext);\n return (\n \n );\n};\n\nexport const useReplyForm = (\n props: Omit,\n) => {\n return useCommentOrReplyForm({ ...props, name: \"post_comment_reply\" });\n};\n\nexport const ReplyFormFields = (\n props: Omit,\n) => {\n return (\n \n );\n};\n\nexport const EditModal = ({\n title,\n isOpen,\n onClose,\n formState,\n children,\n}: {\n title: string;\n isOpen: boolean;\n onClose: () => void;\n formState: ReturnType;\n children: ReactNode;\n}) => {\n const ModalCancelButton = () => {\n return (\n \n \n \n );\n };\n\n const ModalSubmitButton = () => {\n return (\n \n );\n };\n return (\n \n \n \n {title}\n \n\n \n {children}\n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n );\n};\n\ntype useCommentOrReplyFormProps = {\n onSubmit: (data: FormData) => void;\n defaultValues?: { content?: string; anonymous?: string };\n defaultImageFiles?: HelperImageFile[];\n defaultFilesWithoutImage?: HelperNotImageFile[];\n hasAnonymousField?: boolean;\n currentUser?: SharedCurrentUser;\n name: string;\n};\n\nconst useCommentOrReplyForm = ({\n onSubmit: propOnSubmit,\n defaultValues: propDefaultValues = { content: \"\", anonymous: \"\" },\n defaultImageFiles = [],\n defaultFilesWithoutImage = [],\n hasAnonymousField = false,\n name,\n}: useCommentOrReplyFormProps) => {\n const defaultValues = {\n content: propDefaultValues.content ?? \"\",\n anonymous: propDefaultValues.anonymous ?? \"\",\n };\n const uploadFilesState = useUploadFiles(\n defaultImageFiles,\n defaultFilesWithoutImage,\n );\n const [uploadFiles, , , clear, isUploadFilesChange] = uploadFilesState;\n\n const schema = yup.object({\n content: yup.string().trim().required().max(65535),\n anonymous: anonymousSchema(hasAnonymousField),\n });\n\n const methods = useForm({\n defaultValues,\n resolver: yupResolver(schema),\n mode: \"onChange\",\n });\n\n const {\n handleSubmit,\n reset,\n formState: { isDirty, isValid, isSubmitting },\n } = methods;\n\n const onSubmit = async (data: yup.InferType) => {\n const formData = new FormData();\n formData.append(`${name}[content]`, data.content);\n if (hasAnonymousField && data.anonymous) {\n formData.append(`${name}[anonymous]`, data.anonymous);\n }\n if (uploadFiles.length !== 0) {\n for (const file of uploadFiles) {\n if (\"signed_id\" in file) {\n formData.append(`${name}[files][]`, file.signed_id);\n } else {\n const { blob } = await uploadFile({ file });\n formData.append(`${name}[files][]`, blob.signed_id);\n }\n }\n } else {\n formData.append(`${name}[files][]`, \"\");\n }\n\n propOnSubmit(formData);\n\n clear();\n reset({ content: \"\", anonymous: data.anonymous });\n };\n const isSubmittable = isValid && (isDirty || isUploadFilesChange);\n\n return {\n methods,\n uploadFilesState,\n onSubmit: handleSubmit(onSubmit),\n isSubmittable,\n isSubmitting,\n hasAnonymousField,\n };\n};\n\ntype CommentOrReplyFormFieldsProps = {\n formState: ReturnType;\n name: string;\n currentUser: SharedCurrentUser;\n anonymous: boolean;\n placeholder?: string;\n autoFocus?: boolean;\n};\n\nconst CommentOrReplyFormFields = ({\n formState,\n name,\n currentUser,\n anonymous,\n placeholder = \"\",\n autoFocus = false,\n}: CommentOrReplyFormFieldsProps) => {\n const [uploadFiles, addUploadFiles, removeUploadFile] =\n formState.uploadFilesState;\n\n const { control } = formState.methods;\n\n return (\n <>\n \n \n {currentUser?.is_all_public_feature_accessible && (\n \n )}\n \n {currentUser?.is_all_public_feature_accessible &&\n (anonymous ? (\n \n ) : (\n \n ))}\n \n \n (\n \n )}\n />\n \n \n \n \n \n \n ファイル添付\n \n \n \n \n {uploadFiles.length > 0 && (\n \n \n \n プレビュー表示にならないファイル\n \n \n \n \n \n \n jpeg,jpg,png,gifの形式のファイルのみプレビュー表示され、それ以外はダウンロード形式になります。\n \n \n \n \n )}\n \n \n \n >\n );\n};\n","import {\n Box,\n ButtonProps,\n List,\n ListItem,\n ListItemProps,\n Popover,\n PopoverBody,\n PopoverContent,\n PopoverTrigger,\n} from \"@chakra-ui/react\";\nimport React from \"react\";\nimport MoreHorizIcon from \"../icons/MoreHorizIcon\";\n\nconst MoreMenu = ({\n buttonProps,\n listItemProps,\n listItemText,\n}: {\n buttonProps: ButtonProps & { \"data-testid\"?: string };\n listItemProps: ListItemProps & { href?: string };\n listItemText: string;\n}) => {\n return (\n \n \n \n \n \n \n \n \n \n \n {listItemText}\n \n
\n \n \n \n );\n};\n\nexport default MoreMenu;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst ExpandLessIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default ExpandLessIcon;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst TreeCurveIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default TreeCurveIcon;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst TreeJunctionIcon = (props: IconProps) => (\n \n \n \n \n);\n\nexport default TreeJunctionIcon;\n","import { Box, HStack, RadioGroup, Stack } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport \"react-datepicker/dist/react-datepicker.css\";\nimport { Control, Controller } from \"react-hook-form\";\nimport { SharedApprovedCurrentUser } from \"../../shared/lib/types\";\nimport PostResourceAvatar, {\n PostResourceAvatarProps,\n} from \"../../shared/components/atoms/PostResourceAvatar\";\nimport { FormLabel, InputError, Radio } from \"../../shared/components/atoms/form\";\n\nconst AvatarPreview = ({\n author,\n}: {\n author: PostResourceAvatarProps[\"author\"];\n}) => {\n return (\n \n 表示用プレビュー\n \n \n {author.display_name}\n \n \n );\n};\n\nconst AnonymousField = ({\n control,\n anonymous,\n currentUser,\n label,\n showPreview = true,\n}: {\n control: Control;\n anonymous: string;\n currentUser: SharedApprovedCurrentUser;\n label: string;\n showPreview?: boolean;\n}) => {\n return (\n \n {label}\n (\n \n \n \n \n 実名\n \n \n ニックネーム\n \n \n \n {fieldState.error != null && (\n {fieldState.error.message}\n )}\n \n )}\n />\n {anonymous === \"\" ? (\n <>>\n ) : showPreview && anonymous === \"0\" ? (\n \n ) : showPreview && anonymous === \"1\" ? (\n \n ) : (\n <>>\n )}\n \n );\n};\n\nexport default AnonymousField;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst ThumbUpIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default ThumbUpIcon;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst ThumbUpFilledIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default ThumbUpFilledIcon;\n","import React, { useState } from \"react\";\nimport { Box, Flex, Text } from \"@chakra-ui/react\";\nimport ThumbUpIcon from \"../../../shared/components/icons/ThumbUpIcon\";\nimport ThumbUpFilledIcon from \"../../../shared/components/icons/ThumbUpFilledIcon\";\nimport { motion, useAnimation } from \"framer-motion\";\n\ntype ThumbUpButtonWithCountProps = {\n count: number;\n isActive: boolean;\n onActive: () => Promise;\n onInactive: () => Promise;\n isDisabled?: boolean;\n};\n\nconst ThumbUpButtonWithCount = ({\n count: propCount,\n isActive: propIsActive,\n onActive,\n onInactive,\n isDisabled = false,\n}: ThumbUpButtonWithCountProps) => {\n const [count, setCount] = useState(propCount);\n const [isActive, setIsActive] = useState(propIsActive);\n const [inProgress, setInProgress] = useState(false);\n const toggleActive = async () => {\n setInProgress(true);\n if (!isActive) {\n await onActive();\n setCount(count + 1);\n setIsActive(true);\n await controls.start({\n scale: [1, 1.3, 1.6, 1.3, 1],\n rotate: [\"-5deg\", \"0deg\", \"10deg\", \"-10deg\", \"10deg\", \"-10deg\", \"0deg\"],\n });\n } else {\n setCount(count - 1);\n setIsActive(false);\n await onInactive();\n }\n setInProgress(false);\n };\n const controls = useAnimation();\n\n return (\n \n \n {isActive ? (\n \n ) : (\n \n )}\n \n {count > 0 && (\n \n {count}\n \n )}\n \n 参考になった\n \n \n );\n};\n\nexport default ThumbUpButtonWithCount;\n","import React from \"react\";\nimport useFlash from \"../../shared/lib/useFlash\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport ThumbUpButtonWithCount from \"./ThumUpButton/ThumbUpButtonWithCount\";\nimport { PostComment, UserPostCommentReply } from \"../lib/types\";\n\nconst UsefulButton = ({\n postChild,\n path,\n isDisabled = false,\n}: {\n postChild: PostComment | UserPostCommentReply;\n path: string;\n isDisabled?: boolean;\n}) => {\n const request = useRequest();\n const showFlash = useFlash();\n const showReplyNotFoundFlash = () => {\n showFlash({\n error: (\n <>\n 参考になったをしようとしたコメント、返信が見つかりませんでした。\n
\n ページを再読み込みしてください。\n >\n ),\n });\n };\n\n return (\n {\n const res = await request(path, \"PUT\", {});\n if (res.status === 404) {\n showReplyNotFoundFlash();\n }\n }}\n onInactive={async () => {\n const res = await request(path, \"DELETE\", {});\n if (res.status === 404) {\n showReplyNotFoundFlash();\n }\n }}\n isDisabled={isDisabled}\n />\n );\n};\n\nexport default UsefulButton;\n","import { Link, LinkProps, forwardRef } from \"@chakra-ui/react\";\nimport React, { ReactNode } from \"react\";\nimport { isVisitableUserProfile } from \"../../shared/lib/userProfileUtils\";\nimport { userPath } from \"../../../../routes\";\nimport {\n SharedCurrentUser,\n HelperPostResourceAuthor,\n HelperRealNamePostResourceAuthor,\n} from \"../../shared/lib/types\";\n\nconst UserProfileLink = forwardRef(\n (\n {\n author,\n currentUser,\n children,\n ...props\n }: {\n author: HelperPostResourceAuthor;\n currentUser: SharedCurrentUser;\n children: ReactNode;\n } & LinkProps,\n ref,\n ) => {\n return (\n \n {children}\n \n );\n },\n);\n\nexport default UserProfileLink;\n","import {\n Box,\n Center,\n Divider,\n Flex,\n Link,\n SlideFade,\n Spinner,\n Stack,\n Text,\n forwardRef,\n useDisclosure,\n} from \"@chakra-ui/react\";\nimport dayjs from \"dayjs\";\nimport React, { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n postCommentRepliesPath,\n postCommentReplyPath,\n postCommentReplyUsefulPath,\n} from \"../../../../routes\";\nimport CustomLinkLinkify from \"../../shared/components/atoms/CustomLinkLinkify\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport { SharedApprovedCurrentUser } from \"../../shared/lib/types\";\nimport { Button } from \"../../shared/components/atoms\";\nimport PostResourceAvatar from \"../../shared/components/atoms/PostResourceAvatar\";\nimport PostResourceDisplayName from \"../../shared/components/atoms/PostResourceDisplayName\";\nimport ExpandLessIcon from \"../../shared/components/icons/ExpandLessIcon\";\nimport TreeCurveIcon from \"../../shared/components/icons/TreeCurveIcon\";\nimport TreeJunctionIcon from \"../../shared/components/icons/TreeJunctionIcon\";\nimport AnonymousField from \"./AnonymousField\";\nimport FilesWithoutImage from \"../../shared/components/atoms/FilesWithoutImage\";\nimport ImagesWithSlider from \"../../shared/components/atoms/ImagesWithSlider\";\nimport { EditModal, ReplyFormFields, useReplyForm } from \"./CommentReplyForm\";\nimport MoreMenu from \"../../shared/components/atoms/MoreMenu\";\nimport UsefulButton from \"./UsefulButton\";\nimport UserProfileLink from \"./UserProfileLink\";\nimport { HelperImageFile } from \"../../../shared/lib/types\";\nimport useFlash from \"../../shared/lib/useFlash\";\nimport {\n PostComment,\n PostCommentRepliesIndex,\n PostsShowProps,\n UserPostCommentReply,\n} from \"../lib/types\";\n\nconst ReplyWrapper = ({\n reply: propReply,\n currentUser,\n post,\n}: {\n reply: UserPostCommentReply;\n currentUser: SharedApprovedCurrentUser;\n post: PostsShowProps;\n}) => {\n const [reply, setReply] = useState(propReply);\n return (\n \n );\n};\n\nconst EditReplyModal = ({\n reply,\n onUpdated,\n editModalDisclosure,\n currentUser,\n}: {\n reply: UserPostCommentReply;\n onUpdated: (data: UserPostCommentReply) => void;\n editModalDisclosure: ReturnType;\n currentUser: SharedApprovedCurrentUser;\n}) => {\n const request = useRequest();\n const showFlash = useFlash();\n\n const editReplyFormState = useReplyForm({\n onSubmit: async (data) => {\n const res = await request(postCommentReplyPath(reply.code), \"PUT\", data);\n if (res.ok) {\n onUpdated(await res.json());\n } else if (res.status === 404) {\n showFlash({\n error: (\n <>\n 返信が見つかりませんでした。\n
\n ページを再読み込みしてください。\n >\n ),\n });\n }\n },\n defaultValues: { content: reply.content },\n defaultImageFiles: reply.image_files,\n defaultFilesWithoutImage: reply.files_without_image,\n });\n\n return (\n \n \n \n );\n};\n\nconst Reply = ({\n reply,\n currentUser,\n onUpdated,\n post,\n}: {\n reply: UserPostCommentReply;\n currentUser: SharedApprovedCurrentUser;\n onUpdated: (reply: UserPostCommentReply) => void;\n post: PostsShowProps;\n}) => {\n const editModalDisclosure = useDisclosure();\n const ref = useRef(null);\n useEffect(() => {\n if (ref.current == null) return;\n\n const search = new URLSearchParams(location.search);\n if (reply.code == search.get(\"reply_code\")) {\n ref.current.scrollIntoView({ block: \"start\" });\n // ピッタリはみづらいので微調整\n window.scrollTo(0, window.scrollY - 24);\n }\n // 初回のみ実行したいので\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return (\n \n \n \n \n \n \n \n \n \n \n {!post.is_resolved && reply.is_own && (\n \n )}\n \n \n \n \n 投稿日:{dayjs(reply.created_at).format(\"L LT\")}\n \n {reply.updated_at !== \"\" &&\n reply.created_at !== reply.updated_at && (\n \n 最終更新日:{dayjs(reply.updated_at).format(\"L LT\")}\n \n )}\n \n \n {reply.content}\n \n {reply.files_without_image.length !== 0 && (\n \n \n \n )}\n \n {reply.image_files.length !== 0 && (\n \n file.url,\n )}\n />\n \n )}\n \n \n \n \n \n \n \n );\n};\n\nconst NewReplyForm = forwardRef(\n (\n {\n comment,\n fetchReplies,\n currentUser,\n post,\n onSubmit,\n }: {\n comment: PostComment;\n fetchReplies: (comment_code: string) => void;\n currentUser: SharedApprovedCurrentUser;\n post: PostsShowProps;\n onSubmit?: () => void;\n },\n ref,\n ) => {\n const request = useRequest();\n const showFlash = useFlash();\n\n const newReplyFormState = useReplyForm({\n onSubmit: async (data) => {\n const res = await request(\n postCommentRepliesPath(comment.code),\n \"POST\",\n data,\n );\n if (res.ok) {\n if (onSubmit) onSubmit();\n fetchReplies(comment.code);\n } else if (res.status === 404) {\n showFlash({\n error: (\n <>\n コメントが見つかりませんでした。\n
\n ページを再読み込みしてください。\n >\n ),\n });\n }\n },\n hasAnonymousField: currentUser?.is_anonymousable,\n defaultValues: {\n content: \"\",\n anonymous: post.previous_post_anonymous,\n },\n });\n\n const anonymous = newReplyFormState.methods.watch(\"anonymous\");\n\n return (\n \n {currentUser?.is_anonymousable && (\n \n )}\n \n \n \n \n \n );\n },\n);\n\nconst Replies = ({\n comment,\n currentUser,\n onFetched,\n onClose,\n isFocusInput,\n post,\n}: {\n comment: PostComment;\n currentUser: SharedApprovedCurrentUser;\n onFetched: (result: PostCommentRepliesIndex) => void;\n onClose: () => void;\n isFocusInput: boolean;\n post: PostsShowProps;\n}) => {\n // TODO: dev storybookのためなのでmockして消したい\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-expect-error\n const [replies, setReplies] = useState(comment.replies);\n\n const request = useRequest();\n const fetchReplies = useCallback(\n async (comment_code: string) => {\n const res = await request(postCommentRepliesPath(comment_code), \"GET\");\n const result = await res.json();\n setReplies(result.replies);\n onFetched(result);\n },\n [request, onFetched],\n );\n\n useEffect(() => {\n void fetchReplies(comment.code);\n }, [fetchReplies, comment.code]);\n\n const ref = useRef();\n const endOfRepliesRef = useRef(null);\n\n // 「返信する」を押した時のみ実行して欲しいが、repliesの変更を追う必要があるので\n // 返信の追加時にも発火してしまう\n // stateで1回実行したかどうかを保持しておく\n const [firstScroll, setFirstScroll] = useState(false);\n useEffect(() => {\n if (!firstScroll && replies && isFocusInput && ref.current) {\n setFirstScroll(true);\n ref.current.scrollIntoView({ behavior: \"smooth\", block: \"center\" });\n }\n }, [replies, isFocusInput, firstScroll]);\n\n return (\n <>\n \n {!replies ? (\n \n \n \n ) : (\n [...replies].reverse().map((reply, i) => (\n \n \n {post.is_resolved && i == replies.length - 1 ? (\n \n ) : (\n <>\n \n \n \n \n >\n )}\n \n \n \n \n \n ))\n )}\n \n {post.is_resolved ? (\n \n 解決済みの質問のため、\n \n 返信できません\n \n ) : (\n \n \n {\n endOfRepliesRef.current?.scrollIntoView({ behavior: \"smooth\" });\n }}\n />\n \n )}\n \n {\n onClose();\n }}\n >\n \n 返信一覧を閉じる\n \n \n \n >\n );\n};\n\nexport default Replies;\n","import {\n Box,\n Center,\n Divider,\n Flex,\n HStack,\n Link,\n SlideFade,\n Stack,\n useDisclosure,\n} from \"@chakra-ui/react\";\nimport dayjs from \"dayjs\";\nimport React, { useEffect, useRef, useState } from \"react\";\nimport { postCommentPath, postCommentUsefulPath } from \"../../../../routes\";\nimport { HelperImageFile } from \"../../../shared/lib/types\";\nimport CustomLinkLinkify from \"../../shared/components/atoms/CustomLinkLinkify\";\nimport useFlash from \"../../shared/lib/useFlash\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport { SharedCurrentUser } from \"../../shared/lib/types\";\nimport PostResourceAvatar from \"../../shared/components/atoms/PostResourceAvatar\";\nimport PostResourceDisplayName from \"../../shared/components/atoms/PostResourceDisplayName\";\nimport ExpandMoreIcon from \"../../shared/components/icons/ExpandMoreIcon\";\nimport FilesWithoutImage from \"../../shared/components/atoms/FilesWithoutImage\";\nimport ImagesWithSlider from \"../../shared/components/atoms/ImagesWithSlider\";\nimport {\n CommentFormFields,\n EditModal,\n useCommentForm,\n} from \"./CommentReplyForm\";\nimport MoreMenu from \"../../shared/components/atoms/MoreMenu\";\nimport Replies from \"./Replies\";\nimport UsefulButton from \"./UsefulButton\";\nimport UserProfileLink from \"./UserProfileLink\";\nimport { PostComment, PostsShowProps } from \"../lib/types\";\n\nconst CommentEditModal = ({\n comment,\n onUpdated,\n disclosure,\n currentUser,\n}: {\n comment: PostComment;\n onUpdated: (comment: PostComment) => void;\n disclosure: ReturnType;\n currentUser: SharedCurrentUser;\n}) => {\n const request = useRequest();\n const showFlash = useFlash();\n\n const commentFormState = useCommentForm({\n onSubmit: async (data) => {\n const res = await request(postCommentPath(comment.code), \"PUT\", data);\n if (res.ok) {\n onUpdated(await res.json());\n } else if (res.status === 404) {\n showFlash({\n error: (\n <>\n コメントが見つかりませんでした。\n
\n ページを再読み込みしてください。\n >\n ),\n });\n }\n },\n defaultValues: { content: comment.content },\n defaultImageFiles: comment.image_files,\n defaultFilesWithoutImage: comment.files_without_image,\n });\n return (\n \n \n \n );\n};\n\nconst Comment = ({\n comment,\n currentUser,\n onUpdated,\n post,\n}: {\n comment: PostComment;\n currentUser: SharedCurrentUser;\n onUpdated: (comment: PostComment) => void;\n post: PostsShowProps;\n}) => {\n const [showReplies, setShowReplies] = useState(false);\n const [repliesCount, setRepliesCount] = useState(comment.replies_count);\n const [isReplyFocusInput, setIsReplyFocusInput] = useState(false);\n const disclosure = useDisclosure();\n const ref = useRef(null);\n\n useEffect(() => {\n if (ref.current == null) return;\n\n const search = new URLSearchParams(location.search);\n if (\n comment.code == search.get(\"comment_code\") &&\n search.has(\"reply_code\")\n ) {\n setShowReplies(true);\n } else if (comment.code == search.get(\"comment_code\")) {\n ref.current.scrollIntoView({ block: \"start\" });\n // ピッタリはみづらいので微調整\n window.scrollTo(0, window.scrollY - 64);\n }\n // 初回のみ実行したいので\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return (\n \n \n \n \n \n \n {showReplies && (\n \n \n \n )}\n \n \n \n \n \n \n {!post.is_resolved && comment.is_own && (\n \n )}\n \n \n \n \n 投稿日:{dayjs(comment.created_at).format(\"L LT\")}\n \n {comment.updated_at !== \"\" &&\n comment.created_at !== comment.updated_at && (\n \n 最終更新日:{dayjs(comment.updated_at).format(\"L LT\")}\n \n )}\n \n \n {comment.content}\n \n {comment.files_without_image.length !== 0 && (\n \n \n \n )}\n \n {comment.image_files.length !== 0 && (\n \n file.url,\n )}\n />\n \n )}\n \n \n \n {repliesCount !== 0 && (\n {\n setShowReplies(!showReplies);\n setIsReplyFocusInput(false);\n }}\n >\n \n 返信{repliesCount}件\n \n )}\n {!post.is_resolved && (\n {\n setShowReplies(true);\n setIsReplyFocusInput(true);\n }}\n >\n 返信する\n \n )}\n \n \n \n \n {showReplies && currentUser?.is_all_public_feature_accessible && (\n setRepliesCount(result.replies_count)}\n onClose={() => setShowReplies(false)}\n isFocusInput={isReplyFocusInput}\n post={post}\n />\n )}\n \n \n );\n};\n\nconst CommentWrapper = ({\n comment: propComment,\n currentUser,\n post,\n}: {\n comment: PostComment;\n currentUser: SharedCurrentUser;\n post: PostsShowProps;\n}) => {\n const [comment, setComment] = useState(propComment);\n return (\n \n );\n};\n\nexport default CommentWrapper;\n","import { Box, Stack, Text } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport Comment from \"../../../posts/components/Comment\";\nimport { SharedCurrentUser } from \"../../lib/types\";\nimport { PostComment, PostsShowProps } from \"../../../posts/lib/types\";\n\nconst FetchedComments = ({\n comments,\n currentUser,\n post,\n}: {\n comments: PostComment[];\n currentUser: SharedCurrentUser;\n post: PostsShowProps;\n}) => {\n if (comments.length === 0) {\n return (\n \n \n この質問にはまだ回答がついていません。\n \n \n ぜひ、あなたの見解や経験を元に回答してみませんか?\n \n \n );\n }\n return (\n <>\n \n \n {comments.length}\n \n 件のコメントがあります\n \n \n {[...comments].reverse().map((comment) => (\n \n ))}\n \n >\n );\n};\n\nexport default FetchedComments;\n","import { Box, Link } from \"@chakra-ui/react\";\nimport React, { ReactNode } from \"react\";\nimport ChevronLeftIcon from \"../icons/ChevronLeftIcon\";\n\nconst GoBackLink = ({\n children,\n href = \"\",\n onClick = undefined,\n}: {\n children: ReactNode;\n href?: string;\n onClick?: () => void;\n}) => {\n return (\n \n \n \n {children}\n \n \n );\n};\n\nexport default GoBackLink;\n","import { Text } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport { SharedCurrentUser } from \"../../lib/types\";\nimport Overlay from \"./Overlay\";\nimport RegistrationButton from \"./RegisterOrLoginButtons\";\n\nconst NotApprovedUserOverlay = ({\n currentUser,\n}: {\n currentUser: SharedCurrentUser;\n}) => {\n return (\n \n \n {currentUser == null\n ? \"会員登録が完了すると、コメントを閲覧したり発言をすることが可能になります\"\n : currentUser.is_temporary && !currentUser.personal_info_applicationing\n ? \"本人確認が完了すると、コメントを閲覧したり発言をすることが可能になります\"\n : \"本人確認の審査が完了するまでお待ちください。\"}\n \n {(currentUser == null || !currentUser.personal_info_applicationing) && (\n \n )}\n \n );\n};\n\nexport default NotApprovedUserOverlay;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst CheckCircleIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default CheckCircleIcon;\n","import { Box, Flex, Grid, HStack } from \"@chakra-ui/react\";\nimport React, { ReactNode } from \"react\";\nimport CheckCircleIcon from \"../../icons/CheckCircleIcon\";\n\nconst QuestionResult = ({\n children,\n ratio,\n selected = false,\n}: {\n children: ReactNode;\n ratio: number;\n selected?: boolean;\n}) => {\n return (\n \n \n \n \n {children} \n {selected && }\n \n \n \n {ratio}%\n \n \n );\n};\n\nexport default QuestionResult;\n","import { SlideFade, Stack, Text } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport { questionVotesPath } from \"../../../../../../routes\";\nimport useRequest from \"../../../lib/useRequest\";\nimport Button from \"../Button\";\nimport useFlash from \"../../../lib/useFlash\";\nimport { SharedCurrentUser } from \"../../../lib/types\";\nimport RegistrationRequiredAction from \"../RegistrationRequiredAction\";\nimport { PostsShowProps } from \"../../../../posts/lib/types\";\n\nconst Votes = ({\n questionnaire,\n currentUser,\n beforeVote,\n onVote,\n}: {\n questionnaire: NonNullable;\n currentUser: SharedCurrentUser;\n beforeVote: () => void;\n onVote: () => void;\n}) => {\n const request = useRequest();\n const showFlash = useFlash();\n\n const vote = async (questionCode: string) => {\n beforeVote();\n const res = await request(questionVotesPath(questionCode), \"POST\");\n if (res.ok) {\n onVote();\n } else if (res.status === 404) {\n showFlash({\n error: (\n <>\n アンケートが見つかりませんでした。\n
\n ページを再読み込みしてください。\n >\n ),\n });\n }\n };\n\n return (\n <>\n \n \n {questionnaire.questions.map((question) => (\n vote(question.code)}\n render={(props) => (\n \n )}\n />\n ))}\n \n \n ※ 投票者の情報は一切公開されません\n \n \n >\n );\n};\n\nexport default Votes;\n","import {\n Box,\n CircularProgress,\n SlideFade,\n Stack,\n Text,\n} from \"@chakra-ui/react\";\nimport dayjs from \"dayjs\";\nimport React, { useState } from \"react\";\nimport { postPath } from \"../../../../../../routes\";\nimport useRequest from \"../../../lib/useRequest\";\nimport QuestionResult from \"./QuestionResult\";\nimport Votes from \"./Votes\";\nimport { SharedCurrentUser } from \"../../../lib/types\";\nimport { PostsShowProps } from \"../../../../posts/lib/types\";\n\nconst Questionnaire = ({\n currentUser,\n questionnaire: propQuestionnaire,\n}: {\n currentUser: SharedCurrentUser;\n questionnaire: NonNullable;\n}) => {\n const request = useRequest();\n\n const [questionnaire, setQuestionnaire] = useState(propQuestionnaire);\n const [questionnaireLoading, setQuestionnaireLoading] = useState(false);\n\n const fetchQuestionnaire = async () => {\n setQuestionnaireLoading(true);\n const res = await request(postPath(questionnaire.post_code), \"GET\");\n setQuestionnaire((await res.json()).questionnaire);\n setQuestionnaireLoading(false);\n };\n\n if (questionnaireLoading) {\n return (\n \n );\n }\n\n return (\n <>\n \n {questionnaire.total_votes_count}票・\n {questionnaire.voting_ended\n ? \"最終結果\"\n : `残り${dayjs(questionnaire.voting_ended_at).toNow(true)}`}\n \n \n {questionnaire.aggregate == null ? (\n setQuestionnaireLoading(true)}\n onVote={async () => {\n await fetchQuestionnaire();\n setQuestionnaireLoading(false);\n }}\n />\n ) : (\n \n \n \n {questionnaire.questions.map((question) => (\n \n {question.content}\n \n ))}\n \n \n \n )}\n \n >\n );\n};\n\nexport default Questionnaire;\n","import {\n Box,\n Divider,\n Flex,\n HStack,\n Heading,\n Link,\n Stack,\n Text,\n} from \"@chakra-ui/react\";\nimport dayjs from \"dayjs\";\nimport React, { ReactNode } from \"react\";\nimport { editPostPath, userPath } from \"../../../../../routes\";\nimport { HelperImageFile, SharedTag } from \"../../../../shared/lib/types\";\nimport CustomLinkLinkify from \"../../components/atoms/CustomLinkLinkify\";\nimport { isVisitableUserProfile } from \"../../lib/userProfileUtils\";\nimport {\n HelperRealNamePostResourceAuthor,\n SharedCurrentUser,\n} from \"../../lib/types\";\nimport BookmarkButton from \"../atoms/BookmarkButton\";\nimport FilesWithoutImage from \"../atoms/FilesWithoutImage\";\nimport ImagesWithSlider from \"../atoms/ImagesWithSlider\";\nimport PostResourceAvatar from \"../atoms/PostResourceAvatar\";\nimport PostResourceDisplayName from \"../atoms/PostResourceDisplayName\";\nimport Tag from \"../atoms/Tag\";\nimport MoreMenu from \"./MoreMenu\";\nimport Questionnaire from \"./Questionnaire\";\nimport { PostsShowProps } from \"../../../posts/lib/types\";\n\nconst PostDetail = ({\n post,\n currentUser,\n preview = false,\n children,\n}: {\n post: PostsShowProps;\n currentUser: SharedCurrentUser;\n preview?: boolean;\n // TODO: new_post_design_featureリリース後に削除\n children?: ReactNode;\n}) => {\n const props = isVisitableUserProfile(post.author, currentUser)\n ? {\n as: Link,\n href: userPath((post.author as HelperRealNamePostResourceAuthor).code),\n cursor: \"pointer\",\n target: \"_blank\",\n rel: \"noreferrer\",\n }\n : {};\n\n return (\n \n \n \n \n \n \n \n \n \n 投稿日:{dayjs(post.created_at).format(\"L LT\")}\n \n {post.updated_at !== \"\" &&\n post.created_at !== post.updated_at && (\n \n 更新日:{dayjs(post.updated_at).format(\"L LT\")}\n \n )}\n \n \n \n {!preview && (\n \n \n {!post.is_resolved && post.is_own && (\n \n )}\n \n )}\n \n \n {post.title}\n \n \n {post.tags.map((tag: SharedTag) => (\n {tag.name}\n ))}\n \n \n \n {post.content}\n \n {post.files_without_image.length !== 0 && (\n \n \n \n )}\n {post.image_files.length !== 0 && (\n \n file.url)}\n />\n \n )}\n {post.questionnaire && (\n \n \n \n )}\n \n {children}\n \n );\n};\n\nexport default PostDetail;\n","import { Stack } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport Comment from \"../../../posts/components/Comment\";\n\nconst TemporaryComments = () => {\n return (\n \n {[\n {\n code: \"1\",\n author: {\n code: \"1\",\n display_image_url: \"\",\n display_name: \"高橋憲太郎\",\n avatar_bgcolor: \"\",\n anonymous: false,\n is_profile_visitable: false,\n is_guest: false,\n } as {\n code: string;\n display_image_url: string;\n display_name: string;\n avatar_bgcolor: \"\",\n anonymous: false;\n is_profile_visitable: false,\n is_guest: false,\n },\n content:\n \"本人確認が完了すると、コメントを閲覧したり発言をすることが可能になります\",\n files_without_image: [],\n image_files: [],\n created_at: \"\",\n updated_at: \"\",\n is_own: false,\n replies: [],\n replies_count: 0,\n useful_count: 0,\n is_marked_useful: false,\n first_reply: null\n },\n {\n code: \"2\",\n author: {\n code: \"2\",\n display_image_url: \"\",\n display_name: \"田中美和\",\n avatar_bgcolor: \"\",\n anonymous: false,\n is_profile_visitable: false,\n is_guest: false,\n } as {\n code: string;\n display_image_url: string;\n display_name: string;\n avatar_bgcolor: \"\",\n anonymous: false;\n is_profile_visitable: false,\n is_guest: false,\n },\n content: \"本人確認の申請をする\",\n files_without_image: [],\n image_files: [],\n created_at: \"\",\n updated_at: \"\",\n is_own: false,\n replies: [],\n replies_count: 0,\n useful_count: 0,\n is_marked_useful: false,\n first_reply: null\n },\n {\n code: \"3\",\n author: {\n code: \"3\",\n display_image_url: \"\",\n display_name: \"山本健太\",\n avatar_bgcolor: \"\",\n anonymous: false,\n is_profile_visitable: false,\n is_guest: false,\n } as {\n code: string;\n display_image_url: string;\n display_name: string;\n avatar_bgcolor: \"\",\n anonymous: false;\n is_profile_visitable: false,\n is_guest: false,\n },\n content:\n \"A-Loopは、建築士の方が利用いただけるコミュニティサービスです。\",\n files_without_image: [],\n image_files: [],\n created_at: \"\",\n updated_at: \"\",\n is_own: false,\n replies: [],\n replies_count: 0,\n useful_count: 0,\n is_marked_useful: false,\n first_reply: null\n },\n ].map((comment) => (\n \n ))}\n \n );\n};\n\nexport default TemporaryComments;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst DoneIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default DoneIcon;\n","import { Box, Fade, Stack } from \"@chakra-ui/react\";\nimport React, { ReactNode } from \"react\";\nimport { useInView } from \"react-intersection-observer\";\nimport ExpandLessIcon from \"../../shared/components/icons/ExpandLessIcon\";\nimport { isShowFloatingRegisterButton } from \"../../shared/lib/isShowFloatingRegisterButton\";\nimport { SharedCurrentUser } from \"../../shared/lib/types\";\n\nconst BackToTop = ({\n children,\n currentUser,\n}: {\n children: ReactNode;\n currentUser: SharedCurrentUser;\n}) => {\n const { ref, inView } = useInView({ initialInView: true });\n const onClickBackToTop = () => {\n window.scrollTo({ top: 0, left: 0, behavior: \"smooth\" });\n };\n return (\n <>\n \n {children}\n \n \n \n \n \n \n TOPに戻る\n \n \n \n >\n );\n};\n\nexport default BackToTop;\n","import {\n Box,\n Container,\n Divider,\n Flex,\n HStack,\n Heading,\n List,\n ListItem,\n Modal,\n ModalBody,\n ModalCloseButton,\n ModalContent,\n ModalFooter,\n ModalHeader,\n ModalOverlay,\n Popover,\n PopoverArrow,\n PopoverBody,\n PopoverContent,\n PopoverTrigger,\n Show,\n Stack,\n Text,\n useDisclosure,\n} from \"@chakra-ui/react\";\nimport dayjs from \"dayjs\";\nimport React, { useCallback, useContext, useEffect, useRef, useState } from \"react\";\nimport {\n postCommentsPath,\n postPath,\n postResolvedPath,\n postsPath,\n} from \"../../../routes\";\nimport { Flash } from \"../../shared/lib/types\";\nimport useFlash from \"../shared/lib/useFlash\";\nimport useRequest from \"../shared/lib/useRequest\";\nimport { SharedCurrentUser } from \"../shared/lib/types\";\nimport { Button } from \"../shared/components/atoms\";\nimport Background from \"../shared/components/atoms/Background\";\nimport FetchedComments from \"../shared/components/atoms/FetchedComments\";\nimport Footer from \"../shared/components/atoms/Footer\";\nimport GoBackLink from \"../shared/components/atoms/GoBackLink\";\nimport Header from \"../shared/components/atoms/Header\";\nimport HelpMessage from \"../shared/components/atoms/HelpMessage\";\nimport NotApprovedUserOverlay from \"../shared/components/atoms/NotApprovedUserOverlay\";\nimport PostDetail from \"../shared/components/atoms/PostDetail\";\nimport ServiceCaution from \"../shared/components/atoms/ServiceCaution\";\nimport Tag from \"../shared/components/atoms/Tag\";\nimport TemporaryComments from \"../shared/components/atoms/TemporaryComments\";\nimport ChatBubbleIcon from \"../shared/components/icons/ChatBubbleIcon\";\nimport DoneIcon from \"../shared/components/icons/DoneIcon\";\nimport Application from \"../shared/components/layouts/Application\";\nimport AnonymousField from \"./components/AnonymousField\";\nimport BackToTop from \"./components/BackToTop\";\nimport {\n CommentFormFields,\n useCommentForm,\n} from \"./components/CommentReplyForm\";\nimport { PostsShowProps, RelatedPosts } from \"./lib/types\";\nimport { FlipperContext } from \"../../shared/lib/FlipperContext\";\nimport EventBanner from \"./components/EventBanner\";\nimport bannerSp from \"./assets/dummy-sp.png\";\n\nconst CommentForm = ({\n currentUser,\n fetchComments,\n post,\n onSubmit,\n}: {\n currentUser: SharedCurrentUser;\n fetchComments: (post_code: string) => void;\n post: PostsShowProps;\n onSubmit?: () => void;\n}) => {\n const request = useRequest();\n const showFlash = useFlash();\n\n const commentFormState = useCommentForm({\n onSubmit: async (data) => {\n const res = await request(postCommentsPath(post.code), \"POST\", data);\n if (res.ok) {\n if (onSubmit) onSubmit();\n fetchComments(post.code);\n } else if (res.status === 404) {\n showFlash({\n error: (\n <>\n 投稿が見つかりませんでした。\n
\n ページを再読み込みしてください。\n >\n ),\n });\n }\n },\n hasAnonymousField: currentUser?.is_anonymousable,\n defaultValues: {\n content: \"\",\n anonymous: post.previous_post_anonymous,\n },\n });\n\n const anonymous = commentFormState.methods.watch(\"anonymous\");\n\n return (\n \n 質問にコメントする\n {post.is_resolved ? (\n \n 解決済みの質問のため、\n \n コメントできません\n \n ) : (\n <>\n \n {currentUser?.is_anonymousable && (\n \n )}\n \n \n \n \n \n >\n )}\n \n );\n};\n\nconst Page = ({\n post,\n relatedPosts,\n current_user,\n}: {\n post: PostsShowProps;\n relatedPosts: RelatedPosts;\n current_user: SharedCurrentUser;\n}) => {\n const [fetchCommentsResult, setFetchCommentsResult] = useState({\n // TODO: dev storybookに渡すためにしているのでmockして無くしたい\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-expect-error\n comments: post.comments ?? [],\n comments_count: post.comments_count,\n });\n\n const request = useRequest();\n const flash = useFlash();\n const ref = useRef(null);\n\n const fetchComments = useCallback(\n async (post_code: string) => {\n const res = await request(postCommentsPath(post_code), \"GET\");\n\n if (res.ok) {\n setFetchCommentsResult(await res.json());\n } else if (res.status === 401) {\n flash({\n error: \"エラーが発生しました。ページを再読み込みしてください。\",\n });\n }\n },\n [request, flash],\n );\n\n useEffect(() => {\n if (current_user?.is_all_public_feature_accessible) {\n void fetchComments(post.code);\n }\n }, [\n fetchComments,\n post.code,\n current_user?.is_all_public_feature_accessible,\n ]);\n\n const resolveModalDisclosure = useDisclosure();\n\n return (\n \n \n Q&A一覧に戻る\n \n Q&A投稿方法\n \n \n {!post.is_resolved && post.is_own && (\n \n \n \n \n コメント受付を終了したい場合\n \n \n \n \n \n \n 投稿を解決するとコメントができなくなります。また、解決済みにした投稿は元に戻すことができません。\n \n \n \n \n }\n size=\"sm\"\n onClick={resolveModalDisclosure.onOpen}\n >\n 解決した\n \n \n )}\n {post.is_resolved && (\n \n \n \n この質問は解決済みです\n ※コメントや返信はできません\n \n \n )}\n \n \n \n \n \n {current_user?.is_all_public_feature_accessible ? (\n <>\n \n \n >\n ) : (\n \n )}\n \n \n {\n ref.current?.scrollIntoView({ behavior: \"smooth\" });\n }}\n />\n {!current_user?.is_all_public_feature_accessible && (\n \n )}\n \n \n \n {relatedPosts.length !== 0 && (\n \n 関連するQ&A\n \n {relatedPosts.map((relatedPost) => (\n \n \n {relatedPost.title}\n \n \n {relatedPost.tags.map((tag) => (\n {tag.name}\n ))}\n \n \n {relatedPost.content}\n \n \n \n {dayjs(relatedPost.created_at).format(\"L LT\")}\n \n \n \n \n コメント {relatedPost.comments_count}件\n \n \n \n \n ))}\n \n \n )}\n \n \n \n \n \n );\n};\n\nconst PostsShow = ({\n post,\n relatedPosts,\n flash,\n currentUser: currentUser,\n}: {\n post: PostsShowProps;\n relatedPosts: RelatedPosts;\n flash: Flash;\n currentUser: SharedCurrentUser;\n}) => {\n const flipper = useContext(FlipperContext);\n\n return (\n \n \n \n {flipper.event_feature && (\n \n \n \n \n \n )}\n {flipper.event_feature && (\n \n )}\n \n \n \n \n \n \n \n \n );\n};\n\nconst ResolveModal = ({\n isOpen,\n onClose,\n post,\n}: {\n isOpen: boolean;\n onClose: () => void;\n post: PostsShowProps;\n}) => {\n const request = useRequest();\n\n const resolvePost = async () => {\n const res = await request(postResolvedPath(post.code), \"PUT\");\n\n if (res.ok) {\n location.reload();\n }\n };\n\n return (\n \n \n \n 投稿を解決済みにする\n \n \n 解決済みにすると以下の操作を行うことができなくなります。\n\n \n ・投稿の編集\n ・コメント・返信の追加・編集\n
\n\n \n 一度解決済みにすると解決済みを解除することはできませんのでご注意ください。\n \n \n\n \n \n \n \n \n \n );\n};\n\nexport default PostsShow;\n","import { Box, HStack, Text } from \"@chakra-ui/react\";\nimport { motion, useAnimation } from \"framer-motion\";\nimport React, { useEffect, useState } from \"react\";\nimport { Button } from \"../../shared/components/atoms\";\nimport ThumbUpFilledIcon from \"../../shared/components/icons/ThumbUpFilledIcon\";\nimport ThumbUpIcon from \"../../shared/components/icons/ThumbUpIcon\";\nimport useRequest from \"../../shared/lib/useRequest\";\n\nconst NewUsefulButton = ({\n useful: propUseful,\n count: propCount,\n path,\n isDisabled = false,\n}: {\n useful: boolean;\n count: number;\n path: string;\n isDisabled?: boolean;\n}) => {\n const [useful, setUseful] = useState(propUseful);\n const [count, setCount] = useState(propCount);\n const [animate, setAnimate] = useState(false);\n const request = useRequest();\n\n const toggleUseful = async () => {\n if (useful) {\n setAnimate(false);\n setCount(count - 1);\n await request(path, \"DELETE\");\n } else {\n setAnimate(true);\n setCount(count + 1);\n await request(path, \"PUT\");\n }\n setUseful(!useful);\n };\n\n return (\n \n );\n};\n\nconst UI = ({\n useful,\n count,\n animate,\n onClick,\n}: {\n useful: boolean;\n count: number;\n animate: boolean;\n onClick: (() => void) | undefined;\n}) => {\n const controls = useAnimation();\n useEffect(() => {\n if (animate) {\n void controls.start({\n scale: [1, 1.3, 1.6, 1.3, 1],\n rotate: [\"-5deg\", \"0deg\", \"10deg\", \"-10deg\", \"10deg\", \"-10deg\", \"0deg\"],\n });\n }\n }, [animate, controls]);\n const props = onClick\n ? {}\n : {\n bgColor: undefined,\n color: undefined,\n cursor: \"default\",\n };\n return (\n \n );\n};\n\nexport default NewUsefulButton;\n","import { Box, Flex, HStack, useDisclosure } from \"@chakra-ui/react\";\nimport dayjs from \"dayjs\";\nimport React, { useEffect, useRef, useState } from \"react\";\nimport {\n postCommentReplyPath,\n postCommentReplyUsefulPath,\n} from \"../../../../routes\";\nimport { HelperImageFile } from \"../../../shared/lib/types\";\nimport CustomLinkLinkify from \"../../shared/components/atoms/CustomLinkLinkify\";\nimport FilesWithoutImage from \"../../shared/components/atoms/FilesWithoutImage\";\nimport ImagesWithSlider from \"../../shared/components/atoms/ImagesWithSlider\";\nimport MoreMenu from \"../../shared/components/atoms/MoreMenu\";\nimport PostResourceAvatar from \"../../shared/components/atoms/PostResourceAvatar\";\nimport PostResourceDisplayName from \"../../shared/components/atoms/PostResourceDisplayName\";\nimport { SharedApprovedCurrentUser } from \"../../shared/lib/types\";\nimport useFlash from \"../../shared/lib/useFlash\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport { PostsShowProps, UserPostCommentReply } from \"../lib/types\";\nimport { EditModal, ReplyFormFields, useReplyForm } from \"./CommentReplyForm\";\nimport NewUsefulButton from \"./NewUsefulButton\";\nimport UserProfileLink from \"./UserProfileLink\";\n\nconst ReplyWrapper = ({\n reply: propReply,\n currentUser,\n post,\n}: {\n reply: UserPostCommentReply;\n currentUser: SharedApprovedCurrentUser;\n post: PostsShowProps;\n}) => {\n const [reply, setReply] = useState(propReply);\n return (\n \n );\n};\n\nconst EditReplyModal = ({\n reply,\n onUpdated,\n editModalDisclosure,\n currentUser,\n}: {\n reply: UserPostCommentReply;\n onUpdated: (data: UserPostCommentReply) => void;\n editModalDisclosure: ReturnType;\n currentUser: SharedApprovedCurrentUser;\n}) => {\n const request = useRequest();\n const showFlash = useFlash();\n\n const editReplyFormState = useReplyForm({\n onSubmit: async (data) => {\n const res = await request(postCommentReplyPath(reply.code), \"PUT\", data);\n if (res.ok) {\n onUpdated(await res.json());\n } else if (res.status === 404) {\n showFlash({\n error: (\n <>\n 返信が見つかりませんでした。\n
\n ページを再読み込みしてください。\n >\n ),\n });\n }\n },\n defaultValues: { content: reply.content },\n defaultImageFiles: reply.image_files,\n defaultFilesWithoutImage: reply.files_without_image,\n });\n\n return (\n \n \n \n );\n};\n\nconst Reply = ({\n reply,\n currentUser,\n onUpdated,\n post,\n}: {\n reply: UserPostCommentReply;\n currentUser: SharedApprovedCurrentUser;\n onUpdated: (reply: UserPostCommentReply) => void;\n post: PostsShowProps;\n}) => {\n const editModalDisclosure = useDisclosure();\n const ref = useRef(null);\n useEffect(() => {\n if (ref.current == null) return;\n\n const search = new URLSearchParams(location.search);\n if (reply.code == search.get(\"reply_code\")) {\n ref.current.scrollIntoView({ block: \"start\" });\n // ピッタリはみづらいので微調整\n window.scrollTo(0, window.scrollY - 24);\n }\n // 初回のみ実行したいので\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n \n 投稿日:{dayjs(reply.created_at).format(\"L LT\")}\n \n {reply.updated_at !== \"\" &&\n reply.created_at !== reply.updated_at && (\n \n 最終更新日:{dayjs(reply.updated_at).format(\"L LT\")}\n \n )}\n \n \n \n {!post.is_resolved && reply.is_own && (\n \n )}\n \n \n {reply.content}\n \n {reply.files_without_image.length !== 0 && (\n \n \n \n )}\n {reply.image_files.length !== 0 && (\n \n file.url)}\n />\n \n )}\n \n \n \n \n \n \n );\n};\n\nexport default ReplyWrapper;\n","import { Box, Divider, Spinner, Stack } from \"@chakra-ui/react\";\nimport React, { useCallback, useEffect, useRef, useState } from \"react\";\nimport { postCommentRepliesPath } from \"../../../../routes\";\nimport { SharedApprovedCurrentUser } from \"../../shared/lib/types\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport {\n PostComment,\n PostCommentRepliesIndex,\n PostsShowProps,\n} from \"../lib/types\";\nimport Reply from \"./Reply\";\n\nconst Replies = ({\n comment,\n currentUser,\n onFetched,\n isFocusEndOfReplies,\n post,\n}: {\n comment: PostComment;\n currentUser: SharedApprovedCurrentUser;\n onFetched: (result: PostCommentRepliesIndex) => void;\n isFocusEndOfReplies: boolean;\n post: PostsShowProps;\n}) => {\n // TODO: dev storybookのためなのでmockして消したい\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-expect-error\n const [replies, setReplies] = useState(comment.replies);\n const endOfRepliesRef = useRef(null);\n const request = useRequest();\n\n const fetchReplies = useCallback(\n async (comment_code: string) => {\n const res = await request(postCommentRepliesPath(comment_code), \"GET\");\n const result = await res.json();\n setReplies(result.replies);\n onFetched(result);\n },\n [request, onFetched],\n );\n\n useEffect(() => {\n void fetchReplies(comment.code);\n }, [fetchReplies, comment.code]);\n\n useEffect(() => {\n if (isFocusEndOfReplies) {\n // すぐ遷移しようとすると途中で止まるので一瞬待つ\n setTimeout(() => {\n endOfRepliesRef.current?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n });\n }, 250);\n }\n }, [isFocusEndOfReplies]);\n\n return (\n <>\n \n {!replies ? (\n \n \n \n ) : (\n \n {/* 最初の一件はすでに表示済みなので除外 */}\n {[...replies]\n .reverse()\n .slice(1)\n .map((reply, i) => (\n \n \n {/* 2の理由は最初の一件が除外されているため */}\n {i !== replies.length - 2 && (\n \n )}\n \n ))}\n \n )}\n \n \n >\n );\n};\nexport default Replies;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst SendIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default SendIcon;\n","import { Box, Flex, HStack, RadioGroup } from \"@chakra-ui/react\";\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport React, { useState } from \"react\";\nimport { Controller, useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport { Button } from \"../../shared/components/atoms\";\nimport {\n InputError,\n Radio,\n Textarea,\n} from \"../../shared/components/atoms/form\";\nimport PostResourceAvatar from \"../../shared/components/atoms/PostResourceAvatar\";\nimport SendIcon from \"../../shared/components/icons/SendIcon\";\nimport { SharedCurrentUser } from \"../../shared/lib/types\";\nimport FileUploadButton from \"../../../shared/components/FileUploadButton\";\nimport AddIcon from \"../../shared/components/icons/AddIcon\";\nimport UploadFiles, {\n useUploadFiles,\n} from \"../../../shared/components/UploadFiles\";\nimport { postCommentRepliesPath } from \"../../../../routes\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport { uploadFiles } from \"../../../shared/lib/uploadFile\";\nimport useFlash from \"../../shared/lib/useFlash\";\nimport { PostComment, PostsShowProps } from \"../lib/types\";\nimport { Stack } from \"@mui/material\";\nimport PostResourceDisplayName from \"../../shared/components/atoms/PostResourceDisplayName\";\n\nconst schema = yup.object().shape({\n content: yup.string().trim().required().max(65535),\n anonymous: yup.string().when(\"$isRequredAnonymous\", {\n is: true,\n then: (schema) => schema.required(\"表示名を選択してください\"),\n }),\n});\n\nconst ReplyForm = ({\n post,\n comment,\n currentUser,\n onSubmitted,\n}: {\n post: PostsShowProps;\n comment: PostComment;\n currentUser: SharedCurrentUser;\n onSubmitted: () => void;\n}) => {\n const [active, setActive] = useState(false);\n const {\n handleSubmit,\n control,\n watch,\n formState: { isSubmitting, isValid },\n reset,\n } = useForm({\n defaultValues: { content: \"\", anonymous: post.previous_post_anonymous },\n resolver: yupResolver(schema),\n context: { isRequredAnonymous: currentUser?.is_anonymousable },\n });\n const [files, addFiles, removeFile, clear] = useUploadFiles();\n const request = useRequest();\n const flash = useFlash();\n const anonymous = watch(\"anonymous\");\n\n const isAnonymous = anonymous === \"1\";\n const onSubmit = async (data: yup.InferType) => {\n const result = await uploadFiles({ files } as { files: File[] });\n if (result.some((res) => !!res.error)) {\n flash({\n error: \"ファイルアップロードに失敗しました。もう一度お試しください\",\n });\n return;\n }\n const res = await request(postCommentRepliesPath(comment.code), \"POST\", {\n post_comment_reply: {\n ...data,\n files: result.map((res) => res.blob.signed_id),\n },\n });\n if (res.ok) {\n clear();\n reset({ content: \"\", anonymous });\n onSubmitted();\n }\n };\n\n return (\n setActive(true)}\n onSubmit={handleSubmit(onSubmit)}\n data-testid=\"new-reply-form\"\n >\n {active && currentUser?.is_all_public_feature_accessible && (\n \n {currentUser.is_anonymousable && (\n (\n \n \n \n 実名で返信\n ニックネームで返信\n \n \n {error && {error.message}}\n \n )}\n />\n )}\n \n \n \n \n \n )}\n \n (\n \n {active && (\n \n ※出典(引用元)を記載するとコメントの信憑性が高まります。\n \n )}\n {active && (\n \n \n \n \n ファイル添付\n \n \n \n \n )}\n \n );\n};\n\nexport default ReplyForm;\n","import {\n Box,\n Divider,\n Flex,\n HStack,\n Text,\n useDisclosure,\n} from \"@chakra-ui/react\";\nimport dayjs from \"dayjs\";\nimport React, { useEffect, useRef, useState } from \"react\";\nimport { postCommentPath, postCommentUsefulPath } from \"../../../../routes\";\nimport { HelperImageFile } from \"../../../shared/lib/types\";\nimport CustomLinkLinkify from \"../../shared/components/atoms/CustomLinkLinkify\";\nimport FilesWithoutImage from \"../../shared/components/atoms/FilesWithoutImage\";\nimport ImagesWithSlider from \"../../shared/components/atoms/ImagesWithSlider\";\nimport MoreMenu from \"../../shared/components/atoms/MoreMenu\";\nimport PostResourceAvatar from \"../../shared/components/atoms/PostResourceAvatar\";\nimport PostResourceDisplayName from \"../../shared/components/atoms/PostResourceDisplayName\";\nimport ArrowDropDownIcon from \"../../shared/components/icons/ArrowDropDownIcon\";\nimport ArrowDropUpIcon from \"../../shared/components/icons/ArrowDropUpIcon\";\nimport { SharedCurrentUser } from \"../../shared/lib/types\";\nimport useFlash from \"../../shared/lib/useFlash\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport {\n CommentFormFields,\n EditModal,\n useCommentForm,\n} from \"./CommentReplyForm\";\nimport NewReplies from \"./NewReplies\";\nimport NewUsefulButton from \"./NewUsefulButton\";\nimport Reply from \"./Reply\";\nimport { PostComment, PostsShowProps } from \"../lib/types\";\nimport ReplyForm from \"./ReplyForm\";\nimport UserProfileLink from \"./UserProfileLink\";\n\nconst CommentWrapper = ({\n comment: propComment,\n currentUser,\n post,\n}: {\n comment: PostComment;\n currentUser: SharedCurrentUser;\n post: PostsShowProps;\n}) => {\n const [comment, setComment] = useState(propComment);\n return (\n \n );\n};\n\nconst Comment = ({\n comment,\n currentUser,\n onUpdated,\n post,\n}: {\n comment: PostComment;\n currentUser: SharedCurrentUser;\n onUpdated: (comment: PostComment) => void;\n post: PostsShowProps;\n}) => {\n const [showReplies, setShowReplies] = useState(false);\n const [repliesCount, setRepliesCount] = useState(comment.replies_count);\n const [isFocusEndOfReplies, setIsFocusEndOfReplies] = useState(false);\n const disclosure = useDisclosure();\n const ref = useRef(null);\n const request = useRequest();\n const flash = useFlash();\n\n useEffect(() => {\n if (ref.current == null) return;\n\n const search = new URLSearchParams(location.search);\n if (\n comment.code == search.get(\"comment_code\") &&\n search.has(\"reply_code\")\n ) {\n setShowReplies(true);\n } else if (comment.code == search.get(\"comment_code\")) {\n ref.current.scrollIntoView({ block: \"start\" });\n // ピッタリはみづらいので微調整\n window.scrollTo(0, window.scrollY - 64);\n }\n // 初回のみ実行したいので\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const onReply = async () => {\n const res = await request(postCommentPath(comment.code), \"GET\");\n if (res.ok) {\n const comment = await res.json();\n setShowReplies(true);\n setRepliesCount(comment.replies_count);\n setIsFocusEndOfReplies(true);\n onUpdated(comment);\n } else {\n flash({\n error: \"回答が見つかりませんでした。ページを再読み込みしてください。\",\n });\n }\n };\n\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n \n 投稿日:{dayjs(comment.created_at).format(\"L LT\")}\n \n {comment.updated_at !== \"\" &&\n comment.created_at !== comment.updated_at && (\n \n 最終更新日:{dayjs(comment.updated_at).format(\"L LT\")}\n \n )}\n \n \n \n {!post.is_resolved && comment.is_own && (\n \n )}\n \n \n {comment.content}\n \n {comment.files_without_image.length !== 0 && (\n \n \n \n )}\n {comment.image_files.length !== 0 && (\n \n file.url,\n )}\n />\n \n )}\n \n \n \n {currentUser?.is_all_public_feature_accessible &&\n comment.first_reply && (\n \n \n \n \n 返信\n \n \n \n \n \n \n )}\n {repliesCount > 1 && (\n <>\n \n {\n setIsFocusEndOfReplies(false);\n setShowReplies(!showReplies);\n }}\n role=\"group\"\n sx={{\n \"&:hover span\": {\n textDecoration: \"underline\",\n },\n \"&:hover .no-underline\": {\n textDecoration: \"none\",\n },\n }}\n >\n \n その他の返信\n \n \n \n {repliesCount - 1}\n \n 件\n \n \n を表示する\n {showReplies ? (\n \n ) : (\n \n )}\n \n \n {showReplies && currentUser?.is_all_public_feature_accessible && (\n setRepliesCount(result.replies_count)}\n isFocusEndOfReplies={isFocusEndOfReplies}\n post={post}\n />\n )}\n >\n )}\n \n \n {post.is_resolved ? (\n \n 解決済みの質問のため、\n \n 返信できません\n \n ) : (\n {\n await onReply();\n }}\n />\n )}\n \n \n \n \n );\n};\n\nconst CommentEditModal = ({\n comment,\n onUpdated,\n disclosure,\n currentUser,\n}: {\n comment: PostComment;\n onUpdated: (comment: PostComment) => void;\n disclosure: ReturnType;\n currentUser: SharedCurrentUser;\n}) => {\n const request = useRequest();\n const showFlash = useFlash();\n\n const commentFormState = useCommentForm({\n onSubmit: async (data) => {\n const res = await request(postCommentPath(comment.code), \"PUT\", data);\n if (res.ok) {\n onUpdated(await res.json());\n } else if (res.status === 404) {\n showFlash({\n error: (\n <>\n 回答が見つかりませんでした。\n
\n ページを再読み込みしてください。\n >\n ),\n });\n }\n },\n defaultValues: { content: comment.content },\n defaultImageFiles: comment.image_files,\n defaultFilesWithoutImage: comment.files_without_image,\n });\n return (\n \n \n \n );\n};\n\nexport default CommentWrapper;\n","import { Box, HStack, Stack } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport { SharedCurrentUser } from \"../../lib/types\";\nimport NewComment from \"../../../posts/components/NewComment\";\nimport { PostComment, PostsShowProps } from \"../../../posts/lib/types\";\n\nconst FetchedComments = ({\n comments,\n currentUser,\n post,\n}: {\n comments: PostComment[];\n currentUser: SharedCurrentUser;\n post: PostsShowProps;\n}) => {\n if (comments.length === 0) {\n return <>>;\n }\n return (\n <>\n \n 回答\n \n {comments.length}\n 件\n \n \n \n {[...comments].reverse().map((comment) => (\n \n ))}\n \n >\n );\n};\n\nexport default FetchedComments;\n","import { Stack } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport Comment from \"../../../posts/components/NewComment\";\n\nconst TemporaryComments = () => {\n return (\n \n {[\n {\n code: \"1\",\n author: {\n code: \"1\",\n display_image_url: \"\",\n display_name: \"高橋憲太郎\",\n avatar_bgcolor: \"\",\n anonymous: false,\n is_profile_visitable: false,\n is_guest: false,\n } as {\n code: string;\n display_image_url: string;\n display_name: string;\n avatar_bgcolor: \"\",\n anonymous: false;\n is_profile_visitable: false,\n is_guest: false,\n },\n content:\n \"本人確認が完了すると、回答を閲覧したり発言をすることが可能になります\",\n files_without_image: [],\n image_files: [],\n created_at: \"\",\n updated_at: \"\",\n is_own: false,\n is_commented: false,\n replies: [],\n replies_count: 0,\n useful_count: 0,\n is_marked_useful: false,\n first_reply: null\n },\n {\n code: \"2\",\n author: {\n code: \"2\",\n display_image_url: \"\",\n display_name: \"田中美和\",\n avatar_bgcolor: \"\",\n anonymous: false,\n is_profile_visitable: false,\n is_guest: false,\n } as {\n code: string;\n display_image_url: string;\n display_name: string;\n avatar_bgcolor: \"\",\n anonymous: false;\n is_profile_visitable: false,\n is_guest: false,\n },\n content: \"本人確認の申請をする\",\n files_without_image: [],\n image_files: [],\n created_at: \"\",\n updated_at: \"\",\n is_own: false,\n is_commented: false,\n replies: [],\n replies_count: 0,\n useful_count: 0,\n is_marked_useful: false,\n first_reply: null\n },\n ].map((comment) => (\n \n ))}\n \n );\n};\n\nexport default TemporaryComments;\n","import { Text } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport { SharedCurrentUser } from \"../../lib/types\";\nimport Overlay from \"./Overlay\";\nimport RegistrationButton from \"./RegisterOrLoginButtons\";\n\nconst NotApprovedUserOverlay = ({\n currentUser,\n}: {\n currentUser: SharedCurrentUser;\n}) => {\n return (\n \n \n {currentUser == null\n ? \"会員登録が完了すると、回答を閲覧したり発言をすることが可能になります\"\n : currentUser.is_temporary && !currentUser.personal_info_applicationing\n ? \"本人確認が完了すると、回答を閲覧したり発言をすることが可能になります\"\n : \"本人確認の審査が完了するまでお待ちください。\"}\n \n {(currentUser == null || !currentUser.personal_info_applicationing) && (\n \n )}\n \n );\n};\n\nexport default NotApprovedUserOverlay;\n","import {\n Box,\n Container,\n Flex,\n HStack,\n Heading,\n List,\n ListItem,\n Modal,\n ModalBody,\n ModalCloseButton,\n ModalContent,\n ModalFooter,\n ModalHeader,\n ModalOverlay,\n Popover,\n PopoverArrow,\n PopoverBody,\n PopoverContent,\n PopoverTrigger,\n Show,\n Stack,\n Text,\n useDisclosure,\n} from \"@chakra-ui/react\";\nimport dayjs from \"dayjs\";\nimport React, {\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport {\n postCommentsPath,\n postPath,\n postResolvedPath,\n postsPath,\n} from \"../../../routes\";\nimport { Flash } from \"../../shared/lib/types\";\nimport { Button } from \"../shared/components/atoms\";\nimport Background from \"../shared/components/atoms/Background\";\nimport Footer from \"../shared/components/atoms/Footer\";\nimport GoBackLink from \"../shared/components/atoms/GoBackLink\";\nimport Header from \"../shared/components/atoms/Header\";\nimport HelpMessage from \"../shared/components/atoms/HelpMessage\";\nimport NewFetchedComments from \"../shared/components/atoms/NewFetchedComments\";\nimport PostDetail from \"../shared/components/atoms/PostDetail\";\nimport ServiceCaution from \"../shared/components/atoms/ServiceCaution\";\nimport Tag from \"../shared/components/atoms/Tag\";\nimport ChatBubbleIcon from \"../shared/components/icons/ChatBubbleIcon\";\nimport DoneIcon from \"../shared/components/icons/DoneIcon\";\nimport Application from \"../shared/components/layouts/Application\";\nimport { SharedCurrentUser } from \"../shared/lib/types\";\nimport useFlash from \"../shared/lib/useFlash\";\nimport useRequest from \"../shared/lib/useRequest\";\nimport AnonymousField from \"./components/AnonymousField\";\nimport BackToTop from \"./components/BackToTop\";\nimport {\n CommentFormFields,\n useCommentForm,\n} from \"./components/CommentReplyForm\";\nimport { PostsShowProps, RelatedPosts } from \"./lib/types\";\nimport TemporaryComments from \"../shared/components/atoms/NewTemporaryComments\";\nimport NotApprovedUserOverlay from \"../shared/components/atoms/NewNotApprovedUserOverlay\";\nimport { FlipperContext } from \"../../shared/lib/FlipperContext\";\nimport EventBanner from \"./components/EventBanner\";\nimport bannerSp from \"./assets/dummy-sp.png\";\n\nconst PostsShow = ({\n post,\n relatedPosts,\n flash,\n currentUser: currentUser,\n}: {\n post: PostsShowProps;\n relatedPosts: RelatedPosts;\n flash: Flash;\n currentUser: SharedCurrentUser;\n}) => {\n const flipper = useContext(FlipperContext);\n\n return (\n \n \n \n {flipper.event_feature && (\n \n \n \n \n \n )}\n {flipper.event_feature && }\n \n \n \n \n \n \n \n \n );\n};\n\nconst Page = ({\n post,\n relatedPosts,\n current_user,\n}: {\n post: PostsShowProps;\n relatedPosts: RelatedPosts;\n current_user: SharedCurrentUser;\n}) => {\n const [fetchCommentsResult, setFetchCommentsResult] = useState({\n comments: [],\n comments_count: post.comments_count,\n });\n\n const request = useRequest();\n const flash = useFlash();\n const ref = useRef(null);\n\n const fetchComments = useCallback(\n async (post_code: string) => {\n const res = await request(postCommentsPath(post_code), \"GET\");\n\n if (res.ok) {\n setFetchCommentsResult(await res.json());\n } else if (res.status === 401) {\n flash({\n error: \"エラーが発生しました。ページを再読み込みしてください。\",\n });\n }\n },\n [request, flash],\n );\n\n useEffect(() => {\n if (current_user?.is_all_public_feature_accessible) {\n void fetchComments(post.code);\n }\n }, [\n fetchComments,\n post.code,\n current_user?.is_all_public_feature_accessible,\n ]);\n\n const resolveModalDisclosure = useDisclosure();\n\n return (\n \n \n Q&A一覧に戻る\n \n Q&A投稿方法\n \n \n {!post.is_resolved && post.is_own && (\n \n \n \n \n 回答受付を終了したい場合\n \n \n \n \n \n \n 投稿を解決すると回答ができなくなります。また、解決済みにした投稿は元に戻すことができません。\n \n \n \n \n }\n size=\"sm\"\n onClick={resolveModalDisclosure.onOpen}\n >\n 解決した\n \n \n )}\n {post.is_resolved && (\n \n \n \n この質問は解決済みです\n ※回答や返信はできません\n \n \n )}\n \n \n \n \n \n {current_user?.is_all_public_feature_accessible ? (\n <>\n \n \n \n \n >\n ) : (\n \n )}\n \n {!current_user?.is_all_public_feature_accessible && (\n \n )}\n \n \n {\n ref.current?.scrollIntoView({ behavior: \"smooth\" });\n }}\n />\n \n {relatedPosts.length !== 0 && (\n \n 関連するQ&A\n \n {relatedPosts.map((relatedPost) => (\n \n \n {relatedPost.title}\n \n \n {relatedPost.tags.map((tag) => (\n {tag.name}\n ))}\n \n \n {relatedPost.content}\n \n \n \n {dayjs(relatedPost.created_at).format(\"L LT\")}\n \n \n \n \n 回答 {relatedPost.comments_count}件\n \n \n \n \n ))}\n \n \n )}\n \n \n \n \n \n );\n};\n\nconst CommentForm = ({\n currentUser,\n fetchComments,\n post,\n onSubmit,\n}: {\n currentUser: SharedCurrentUser;\n fetchComments: (post_code: string) => void;\n post: PostsShowProps;\n onSubmit?: () => void;\n}) => {\n const request = useRequest();\n const showFlash = useFlash();\n const [isCommented, setIsCommented] = useState(post.is_commented);\n\n const commentFormState = useCommentForm({\n onSubmit: async (data) => {\n const res = await request(postCommentsPath(post.code), \"POST\", data);\n if (res.ok) {\n if (onSubmit) onSubmit();\n setIsCommented(true);\n fetchComments(post.code);\n } else if (res.status === 404) {\n showFlash({\n error: (\n <>\n 投稿が見つかりませんでした。\n
\n ページを再読み込みしてください。\n >\n ),\n });\n }\n },\n hasAnonymousField: currentUser?.is_anonymousable,\n defaultValues: {\n content: \"\",\n anonymous: post.previous_post_anonymous,\n },\n });\n\n const {\n methods: {\n formState: { isValid },\n },\n } = commentFormState;\n const anonymous = commentFormState.methods.watch(\"anonymous\");\n\n return (\n \n 質問に回答する\n {post.is_resolved ? (\n \n 解決済みの質問のため、\n \n 回答できません\n \n ) : isCommented ? (\n \n あなたはこの質問に対して、すでに回答済みです \n 補足がある場合は、自分の回答に対して返信を追加してください\n \n ) : (\n <>\n \n {currentUser?.is_anonymousable && (\n \n )}\n \n \n \n \n \n >\n )}\n \n );\n};\n\nconst ResolveModal = ({\n isOpen,\n onClose,\n post,\n}: {\n isOpen: boolean;\n onClose: () => void;\n post: PostsShowProps;\n}) => {\n const request = useRequest();\n\n const resolvePost = async () => {\n const res = await request(postResolvedPath(post.code), \"PUT\");\n\n if (res.ok) {\n location.reload();\n }\n };\n\n return (\n \n \n \n 投稿を解決済みにする\n \n \n 解決済みにすると以下の操作を行うことができなくなります。\n\n \n ・投稿の編集\n ・回答・返信の追加・編集\n
\n\n \n 一度解決済みにすると解決済みを解除することはできませんのでご注意ください。\n \n \n\n \n \n \n \n \n \n );\n};\n\nexport default PostsShow;\n","import {\n Box,\n CheckboxGroup,\n CloseButton,\n Flex,\n HStack,\n Stack,\n Text,\n useDisclosure,\n} from \"@chakra-ui/react\";\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport ja from \"date-fns/locale/ja\";\nimport React from \"react\";\nimport ReactDatepicker, { registerLocale } from \"react-datepicker\";\nimport \"react-datepicker/dist/react-datepicker.css\";\nimport { Helmet } from \"react-helmet\";\nimport {\n Controller,\n FormProvider,\n useFieldArray,\n useForm,\n useFormContext,\n} from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport {\n HelperImageFile,\n HelperNotImageFile,\n SharedTag,\n} from \"../../../shared/lib/types\";\nimport {\n SharedApprovedCurrentUser,\n SharedCurrentUser,\n} from \"../../shared/lib/types\";\nimport { Button } from \"../../shared/components/atoms\";\nimport Dropzone from \"../../shared/components/atoms/Dropzone\";\nimport { FormLabel, Input, Textarea } from \"../../shared/components/atoms/form\";\nimport Checkbox from \"../../shared/components/atoms/form/Checkbox\";\nimport AddIcon from \"../../shared/components/icons/AddIcon\";\nimport SendIcon from \"../../shared/components/icons/SendIcon\";\nimport AnonymousField from \"./AnonymousField\";\nimport UploadFiles, { UploadFileType, useUploadFiles } from \"../../../shared/components/UploadFiles\";\nimport { anonymousSchema } from \"../lib/schema\";\nimport { uploadFile } from \"../../../shared/lib/uploadFile\";\n\n// jaの型がなぜが合わない\n// 動作としては問題ないので無視\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-expect-error\nregisterLocale(\"ja\", ja);\n\nconst tomorrow = () => new Date(new Date().getTime() + 24 * 60 * 60 * 1000);\nconst afterWeek = () =>\n new Date(new Date().getTime() + 7 * 24 * 60 * 60 * 1000);\n\nconst QuestionnaireForm = () => {\n const { control } = useFormContext();\n const { fields, remove, append } = useFieldArray({\n control,\n name: \"questionnaire.questions\",\n rules: { minLength: 2, maxLength: 10 },\n });\n return (\n <>\n \n \n \n \n 選択肢\n \n \n {fields.map((field, index) => (\n \n (\n \n )}\n />\n {fields.length > 2 && (\n remove(index)} />\n )}\n \n ))}\n \n {fields.length < 10 && (\n \n )}\n \n \n \n アンケート締め切り日時\n \n \n (\n }\n wrapperClassName=\"custom-wrapper\"\n dateFormat=\"yyyy/MM/dd HH:mm\"\n dateFormatCalendar=\"yyyy年 M月\"\n selected={value}\n minDate={tomorrow()}\n maxDate={afterWeek()}\n filterTime={(time) => {\n const tomorrowDate = tomorrow();\n const selectedDate = new Date(time);\n\n return (\n tomorrowDate.getTime() < selectedDate.getTime() &&\n selectedDate.getTime() <= afterWeek().getTime()\n );\n }}\n timeFormat=\"HH:mm\"\n showTimeSelect\n timeCaption=\"時間\"\n />\n )}\n />\n \n まで\n \n \n \n \n >\n );\n};\n\nexport const usePostForm = ({\n isAnonymousField,\n currentUser,\n defaultValues = {\n title: \"\",\n content: \"\",\n tag_ids: [],\n imageFiles: [],\n filesWithoutImage: [],\n },\n}: {\n isAnonymousField: boolean;\n currentUser: SharedCurrentUser;\n defaultValues?: {\n title: string;\n content: string;\n tag_ids: string[];\n imageFiles: HelperImageFile[];\n filesWithoutImage: HelperNotImageFile[];\n };\n}) => {\n const schema = yup.object().shape({\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-expect-error\n title: yup.string().postTitle().required().label(\"タイトル\"),\n content: yup.string().trim().required().label(\"質問内容\"),\n tag_ids: yup.array(yup.string()),\n anonymous: anonymousSchema(\n isAnonymousField && !!currentUser?.is_anonymousable,\n ),\n questionnaire: yup.lazy((value) =>\n value == null\n ? yup.mixed()\n : yup\n .object()\n .optional()\n .shape({\n questions: yup\n .array(\n yup.object().shape({\n content: yup\n .string()\n .trim()\n .required()\n .max(30)\n .label(\"選択肢\"),\n }),\n )\n .required()\n .min(2)\n .max(10),\n voting_ended_at: yup\n .string()\n .trim()\n .required()\n .test(\"voting_ended_at\", (value, ctx) => {\n if (new Date(value) <= tomorrow()) {\n return ctx.createError({\n message:\n \"アンケート締め切り日時は24時間後以降を選択してください\",\n });\n } else if (new Date(value) >= afterWeek()) {\n return ctx.createError({\n message:\n \"アンケート締め切り日時は7日以内を選択してください\",\n });\n } else {\n return true;\n }\n })\n .label(\"アンケート締め切り日時\"),\n }),\n ),\n });\n\n return {\n ...useForm({\n defaultValues: {\n title: defaultValues.title,\n content: defaultValues.content,\n anonymous: \"\",\n tag_ids: defaultValues.tag_ids,\n },\n resolver: yupResolver(schema),\n }),\n defaultValues,\n isAnonymousField,\n };\n};\n\nexport const jsonToFormData = async (data: DataType, uploadFiles: UploadFileType) => {\n const formData = new FormData();\n formData.append(\"post[title]\", data.title);\n formData.append(\"post[content]\", data.content);\n formData.append(\"post[anonymous]\", data.anonymous);\n if (Object.keys(data.tag_ids).length > 0) {\n data.tag_ids.forEach((tag_id: string) => {\n formData.append(\"post[tag_ids][]\", tag_id);\n });\n } else {\n formData.append(\"post[tag_ids][]\", \"\");\n }\n if (data.questionnaire) {\n formData.append(\n \"post[questionnaire][voting_ended_at]\",\n data.questionnaire.voting_ended_at,\n );\n data.questionnaire.questions.forEach((question: { content: string }) => {\n formData.append(\n \"post[questionnaire][questions][][content]\",\n question.content,\n );\n });\n }\n if (uploadFiles.length > 0) {\n for (const file of uploadFiles) {\n if (\"signed_id\" in file) {\n formData.append(\"post[files][]\", file.signed_id);\n } else {\n const { blob } = await uploadFile({ file });\n formData.append(\"post[files][]\", blob.signed_id);\n }\n }\n } else {\n formData.append(\"post[files][]\", \"\");\n }\n\n return formData;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type DataType = Record;\n\nexport const PostForm = ({\n tags,\n withQuestinonaire = false,\n onSubmit,\n currentUser = undefined,\n methods,\n submitLabel,\n}: {\n tags: SharedTag[];\n withQuestinonaire?: boolean;\n onSubmit: (data: DataType, uploadFiles: UploadFileType) => Promise;\n currentUser?: SharedApprovedCurrentUser;\n methods: ReturnType;\n submitLabel: string;\n}) => {\n const questionnaireFormDisclosure = useDisclosure();\n const [uploadFiles, addUploadFiles, removeUploadFile] = useUploadFiles(\n methods.defaultValues.imageFiles,\n methods.defaultValues.filesWithoutImage,\n );\n\n const _onSubmit = async (data: DataType) => {\n await onSubmit(data, uploadFiles);\n };\n const { watch } = methods;\n\n const anonymous = watch(\"anonymous\");\n\n return (\n \n \n \n (\n \n )}\n />\n {methods.isAnonymousField && currentUser?.is_anonymousable && (\n \n )}\n {withQuestinonaire && !questionnaireFormDisclosure.isOpen && (\n {\n methods.setValue(\"questionnaire\", {\n questions: [{ content: \"\" }, { content: \"\" }],\n voting_ended_at: \"\",\n });\n questionnaireFormDisclosure.onOpen();\n }}\n >\n \n \n アンケート形式で質問\n \n \n )}\n {withQuestinonaire && questionnaireFormDisclosure.isOpen && (\n \n \n {\n questionnaireFormDisclosure.onClose();\n methods.setValue(\"questionnaire\", undefined);\n }}\n />\n \n \n \n )}\n (\n \n \n }\n isLoading={methods.formState.isSubmitting}\n >\n \n {submitLabel}\n \n \n \n \n \n );\n};\n","import { Box, Container, Flex, Heading } from \"@chakra-ui/react\";\nimport React, { useState } from \"react\";\nimport { postPath, postsPath, previewPostsPath } from \"../../../routes\";\nimport { Flash, SharedTag } from \"../../shared/lib/types\";\nimport { Button } from \"../shared/components/atoms\";\nimport Background from \"../shared/components/atoms/Background\";\nimport Footer from \"../shared/components/atoms/Footer\";\nimport GoBackLink from \"../shared/components/atoms/GoBackLink\";\nimport Header from \"../shared/components/atoms/Header\";\nimport PostDetail from \"../shared/components/atoms/PostDetail\";\nimport Application from \"../shared/components/layouts/Application\";\nimport {\n filterFilesWithoutImage,\n filterImageFiles,\n UploadFileType,\n} from \"../../shared/components/UploadFiles\";\nimport {\n DataType,\n jsonToFormData,\n PostForm,\n usePostForm,\n} from \"../posts/components/PostForm\";\nimport useRequest from \"../shared/lib/useRequest\";\nimport { SharedApprovedCurrentUser } from \"../shared/lib/types\";\nimport { Helmet } from \"react-helmet\";\nimport { PostPreviewsCreate } from \"./lib/types\";\n\nconst PostsNew = ({\n flash,\n currentUser,\n tags,\n}: {\n flash: Flash;\n currentUser: SharedApprovedCurrentUser;\n tags: SharedTag[];\n}) => {\n const request = useRequest();\n const methods = usePostForm({\n isAnonymousField: true,\n currentUser: currentUser,\n });\n const [showPreview, setShowPreview] = useState(false);\n const [formInputs, setFormInputs] = useState();\n const [previewPost, setPreviewPost] = useState();\n const [files, setFiles] = useState();\n const [isSubmitting, setIsSubmitting] = useState(false);\n\n const onPreview = async (data: DataType, uploadFiles: UploadFileType) => {\n const res = await request(previewPostsPath(), \"POST\", { post: data });\n\n if (res.ok) {\n window.scrollTo(0, 0);\n setPreviewPost({\n ...(await res.json()),\n });\n setFormInputs(data);\n setFiles(uploadFiles);\n setShowPreview(true);\n }\n };\n\n const onSubmit = async () => {\n setIsSubmitting(true);\n const formData = await jsonToFormData(formInputs!, files!);\n const res = await request(postsPath(), \"POST\", formData);\n\n if (res.ok) {\n methods.reset();\n const json = await res.json();\n location.href = postPath(json.code);\n } else {\n setIsSubmitting(false);\n }\n };\n\n return (\n \n \n \n 質問を新規作成 | 建築士のためのコミュニティA-Loop【エーループ】\n \n \n \n \n \n \n \n 質問を新規作成\n \n \n Q&A一覧に戻る\n \n \n \n \n \n {showPreview && (\n \n \n \n 質問内容を確認 |\n 建築士のためのコミュニティA-Loop【エーループ】\n \n \n \n 質問内容を確認\n \n \n ({\n url: URL.createObjectURL(file as File),\n signed_id: \"\",\n })),\n files_without_image: filterFilesWithoutImage(files!).map(\n (file) => ({\n name: file.name,\n signed_id: \"\",\n url: \"\",\n }),\n ),\n }}\n currentUser={currentUser}\n preview\n />\n \n \n \n \n \n \n )}\n \n \n \n \n );\n};\n\nexport default PostsNew;\n","import { Box, Container, Heading } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport { postPath } from \"../../../routes\";\nimport { Flash, SharedTag } from \"../../shared/lib/types\";\nimport Background from \"../shared/components/atoms/Background\";\nimport Footer from \"../shared/components/atoms/Footer\";\nimport GoBackLink from \"../shared/components/atoms/GoBackLink\";\nimport Header from \"../shared/components/atoms/Header\";\nimport Application from \"../shared/components/layouts/Application\";\nimport { UploadFileType } from \"../../shared/components/UploadFiles\";\nimport {\n DataType,\n jsonToFormData,\n PostForm,\n usePostForm,\n} from \"../posts/components/PostForm\";\nimport useFlash from \"../shared/lib/useFlash\";\nimport useRequest from \"../shared/lib/useRequest\";\nimport { SharedApprovedCurrentUser } from \"../shared/lib/types\";\nimport { Post } from \"./lib/types\";\n\nconst PostsEdit = ({\n post,\n flash,\n currentUser,\n tags,\n}: {\n post: Post;\n flash: Flash;\n currentUser: SharedApprovedCurrentUser;\n tags: SharedTag[];\n}) => {\n const request = useRequest();\n const showFlash = useFlash();\n const methods = usePostForm({\n isAnonymousField: false,\n currentUser: currentUser,\n defaultValues: {\n title: post.title,\n content: post.content,\n tag_ids: post.tags.map((tag) => tag.id.toString()),\n imageFiles: post.image_files,\n filesWithoutImage: post.files_without_image,\n },\n });\n\n const onSubmit = async (data: DataType, uploadFiles: UploadFileType) => {\n const formData = await jsonToFormData(data, uploadFiles);\n const res = await request(postPath(post.code), \"PUT\", formData);\n\n if (res.ok) {\n location.href = postPath(post.code);\n } else if (res.status === 404) {\n showFlash({\n error: (\n <>\n 投稿が見つかりませんでした。\n
\n ページを再読み込みしてください。\n >\n ),\n });\n }\n };\n\n return (\n \n \n \n \n \n 質問を編集\n \n \n 戻る\n \n \n \n \n \n \n \n \n );\n};\n\nexport default PostsEdit;\n","import { Flex, FlexProps } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport { AnnouncementsAnnouncementCategory } from \"../../lib/types\";\n\nconst AnnouncementCategory = ({\n category,\n ...props\n}: {\n category: AnnouncementsAnnouncementCategory;\n} & FlexProps) => {\n return (\n \n {category.name}\n \n );\n};\n\nexport default AnnouncementCategory;\n","import { HStack, Link, Text } from \"@chakra-ui/react\";\nimport dayjs from \"dayjs\";\nimport React from \"react\";\nimport { announcementPath } from \"../../../../../routes\";\nimport { SharedAnnouncements } from \"../../lib/types\";\nimport AnnouncementCategory from \"./AnnouncementCategory\";\n\nconst AnnouncementListItem = ({\n announcement,\n}: {\n announcement: SharedAnnouncements[number];\n}) => {\n return (\n \n \n \n {dayjs(\n announcement.publish_started_at ?? announcement.created_at,\n ).format(\"L\")}\n \n \n \n \n \n {announcement.title}\n \n \n \n );\n};\n\nexport default AnnouncementListItem;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst BrandAwareness = (props: IconProps) => (\n \n \n \n \n \n \n \n \n);\n\nexport default BrandAwareness;\n","import { Box, Flex, HStack, Show, Stack } from \"@chakra-ui/react\";\nimport { default as React } from \"react\";\nimport AnnouncementListItem from \"../../shared/components/atoms/AnnouncementListItem\";\nimport { SharedAnnouncements } from \"../../shared/lib/types\";\nimport BrandAwareness from \"../../shared/components/icons/BrandAwareness\";\nimport { Button } from \"../../shared/components/atoms\";\nimport { ChevronRightIcon } from \"../../shared/components/icons\";\nimport { announcementsPath } from \"../../../../routes\";\n\nconst Announcements = ({\n announcements,\n announcementsCount,\n}: {\n announcements: SharedAnnouncements;\n announcementsCount: number;\n}) => {\n return (\n \n \n \n \n \n お知らせ\n \n \n \n {announcementsCount > 3 && (\n }\n variant=\"outline\"\n iconSpacing={1}\n as=\"a\"\n href={announcementsPath()}\n >\n すべてのお知らせを見る\n \n )}\n \n \n \n {announcements.map((announcement) => (\n \n ))}\n \n \n \n }\n variant=\"outline\"\n iconSpacing={1}\n as=\"a\"\n href={announcementsPath()}\n >\n すべてのお知らせを見る\n \n \n \n \n );\n};\n\nexport default Announcements;\n","import {\n Box,\n Flex,\n HStack,\n Heading,\n Link\n} from \"@chakra-ui/react\";\nimport { default as React } from \"react\";\nimport {\n Button\n} from \"../../shared/components/atoms\";\nimport {\n ChevronRightIcon\n} from \"../../shared/components/icons\";\n\nconst TitleWithLinkButton = ({\n title,\n buttonLabel,\n href,\n}: {\n title: string;\n buttonLabel: string;\n href: string;\n}) => {\n return (\n \n {title}\n \n \n );\n};\n\nexport default TitleWithLinkButton;\n","import { Box, Flex } from \"@chakra-ui/react\";\nimport React, { ReactNode } from \"react\";\nimport todetailArrowIcon from \"./assets/todetail-arrow.svg\";\n\nconst IndexLink = ({\n children,\n href,\n}: {\n children: ReactNode;\n href: string;\n}) => {\n return (\n \n \n \n {children}\n \n \n \n \n );\n};\n\nexport default IndexLink;\n","import dayjs from \"dayjs\";\nimport { SharedAnnouncements } from \"../../shared/lib/types\";\n\nexport const announcementPublishedAt = (\n announcement: SharedAnnouncements[number],\n) => {\n return dayjs(\n announcement.publish_started_at ?? announcement.created_at,\n ).format(\"L\");\n};\n","import { Box, Flex, List, ListItem } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport IndexLink from \"./IndexLink\";\nimport { SharedAnnouncements } from \"../../shared/lib/types\";\nimport AnnouncementCategory from \"../../shared/components/atoms/AnnouncementCategory\";\nimport { announcementPublishedAt } from \"../lib/announcementPublishedAt\";\nimport { announcementPath, announcementsPath } from \"../../../../routes\";\n\nconst Announcement = ({\n announcements,\n}: {\n announcements: SharedAnnouncements;\n}) => {\n return (\n \n \n \n お知らせ\n \n \n Information\n \n \n \n \n {announcements.map((announcement) => (\n \n {announcementPublishedAt(announcement)}\n \n \n \n \n {announcement.title}\n \n \n ))}\n
\n \n お知らせ一覧へ\n \n \n \n );\n};\n\nexport default Announcement;\n","import { Box, Flex } from \"@chakra-ui/react\";\nimport React, { ReactNode, useEffect, useRef } from \"react\";\nimport { loadDefaultJapaneseParser } from \"budoux\";\n\nconst parser = loadDefaultJapaneseParser();\n\nconst FeatureCard = ({\n index,\n imgSrc,\n heading,\n content,\n}: {\n index: number;\n imgSrc: string;\n heading: string;\n content: ReactNode;\n}) => {\n const ref = useRef(null);\n useEffect(() => {\n if (ref.current) {\n parser.applyToElement(ref.current);\n }\n }, []);\n return (\n \n \n \n Feature\n \n \n \n \n \n 0{index}\n \n \n
\n \n \n {heading}\n \n \n {content}\n \n \n \n );\n};\n\nexport default FeatureCard;\n","import { Box, Flex, HStack, Link } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport FeatureCard from \"./FeatureCard\";\nimport illust01 from \"./assets/illust01.svg\";\nimport illust02 from \"./assets/illust02.svg\";\nimport illust03 from \"./assets/illust03.svg\";\nimport newPageIcon from \"./assets/newpage.svg\";\nimport fourSquareIcon from \"./assets/four-square.svg\";\nimport featuresBackground from \"./assets/features-background.svg\";\n\nconst Features = () => {\n return (\n \n \n \n \n Features\n \n \n \n \n A-Loop ってどんなサービス ?\n \n \n \n \n \n 意匠、構造、設備など多種多様な建築士と、質問やコメントを通して情報交換ができる\n \n 回答率は88%!(2025/01/29時点)\n \n >\n }\n />\n \n \n \n 設計、法改正、業務効率化など、トレンドの勉強会動画が{\" \"}\n \n 24 時間\n \n いつでも視聴可能\n >\n }\n />\n \n \n \n 業界シェアNo.1\n
1976年設立の「建築構造計算ソフトウェア」を提供する\n \n ユニオンシステム\n \n \n が運営\n >\n }\n />\n \n \n \n \n );\n};\n\nexport default Features;\n","import React, { ReactNode } from \"react\";\nimport logoImage from \"./assets/logo.svg\";\nimport joinIcon3 from \"./assets/join-icon3.svg\";\nimport loginIcon2 from \"./assets/login-icon2.svg\";\nimport arrowIcon from \"./assets/arrow.svg\";\nimport {\n Box,\n Flex,\n HStack,\n List,\n ListItem,\n Show,\n Stack,\n} from \"@chakra-ui/react\";\nimport { ChevronRightIcon } from \"../../shared/components/icons\";\nimport {\n newContactPath,\n newUserDatabaseAuthenticationRegistrationPath,\n newUserDatabaseAuthenticationSessionPath,\n postsPath,\n privacyPolicyPath,\n studyGroupsPath,\n termsOfServicePath,\n} from \"../../../../routes\";\n\nconst Footer = () => {\n return (\n \n \n \n \n \n 建築士のためのコミュニティ\n \n \n \n \n \n \n \n サービス\n \n \n ・Q&A\n \n \n ・勉強会\n \n \n \n \n お問い合わせ\n \n \n 個人情報保護方針\n \n \n 利用規約\n \n \n \n \n \n 無料会員登録\n \n \n 会員ログイン\n \n \n \n \n \n \n {[\n {\n label: \"無料会員登録\",\n href: newUserDatabaseAuthenticationRegistrationPath(),\n },\n {\n label: \"会員ログイン\",\n href: newUserDatabaseAuthenticationSessionPath(),\n },\n { label: \"Q&A\", href: postsPath({ order: \"comments\" }) },\n { label: \"勉強会\", href: studyGroupsPath() },\n { label: \"お問い合わせ\", href: newContactPath() },\n ].map((item) => (\n \n \n {item.label}\n \n \n \n \n \n ))}\n
\n \n \n 個人情報保護方針\n \n \n \n \n \n 利用規約\n \n \n \n \n \n \n \n \n );\n};\n\nconst LinkButton = ({\n icon,\n children,\n href,\n}: {\n icon: string;\n children: ReactNode;\n href: string;\n}) => {\n return (\n \n \n {children}\n \n \n );\n};\nexport default Footer;\n","import {\n Box,\n CloseButton,\n Drawer,\n DrawerBody,\n DrawerContent,\n DrawerOverlay,\n Flex,\n HStack,\n List,\n ListItem,\n Show,\n useDisclosure,\n} from \"@chakra-ui/react\";\nimport React, { useRef } from \"react\";\nimport {\n announcementsPath,\n newContactPath,\n newUserDatabaseAuthenticationRegistrationPath,\n newUserDatabaseAuthenticationSessionPath,\n postsPath,\n studyGroupsPath,\n} from \"../../../../routes\";\nimport { ChevronRightIcon } from \"../../shared/components/icons\";\nimport DehazeIcon from \"../../shared/components/icons/DehazeIcon\";\nimport { SharedAnnouncements } from \"../../shared/lib/types\";\nimport { announcementPublishedAt } from \"../lib/announcementPublishedAt\";\nimport joinIcon2 from \"./assets/join-icon2.svg\";\nimport loginIcon from \"./assets/login-icon.svg\";\nimport logoImage from \"./assets/logo.svg\";\nimport todetailArrowIcon from \"./assets/todetail-arrow.svg\";\nimport toregistrationArrow from \"./assets/toregistration-arrow.svg\";\n\nconst Header = ({\n latestAnnouncement,\n}: {\n latestAnnouncement?: SharedAnnouncements[number];\n}) => {\n const { isOpen, onOpen, onClose } = useDisclosure();\n const ref = useRef(null);\n\n return (\n \n \n \n \n \n \n Q&A\n \n \n 勉強会\n \n \n \n \n お知らせ\n \n {latestAnnouncement && (\n \n {announcementPublishedAt(latestAnnouncement)} 更新\n \n )}\n \n \n \n \n \n ログイン\n \n \n \n \n {isOpen ? (\n \n ) : (\n \n \n \n )}\n \n \n \n \n \n {[\n { label: \"Q&Aコミュニティ\", href: postsPath() },\n { label: \"勉強会\", href: studyGroupsPath() },\n { label: \"お問い合わせ\", href: newContactPath() },\n ].map(({ label, href }) => (\n \n \n {label}\n \n \n \n ))}\n
\n \n \n \n 無料会員登録\n \n \n \n \n 会員ログイン\n \n \n \n \n \n \n \n \n );\n};\n\nexport default Header;\n","import { Box, BoxProps } from \"@chakra-ui/react\";\nimport React, { ReactNode } from \"react\";\nimport ourServiceLeftTopImg from \"./assets/our-service-left-top.svg\";\n\nconst OurServiceCard = ({\n title,\n subtitle,\n description,\n children,\n ...props\n}: {\n title: ReactNode;\n subtitle: ReactNode;\n description: ReactNode;\n children: ReactNode;\n props?: BoxProps;\n}) => {\n return (\n \n \n \n \n {title}\n \n \n {subtitle}\n \n \n \n {description}\n \n {children}\n \n );\n};\n\nexport default OurServiceCard;\n","import { Box, Flex, HStack, Stack, Text } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport { Tag } from \"../../shared/components/atoms\";\nimport PostResourceAvatar from \"../../shared/components/atoms/PostResourceAvatar\";\nimport PostResourceDisplayName from \"../../shared/components/atoms/PostResourceDisplayName\";\nimport ChatBubbleIcon from \"../../shared/components/icons/ChatBubbleIcon\";\nimport { SharedPost } from \"../../shared/lib/types\";\nimport { postPath } from \"../../../../routes\";\n\nconst PostCard = ({\n post,\n postCodeContents,\n}: {\n post: SharedPost;\n postCodeContents: Record;\n}) => {\n return (\n \n \n アンケート受付中\n \n \n \n \n {post.title}\n \n \n \n \n {post.tags.map((tag) => (\n {tag.name}\n ))}\n \n \n {postCodeContents[post.code] ?? post.content}\n \n \n \n \n \n \n \n \n \n \n {post.comments_count}\n \n \n \n \n \n \n );\n};\n\nexport default PostCard;\n","import { Box, Flex, HStack, Show, useBreakpointValue } from \"@chakra-ui/react\";\nimport React, { ReactNode, useRef, useState } from \"react\";\nimport { Helmet } from \"react-helmet\";\nimport \"swiper/css\";\nimport \"swiper/css/pagination\";\nimport { Pagination } from \"swiper/modules\";\nimport { Swiper, SwiperSlide } from \"swiper/react\";\nimport { Swiper as TSwiper } from \"swiper/types\";\nimport slideNext from \"./assets/slide-next.svg\";\nimport slidePrev from \"./assets/slide-prev.svg\";\nimport PostCard from \"./PostCard\";\nimport { SharedPost } from \"../../shared/lib/types\";\n\nconst PostCardsSlider = ({\n posts,\n postCodeContents,\n}: {\n posts: SharedPost[];\n postCodeContents: Record;\n}) => {\n const [swiper, setSwiper] = useState();\n const ref = useRef(null);\n const slidesPerView = useBreakpointValue({ base: 1, md: 2, lg: 3 });\n\n return (\n \n \n \n \n \n \n swiper?.slidePrev()}>\n \n \n \n \n {posts.map((post) => (\n \n \n \n ))}\n \n \n swiper?.slideNext()}>\n \n \n \n \n \n \n );\n};\n\nconst NavButton = ({\n onClick,\n children,\n}: {\n onClick: () => void;\n children: ReactNode;\n}) => {\n return (\n \n \n {children}\n \n \n );\n};\n\nexport default PostCardsSlider;\n","import React from \"react\";\nimport OurServiceCard from \"./OurServiceCard\";\nimport { Box, Flex, useBreakpointValue } from \"@chakra-ui/react\";\nimport ourServiceQaImgPc from \"./assets/our-service-qa.svg\";\nimport ourServiceQaImgSp from \"./assets/our-service-qa-sp.svg\";\nimport { SharedPost } from \"../../shared/lib/types\";\nimport PostCardsSlider from \"./PostCardsSlider\";\nimport IndexLink from \"./IndexLink\";\nimport { postsPath } from \"../../../../routes\";\n\nconst QAOurServiceCard = ({\n posts,\n postCodeContents,\n}: {\n posts: SharedPost[];\n postCodeContents: Record;\n}) => {\n const ourServiceQaImg = useBreakpointValue({\n base: ourServiceQaImgSp,\n lg: ourServiceQaImgPc,\n });\n return (\n \n Q\n \n &\n \n \n A\n \n >\n }\n subtitle=\"Q&Aコミュニティ\"\n description={\n <>\n \n 設計の専門的な疑問、事務所の運営についてなど気軽に質問ができます。\n \n \n [ 質問数:207 回答コメント数:401(2025/01/30 時点)]\n \n >\n }\n >\n \n \n \n \n \n \n Q&A\n \n \n \n );\n};\n\nexport default QAOurServiceCard;\n","import { Box } from \"@chakra-ui/react\";\nimport React, { useRef } from \"react\";\nimport { Helmet } from \"react-helmet\";\nimport \"swiper/css\";\nimport \"swiper/css/pagination\";\nimport { Pagination } from \"swiper/modules\";\nimport { Swiper, SwiperSlide } from \"swiper/react\";\nimport { SharedStudyGroup } from \"../../shared/lib/types\";\nimport { studyGroupPath } from \"../../../../routes\";\n\nconst StudyGroupsSlider = ({\n studyGroups,\n}: {\n studyGroups: SharedStudyGroup[];\n}) => {\n const ref = useRef(null);\n\n return (\n \n \n \n \n console.log(ref.current)}\n modules={[Pagination]}\n pagination={{\n el: \".siwper-pagination\",\n }}\n >\n {studyGroups.map((studyGroup) => (\n \n \n \n \n \n ))}\n \n \n \n );\n};\n\nexport default StudyGroupsSlider;\n","import { Box, Flex, Show, useBreakpointValue } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport { SharedStudyGroup } from \"../../shared/lib/types\";\nimport ourServiceStudyGroupImg from \"./assets/our-service-study-group.svg\";\nimport ourServiceStudyGroupSpImage from \"./assets/our-service-study-group-sp.svg\";\nimport IndexLink from \"./IndexLink\";\nimport OurServiceCard from \"./OurServiceCard\";\nimport StudyGroupsSlider from \"./StudyGroupsSlider\";\nimport { studyGroupPath, studyGroupsPath } from \"../../../../routes\";\n\nconst StudyGroupOurServiceCard = ({\n studyGroups,\n}: {\n studyGroups: SharedStudyGroup[];\n}) => {\n const ourServiceStudyGroupImage = useBreakpointValue({\n base: ourServiceStudyGroupSpImage,\n lg: ourServiceStudyGroupImg,\n });\n return (\n \n \n \n \n {studyGroups.map((studyGroup) => (\n \n \n \n ))}\n \n \n \n \n \n \n \n \n 勉強会一覧へ\n \n \n \n \n );\n};\n\nexport default StudyGroupOurServiceCard;\n","import { Box, Flex, HStack, useBreakpointValue } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport ourServiceBusinessMatchingMainImage from \"./assets/our-service-business-matching-main.png\";\nimport ourServiceBusinessMatchingImg from \"./assets/our-service-business-matching.svg\";\nimport ourServiceBusinessMatchingSpImg from \"./assets/our-service-business-matching-sp.svg\";\nimport OurServiceCard from \"./OurServiceCard\";\n\nconst BusinessMatchingOurServiceCard = () => {\n const ourServiceBusinessMatchingImage = useBreakpointValue({\n base: ourServiceBusinessMatchingSpImg,\n lg: ourServiceBusinessMatchingImg,\n });\n\n return (\n \n \n 受発注\n \n 2025年β版リリース予定\n \n \n >\n }\n description=\"設計事務所や建設会社、ハウスメーカー、工務店などの支援ツールとして、設計案件を依頼したい企業と受注したい企業をマッチングします。\"\n >\n \n \n \n \n \n \n \n );\n};\n\nexport default BusinessMatchingOurServiceCard;\n","import { Box, HStack, Stack } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport fourSquareIcon from \"./assets/four-square.svg\";\nimport QAOurServiceCard from \"./QAOurServiceCard\";\nimport { SharedPost, SharedStudyGroup } from \"../../shared/lib/types\";\nimport StudyGroupOurServiceCard from \"./StudyGroupOurServiceCard\";\nimport BusinessMatchingOurServiceCard from \"./BusinessMatchingOurServiceCard\";\n\nconst OurService = ({\n posts,\n studyGroups,\n postCodeContents,\n}: {\n posts: SharedPost[];\n studyGroups: SharedStudyGroup[];\n postCodeContents: Record;\n}) => {\n return (\n \n \n \n Our Service\n \n \n \n \n A-Loop でできること\n \n \n \n \n \n \n \n \n \n );\n};\n\nexport default OurService;\n","import { Box, Show } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport joinIcon from \"./assets/join-icon.svg\";\nimport { newUserDatabaseAuthenticationRegistrationPath } from \"../../../../routes\";\n\nconst RegistrationLink = () => {\n return (\n \n \n 無料で\n
\n 参加する\n \n \n \n );\n};\n\nexport default RegistrationLink;\n","import {\n Box,\n Flex,\n HStack,\n Show,\n Text,\n useBreakpointValue,\n} from \"@chakra-ui/react\";\nimport React from \"react\";\nimport { newUserDatabaseAuthenticationRegistrationPath } from \"../../../routes\";\nimport {\n SharedAnnouncements,\n SharedPost,\n SharedStudyGroup,\n} from \"../shared/lib/types\";\nimport { BgCityDubble } from \"../svg\";\nimport Announcement from \"./components/Announcement\";\nimport joinIcon2 from \"./components/assets/join-icon2.svg\";\nimport mainVisualImageSp from \"./components/assets/main-visual-sp.png\";\nimport mainVisualImagePc from \"./components/assets/main-visual.png\";\nimport mainVisual2Image from \"./components/assets/main-visual2.svg\";\nimport ourServiceBgImage from \"./components/assets/our-service-bg.png\";\nimport toregistrationArrow from \"./components/assets/toregistration-arrow.svg\";\nimport Features from \"./components/Features\";\nimport Footer from \"./components/Footer\";\nimport Header from \"./components/Header\";\nimport OurService from \"./components/OurService\";\nimport RegistrationLink from \"./components/RegistrationLink\";\n\nconst NewHomeIndex = ({\n posts,\n studyGroups,\n announcements,\n postCodeContents,\n}: {\n posts: SharedPost[];\n studyGroups: SharedStudyGroup[];\n announcements: SharedAnnouncements;\n postCodeContents: Record;\n}) => {\n const mainVisualImage = useBreakpointValue({\n base: mainVisualImageSp,\n lg: mainVisualImagePc,\n });\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n 建築士のための\n \n \n 学び\n \n \n と\n \n \n つながり\n \n \n の\n \n \n コミュニティ\n \n \n \n \n \n 建築士同士のつながりで情報交換・\n \n 知識共有が行える場を提供し、\n
\n \n これからも成長し続けたい\n \n 建築士を応援します。\n \n \n \n \n 無料で参加する\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n 運営会社:ユニオンシステム株式会社\n \n \n \n ⒸUNION SYSTEM Inc. All rights reserved.\n \n \n \n \n \n 無料で参加する\n \n \n \n \n \n );\n};\n\nexport default NewHomeIndex;\n","import {\n Avatar,\n Box,\n Container,\n Flex,\n Grid,\n HStack,\n Heading,\n Image,\n Link,\n ListItem,\n Stack,\n StackProps,\n Text,\n UnorderedList,\n VStack,\n useBreakpointValue,\n} from \"@chakra-ui/react\";\nimport { default as React, ReactNode, useEffect } from \"react\";\nimport { postsPath, studyGroupPath, studyGroupsPath } from \"../../../routes\";\nimport { Flash } from \"../../shared/lib/types\";\nimport {\n Button,\n Footer,\n Header,\n PostSummaryCard,\n Tag,\n} from \"../shared/components/atoms\";\nimport {\n CalendarMonthIcon,\n ChevronRightIcon,\n ScheduleIcon,\n} from \"../shared/components/icons\";\nimport Application from \"../shared/components/layouts/Application\";\nimport { viewEndAt, viewingTime } from \"../shared/lib/studyGroupCardUtil\";\nimport {\n SharedAnnouncements,\n SharedPost,\n SharedStudyGroup,\n SharedCurrentUser,\n} from \"../shared/lib/types\";\nimport {\n BgCityDubble,\n BgCitySingle,\n BgCityTriple,\n HomeLeftDiagonalLine,\n HomeQAndA,\n HomeRightDiagonalLine,\n HomeStudyGroup,\n LogoSVG,\n} from \"../svg\";\nimport Announcements from \"./components/Announcements\";\nimport TitleWithLinkButton from \"./components/TitleWithLinkButton\";\nimport NewHomeIndex from \"./NewHomeIndex\";\n\nconst RecommendContent = ({\n icon,\n title,\n subtitle,\n summary,\n features,\n linkLabel,\n buttonHref,\n ...props\n}: {\n icon: ReactNode;\n title: string;\n subtitle: string;\n summary: ReactNode;\n features: ReactNode;\n linkLabel: string;\n buttonHref: string;\n} & StackProps) => {\n return (\n \n {icon}\n \n {title}\n \n \n {subtitle}\n \n \n {summary}\n \n \n {features}\n \n \n \n );\n};\n\nconst ResponsiveBgCityImage = () => {\n const responsiveBgCity = useBreakpointValue({\n base: BgCityDubble,\n \"2xl\": BgCityTriple,\n });\n\n return (\n \n );\n};\n\nconst NotUserContent = () => {\n return (\n \n \n \n \n \n \n \n A-Loopは、建築士同士がつながるコミュニティサービスです。\n
\n 設計業務のスキルアップを図るためのオンライン勉強会や建築士同士で興味関心のある\n
\n テーマについて質問や意見交換できるオンラインコミュニティなどを提供します。\n \n \n \n \n \n \n \n \n \n \n \n \n \n はじめての方へ\n \n \n おすすめコンテンツ\n \n \n \n \n \n }\n title=\"Q&A\"\n subtitle=\"Q&A\"\n summary={\n <>\n 設計に関する疑問や関心のあることについて\n \n 質問やコメントができます。\n >\n }\n features={\n <>\n \n ・建築士同士の情報交換で問題を効率的に解決\n \n \n ・実名か匿名かを選択して質問・コメントが可能\n \n >\n }\n linkLabel=\"Q&Aを見る\"\n buttonHref={postsPath()}\n />\n }\n title=\"勉強会\"\n subtitle=\"STUDY\"\n summary=\"講師の方をお招きした、建築についての様々な講義の動画が閲覧できます。\"\n features={\n <>\n ・建築士が主催する勉強会へ参加可能\n ・いつでも、どこでも動画視聴が可能\n >\n }\n linkLabel=\"動画を見に行く\"\n buttonHref=\"#study-groups\"\n />\n \n \n \n \n \n \n \n \n );\n};\n\nconst HomeIndex = ({\n currentUser,\n flash,\n studyGroups,\n posts,\n announcements,\n announcementsCount,\n postCodeContents,\n}: {\n currentUser: SharedCurrentUser;\n flash: Flash;\n studyGroups: SharedStudyGroup[];\n posts: SharedPost[];\n announcements: SharedAnnouncements;\n announcementsCount: number;\n postCodeContents: null | Record;\n}) => {\n return (\n <>\n {currentUser == null ? (\n \n \n \n ) : (\n \n \n \n )}\n >\n );\n};\n\nconst BeforeLoginContent = ({\n posts,\n studyGroups,\n announcements,\n postCodeContents,\n}: {\n posts: SharedPost[];\n studyGroups: SharedStudyGroup[];\n announcements: SharedAnnouncements;\n postCodeContents: Record;\n}) => {\n return (\n \n );\n};\n\nconst AfterLoginContent = ({\n currentUser,\n studyGroups,\n posts,\n announcements,\n announcementsCount,\n}: {\n currentUser: SharedCurrentUser;\n studyGroups: SharedStudyGroup[];\n posts: SharedPost[];\n announcements: SharedAnnouncements;\n announcementsCount: number;\n}) => {\n useEffect(() => {\n if (location.hash !== \"\") {\n const tmp = location.hash;\n location.hash = \"\";\n location.hash = tmp;\n }\n }, []);\n\n useEffect(() => {\n if (location.search === \"?error=1\") {\n throw new Error();\n }\n }, []);\n return (\n <>\n \n \n {currentUser == null && }\n {currentUser != null && announcements.length !== 0 && (\n \n \n \n )}\n \n \n \n {currentUser == null && announcements.length !== 0 && (\n \n \n \n )}\n \n {currentUser != null && (\n \n 設計に関する疑問や関心のあることについて質問やコメントができます。\n
\n ・建築士同士の情報交換で問題を効率的に解決\n
\n ・実名か匿名かを選択して質問・コメントが可能\n
\n \n )}\n \n \n {posts.map((post) => (\n \n ))}\n \n \n \n \n \n \n \n \n \n {studyGroups.map((studyGroup) => (\n \n ))}\n \n \n \n \n \n \n \n \n \n >\n );\n};\n\nexport const StudyGroupCard = ({\n studyGroup,\n}: {\n studyGroup: SharedStudyGroup;\n}) => {\n return (\n \n \n \n \n \n \n \n 視聴可能\n \n \n \n \n \n {viewEndAt(studyGroup)}\n \n \n \n \n \n {viewingTime(studyGroup)}\n \n \n \n \n \n \n \n {studyGroup.tags.map((tag) => (\n {tag.name}\n ))}\n \n {studyGroup.title}\n \n \n \n {studyGroup.instructor_info.name}\n \n \n \n \n \n );\n};\n\nexport default HomeIndex;\n","import { Box, BoxProps } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport { BgCityDubble, BgCitySingle, BgCityTriple } from \"../../../svg\";\n\nconst BackgroundBuildingImage = (props: BoxProps) => {\n return (\n \n );\n};\n\nexport default BackgroundBuildingImage;\n","import { Stack, Text } from \"@chakra-ui/react\";\nimport React from \"react\";\n\nexport const BracketsList = ({\n contents = [],\n offset = 0,\n}: {\n contents: string[];\n offset?: number;\n}) => {\n return (\n \n {contents.map((content, index) => (\n \n {content}\n \n ))}\n \n );\n};\n\nexport default BracketsList;\n","import React from \"react\";\nimport BackgroundBuildingImage from \"../shared/components/atoms/BackgroundBuildingImage\";\nimport Background from \"../shared/components/atoms/Background\";\nimport {\n Box,\n Container,\n Heading,\n VStack,\n Text,\n OrderedList,\n ListItem,\n UnorderedList,\n Stack,\n} from \"@chakra-ui/react\";\nimport ShadowCard from \"../shared/components/atoms/ShadowCard\";\nimport BracketsList from \"../shared/components/atoms/BracketsList\";\n\nconst TermsOfService = () => {\n return (\n <>\n \n \n \n \n \n 「A-Loop」利用規約\n \n \n \n 本規約は、ユニオンシステム株式会社(以下「弊社」と称します)が提供する建築士向けオンラインコミュニティサービス「A-Loop」(以下「本サービス」と称します)について、弊社と本サービスを利用いただくすべてのユーザー(以下、「ユーザー」と称します)との間で適用される共通の条件を定めるものとします。\n \n \n \n 第1条(適用範囲及び変更)\n \n \n \n 本規約は、本サービスを利用するすべてのユーザーに適用され、ユーザーは本利用規約を遵守の上、本サービスを利用するものとします。\n \n \n 弊社は、ユーザーに事前の通知をすることなく、本利用規約を変更することができます。本利用規約を変更する場合、弊社はユーザーに対して変更後の規約を公表又は通知するものとし、当該公表又は通知のいずれかがなされた後、ユーザーが本サービスを利用した場合又は会員が1か月以内に退会手続きを行わなかった場合、ユーザーは変更後の規約に同意したものとみなされます。ユーザーは、本規約を誠実に遵守するものとします。\n \n \n \n \n \n 第2条(会員登録)\n \n \n 本利用規約に同意の上で、以下の各号に定める他、弊社所定の方法により会員登録をした方を「会員」とします。\n \n \n \n 会員登録申請は必ず本サービスを利用する個人が行わなければならず、代理人による登録申請は認められません。\n \n \n 会員登録申請に際しては、真実、正確かつ最新の情報を記載することとし、虚偽の記載が判明した場合、会員登録が抹消されることがあります。\n \n \n 弊社は、弊社の基準に従い、登録希望者の登録の可否を判断し、弊社が認める場合に限り登録希望者の登録を認めることとします。\n \n \n \n \n \n 第3条(本サービスの概要)\n \n \n \n 本サービスは、ユーザーが自身の情報を登録し、本サービスの他の会員に対して登録情報を提供し、また他の会員に関する情報を閲覧できるサービスです。会員登録していない場合、本サービス上の機能を利用することはできません。\n \n \n 会員は、自身の登録情報を編集でき、本サービス上で提供する自身に関するすべての情報(以下「会員情報等」と称します)の内容の正確性、真実性、適法性等について、自らが責任を負うものとします。\n \n \n 会員は、本サービス上の機能によって、勉強会の視聴やコメントを行うことができます。\n \n \n 会員は、本サービス上の機能によって、投稿やコメント、ファイルの共有を行うことができます。\n \n \n 会員は、登録されたメールアドレスでお知らせの通知やメールマガジンサービスを受け取ることができます。\n \n \n 会員は、本サービスの利用に際し、自己の判断と責任において他の会員に対応するものとし、弊社はユーザーに対して、本サービスの利用に伴う結果に関する一切の責任を負わないものとします。\n \n \n ユーザーは、本サービスの利用に際してダウンロードその他の方法によりファイル等を自身のコンピューター等にダウンロードする場合、自身が保有する情報の消滅、改変、機器の故障、損傷等が生じないよう十分な注意を払うものとし、かかる損害について弊社は一切の責任を負いません。\n \n \n 弊社は、本サービスの提供・運営のために、本サービスにおけるユーザーの行為、登録データ等を閲覧できるものとします。ただし、弊社はユーザーの行動を監視する義務を負うものではありません。\n \n \n \n \n \n 第4条(ユーザーに関する情報の開示と利用)\n \n \n \n ユーザーが会員登録時及び本サービスの利用に際して弊社に開示した当該ユーザーに関する会員情報等のうち、投稿やコメント等において本サービス上で既に開示済みの情報以外の会員情報等については、原則として、事前の同意なく、弊社は他のユーザーに対して開示しません。ただし、本利用規約又は弊社の個人情報保護方針に定める場合、その他法令に基づく公的機関からの照会、又は弊社が法令によって開示義務を負う場合等はその限りではありません。\n \n \n 弊社は、本条に基づく情報の開示に関してユーザーに発生した損害について一切の責任を負いません。\n \n \n 前各項の他、会員情報等は、弊社の本利用規約及び個人情報保護方針に従い、弊社が管理します。\n \n \n \n \n \n 第5条(業務の委託)\n \n \n サービスの提供にあたり、業務の全部及び一部を第三者に委託する場合があります。\n
\n 第三者に委託する場合、当該第三者に対して、弊社の個人情報保護方針と同等の義務を課し、当該第三者は委託された業務の範囲において、本利用規約に則って業務を行うこととします。\n \n \n \n \n 第6条(会員の義務)\n \n \n \n 会員は、自らの意思と責任をもって、本サービスを利用するものとします。\n \n \n 会員は、本サービス上で常に最新で正確な情報を提供するものとします。\n \n \n 会員は、本サービスを情報の発信を行う場として利用するものとし、責任をもって行動します。\n \n \n \n \n \n 第7条(会員情報等の変更)\n \n \n 会員は、登録した事項に変更が生じた場合には、弊社の定める方法に従い、速やかに変更登録を行うものとします。変更登録がなされなかったことにより生じた損害について、弊社は一切責任を負いません。\n \n \n \n \n 第8条(ID及びパスワードの管理)\n \n \n \n 弊社は、会員に対して、本サービスを利用するために必要となるID及びパスワード(以下「ID等」と称します)を発行します。会員はID等を第三者に貸与し、譲渡し、使用させたり処分したりしてはならないものとします。\n \n \n 会員は、ID等について善良な管理者の注意をもって厳重に管理、保管するものとします。弊社は、ID等を使用して本サービスが利用された場合は、会員本人による利用とみなし、ID等の盗用、不正使用、その他会員本人以外の第三者による利用であっても、当該利用に起因してユーザー又は第三者に生じる損害及び結果について、損害賠償責任その他一切の責任を負わないものとします。\n \n \n \n \n \n 第9条(特定ユーザーに対するサービスの停止・会員資格の剥奪)\n \n \n 本サービスの利用について、ユーザーの故意又は過失に関係なく以下の各項に該当する、又は該当するおそれがあると弊社が判断する行為は禁止されます。かかる禁止事項に違反した場合は、当該違反にかかる投稿等された情報の削除、変更の他、本サービスの利用停止、会員資格の剥奪(再登録の禁止を含む)等、弊社が適当と認める措置をとる場合があります。その場合、削除・変更、利用停止、会員資格の剥奪等の措置に関する投稿・苦情は一切受け付けておらず、かかる措置に関し、弊社は一切責任を負わず、その理由についてユーザーに説明する義務を負いません。\n \n \n \n 反社会的勢力又は反社会的活動に関する行為。\n \n \n 法令又は公序良俗に違反する、又は他人に不利益を与える行為。\n \n \n 犯罪行為又はこれを予告、関与、助長する行為。\n \n \n 特定の個人又は企業に対する誹謗中傷をする行為。\n \n \n 虚偽又は誤解を招くような内容を含む情報を掲載、登録する等の行為。\n \n \n 弊社、他のユーザー又は第三者の著作権、商標権、企業秘密等の知的財産権、肖像権、プライバシーの権利、名誉、その他の権利又は利益を侵害する行為。\n \n 会員の個人情報を第三者に漏えいする行為。\n \n 民族、人種、性別、年齢等による差別につながる表現の掲載行為。\n \n \n 商材の営業目的、勧誘、採用を目的とする情報提供活動。\n \n \n 交際相手を求める行為や出会いを目的とする又は宿泊や居住する相手を探そうとする行為。\n \n \n その他社会的に容認されないと判断される出会い行為。\n \n \n 会員又は弊社に対する、営業目的でのメール送信、その他の営業行為。\n \n \n 出会い系サイト、アダルト関連のすべての業務に関する内容を含む表現の掲載行為。\n \n \n 弊社、他のユーザー又は第三者に損害を生じさせるおそれのある目的又は方法で本サービスを利用する、又は利用しようとする行為。\n \n \n 会員の個人情報を収集、蓄積する行為、又はこれらの行為をしようとする行為。\n \n \n 自動化された手段(ロボット、ボットネット、スクレーパ等)を使用して本サービスにアクセスする行為。\n \n 会員に対し継続的に勧誘又は要求する行為。\n \n コンピューターウイルス、その他の有害なコンピュータープログラムを含む情報を送信する行為。\n \n \n ねずみ講、チェーンメール、MLM(マルチレベルマーケティング)、リードメール等の第三者を勧誘する内容の情報を掲載又は送信する行為。\n \n \n アフィリエイトや招待することでポイント等の利益が発生するサイトへ誘導する情報(ただし、弊社が事前に許可したものを除きます)を掲載又は送信する行為。\n \n \n 通常利用の範囲を超えてサーバーに負担をかける行為、及びそれを助長するような行為、その他本サービスの運営・提供又は他のユーザーによる本サービスの利用を妨害する、又はそれらに支障をきたすおそれのある行為。\n \n 運営者に成りすます行為。\n \n 本サービスに関し、利用できる情報を改ざんする行為。\n \n \n 本サービス上の画像等を含めた情報を無断使用、編集、複製、転載する行為。\n \n \n 弊社又は他者のサーバーに負担をかける行為、又は本サービスの運営やネットワーク・システムに支障を与える行為、又はこれらのおそれのある行為。\n \n \n 登録又は投稿をすることで第三者に成りすます行為。\n \n \n ユーザー本人に許可を受けたか否かを問わず、ユーザー本人以外が登録情報(メールアドレスやパスワード等)を利用して本サービスを利用する行為。\n \n \n 会員資格を第三者に利用させる又は譲渡する行為。\n \n \n 1つの会員資格を複数人で利用する行為又は1人で複数の会員資格を保有する行為。\n \n \n 弊社又はユーザーが所属する業界団体の内部規則に違反する行為。\n \n 弊社の権利を侵害する行為。\n その他、弊社が不適切と判断する行為。\n \n \n \n \n 第10条(免責事項及びユーザーの責任・負担について)\n \n \n ユーザーは、以下の弊社への免責事項及びユーザーの責任・負担についての内容を了解の上、本サービスを利用することとします。\n \n \n \n 弊社が必要と判断した場合には、本利用規約に従い、ユーザーに通知することなくいつでも本サービスを変更、停止又は中止することができるものとします。弊社が本サービスを変更、停止又は中止した場合や、事件・事故等によりやむを得ず本サービスを変更、停止又は中止せざるを得なかった場合にも、弊社はユーザーに対して一切責任を負わないものとします。なお、本サービスの一部のうち、期限を定めてリリースしたものについては、定められた期限の経過をもってその一部は停止され、この停止に関しても、弊社はユーザーに対して一切責任を負わないものとします。\n \n \n 本サービスは、ユーザー自身の責任に基づく利用によって成り立っています。会員が本サービス上で行う送信行為(以下「投稿等」と称します)によって提供される投稿やコメント等の一切の情報及び本サービスによりユーザーへ提供した情報は設計実務又はこれに準ずる行為ではなく、これらに基づいてユーザーが行った一切の判断、行為並びにそれらの結果に関する責任は、ユーザー自身が負うものとします。従って、弊社は、これらについて責任を一切負わないものとします。また、弊社は、投稿等の内容及び提供した情報に関して、その正確性・速報性・合法性・完全性・有用性及びその他業界規制等の遵守状況等、ユーザーに対していかなる保証もいたしません。ユーザー自身の判断で利用ください。投稿等の内容、提供した情報及びリンク設定された外部ウェブサイトによって生じた損害(コンピューターウイルス感染被害等による損害も含みます)や、ユーザーと第三者との間又はユーザー同士の紛争、その他のトラブル等に対し、弊社は一切の補償及び関与をいたしません。ただし、ユーザーの行為により弊社がトラブル等に巻き込まれ、損害又は費用を負担した場合、ユーザーは、当該損害及び費用(損害賠償費用、弁護士費用等の一切を含みます)を賠償するものとします。\n \n \n 会員が本サービスにおいて投稿・編集した文章、画像、動画等、ユーザーの本サービスへの接続、ユーザーの本利用規約への違反もしくはユーザーによる第三者の権利侵害等、ユーザーによる本サービスの利用又はユーザーの行為に起因又は関連して生じたすべてのクレームや請求については、当該ユーザーの責任と費用負担の下で解決するものとします。\n \n \n 本利用規約第9条のいずれかに該当する場合において、本サービスの利用又はユーザーの行為に起因又は関連して生じたすべてのクレームや請求については、当該ユーザーの責任と費用負担の下で解決するものとし、弊社が損害を被った場合、そのユーザーは弊社に当該損害(損害賠償費用、弁護士費用等の一切を含みます)を賠償するものとします。\n \n \n 弊社は、会員の投稿等を監視する義務を負わず、また、原因を問わず、それらの情報の消失によってユーザーに生じた損害等について、一切責任を負わないものとします。\n \n \n 弊社ウェブサイトから他のウェブサイトへのリンク又は他のウェブサイトから弊社ウェブサイトへのリンクが提供されている場合でも、弊社は、弊社ウェブサイト以外のウェブサイト及びそこから得られる情報に関して一切の責任を負わないものとします。\n \n \n 弊社は、当該会員が建築士であることを弊社が定めた一定の基準に従い確認していますが、必ず建築士であることを保証するものではありません。弊社は、弊社の確認基準を超えた、故意の成りすましや建築士証、建築士番号等の盗難、貸与等により、建築士以外の者により建築士として投稿等がなされた場合は、一切の責任を負わないものとします。\n \n \n ユーザーは、(ア)本サービスを利用したこと、又は利用ができなかったこと、(イ)不正アクセスや不正な改変がなされたこと、(ウ)本サービス中の他のユーザーによる投稿等、(エ)その他の行為、第三者の成りすまし行為、(オ)その他本サービスに関連する事項に起因又は関連して生じた損害について、弊社は一切の責任を負わないものとします。\n \n \n 本利用規約に別途定める場合を除き、本利用規約のうち、弊社の責任を免責する規定については、弊社の故意もしくは重過失によってユーザーが損害を被ったことが管轄権を有する裁判所により判断された場合は適用されません。\n \n \n \n \n \n 第11条(会員によって登録、投稿、掲載された内容についての閲覧・変更・削除)\n \n \n \n 弊社は、会員によって投稿等されたすべての内容について、法令又は本利用規約違反の有無の確認その他の目的のために閲覧する権利を有します。この点は、非公開の内容についても同様となります。\n \n \n 会員は、以下の事由に該当する又は該当するおそれがある情報を投稿等することができません。以下の事由に該当する又は該当するおそれがある情報が投稿等された場合、弊社は、会員に対して予告なく、投稿等された情報の削除、変更等、弊社が必要と認める措置をとる場合があります。なお、当該措置の対象に該当するか否かは、すべて弊社が判断するものとし、理由の開示はいたしません。また、以下の事由に該当する場合や、本サービスの管理運営の都合上、当該情報の変更、削除等をする場合もあります。\n \n \n \n 本サービスの趣旨から逸脱した情報\n \n \n 重大な危険行為もしくは第三者の権利侵害に結びつく、又は助長する情報\n \n \n その他、弊社が不適当と判断した情報又は弊社が不適当と判断した行為により投稿等された情報\n \n \n \n \n 投稿等した情報は、投稿した会員本人からの依頼であっても、本利用規約に定める事由やその他特別の事由がある場合を除き、削除はいたしません。\n \n \n 弊社が、第2項各号に定める事由に該当する、又は該当するおそれがあると判断して投稿等された情報を変更・削除した場合、弊社は、当該変更・削除に関する業務に従事した者にかかる人件費、第三者からの当該変更・削除の請求への対応に要した費用、その他当該変更・削除に関して要した費用に相当する金額を含め、弊社が被った損害を、その会員に賠償するよう請求することができるものとします。\n \n \n 弊社は、会員が投稿した内容、その他の投稿等された情報を運営上一定期間保存することがありますが、かかる情報を保存する義務を負うものではなく、弊社はいつでもこれらの情報を削除できるものとします。\n \n \n \n \n \n 第12条(情報の無断使用の禁止)\n \n \n ユーザーは、本サービスが予定している利用態様を超えて、本サービス上の画像等を含めた情報(当該ユーザーが自ら投稿等を行った情報を除く)を無断使用することはできません。従って、本サービス上のあらゆる情報について、ユーザーは、別途弊社が明示的に定めた場合又は弊社の事前の書面による承諾を得た場合において、かかる定めや承諾の範囲内で使用することができます。ただし、複製、譲渡、貸与、翻訳、改変、転載、公衆送信(送信可能化を含みます)、伝送、配布、出版、営業使用等は一切許可されていません。ただし、本サービスで開催する勉強会の告知をする場合はこの限りではありません。\n \n \n \n \n 第13条(投稿内容の著作権・利用権)\n \n \n \n 会員は、自身が商標権・著作権・意匠権等の知的財産権の権利者である、又は権利者から許諾を得ている文章、画像、動画等のみ、本サービスを利用して投稿等することができます。\n \n \n 会員が本サービスを利用して投稿した文章、画像、動画等の商標権・著作権・意匠権等の知的財産権については、当該会員その他の既存の権利者に帰属します。ただし、本サービスを通じて投稿された文章、画像、動画等については、弊社は必要と判断する措置を講じた上で、期間の制限を設けずに自由に利用できるものとします(ただし、弊社の指定した非公開方法に基づく投稿やコメント等には適用されません)。これらの利用においても、弊社は会員に対して何らの支払いも要しないものとし、会員は、本項に基づく弊社による著作物の利用に関して著作者人格権を行使しないものとします。\n \n \n 前項に定める会員が本サービスを利用して投稿した文章、画像、動画等についての著作権を除き、本サービス及び本サービスに関連するすべての情報についての著作権及びその他の知的財産権はすべて弊社又は弊社に利用を許諾した権利者に帰属し、会員は、弊社の事前の書面による承諾を得た場合を除き、複製、譲渡、貸与、翻訳、改変、転載、公衆送信(送信可能化を含む)、伝送、配布、出版、営業使用等を行ってはなりません。\n \n \n 第2項に定める内容に加え、弊社は、本サービスを利用して投稿された文章、画像、動画等を、個人を特定できないよう情報を除去した上で、参考資料やマーケティングデータ等として活用する、又は第三者に提供する場合があります(ただし、弊社の指定した非公開の方法に基づく投稿やコメント等には適用されません)。\n \n \n 会員は、文章、画像、動画等を投稿する際に、当該投稿に関連するサービス・機能の仕様をよく確認し、弊社の指定する利用方法・利用条件を守り、投稿画面に表示される注意事項を確認し遵守するものとします。また、当該会員は、自身の投稿物が他のユーザーによって閲覧、ダウンロード、その他の方法で利用されることに同意するものとします。\n \n \n ユーザーは、他の会員が本サービスを利用して投稿した文章、画像、動画等について、弊社の指定した方法及び範囲内でのみ閲覧、ダウンロード、その他の利用ができるものとします。また、当該ユーザーは、当該利用を行う際に、関連するサービス・機能の仕様をよく確認し、弊社の指定する利用方法・利用条件を守り、利用の際に表示される注意事項を確認し遵守するものとします。\n \n \n 会員が投稿した文章、画像、動画等の公開、公開される場合の範囲、その他のユーザーによる利用の範囲等については、本サービスを構成するサービス、機能、コンテンツごとに弊社が定めるところに従います。\n \n \n \n \n \n 第14条(会員資格の譲渡等の禁止)\n \n \n 会員は、会員資格又は本サービスに関連する権利義務を第三者に譲渡、貸与すること、引き受けさせること、又は第三者と共有することはできません。\n \n \n \n \n 第15条(弊社からの通知の効力)\n \n \n \n 弊社がユーザーに対して行う、弊社が運営するインターネット・サービスに関する一切の通知は、原則として、当該サービスに関連するウェブサイト上に表示された時点(メールその他、弊社が適当と判断する方法で通知を行う場合は、弊社がユーザーから受け取ったメールアドレス又はその他の連絡先に対して通知が発信された時点)で、効力を発生させるものとします。\n \n \n 前項に定める通知の効力は、ユーザーが弊社からの通知を実際に受領又は確認したかどうかにかかわらず、発生します。\n \n \n \n \n \n 第16条(退会)\n \n \n \n 会員が本サービスを退会する場合は、弊社所定の退会方法にて届け出るものとし、弊社での退会処理終了後、退会となります。\n \n \n 会員は、退会手続きを行った場合、弊社で利用いただいていた会員の会員資格に関する一切の権利、特典を失うものとします。\n \n \n 会員が本サービスの会員になる際に弊社に届け出た登録情報(変更登録にかかる情報を含みます)は、本利用規約に違反した会員に関する情報等、本サービスを適切に運営するために必要と弊社が判断する情報を除き、退会後、自動削除されます。ただし、本サービスを利用して投稿等した文章、画像、動画等、及び投稿等した際に入力した会員属性情報については削除されず、会員は、第13条に基づき、弊社が引き続き利用できることを予め承諾するものとします。\n \n \n \n \n \n 第17条(サイトへの接続等)\n \n \n 本サービスを受けるためのサイトへの接続は、ユーザーが自己の責任と費用負担の下で行うものとします。弊社のサイトへの接続中、回線の都合等で接続が中断した場合にも、弊社では一切責任を負いません。\n \n \n \n \n 第18条(サービスの一時的な中断)\n \n \n 弊社は以下の事由により、ユーザーに事前に連絡することなく、一時的にサービスの提供を中断することがあります。このサービスの中断による損害について、弊社は一切の責任を負いません。\n \n \n \n 弊社のシステムの保守、点検、修理等を定期的又は緊急に行う場合\n \n \n 火災・停電によりサービスの提供ができなくなった場合\n \n \n 天変地異等によりサービスの提供ができなくなった場合\n \n \n その他、運用上又は技術上、本サービス提供の一時的な中断を必要と弊社が判断した場合\n \n \n \n \n \n 第19条(提供サービス及び本利用規約の変更・廃止)\n \n \n 弊社は、本サービスの内容を、ユーザーへの事前の通知なく変更することがあります。ユーザーはこれに予め同意するものとし、ユーザーに不利益又は損害が発生したとしても、弊社は一切の責任を負いません。一方、本サービスの停止又は廃止は、ユーザーに通知するものとし、弊社がこの手続きを行った後に本サービスを停止又は廃止した場合には、弊社は一切の責任を負いません。\n \n \n \n \n 第20条(ファイル共有サービス)\n \n \n 弊社は、本サービスを介して送受信されるファイルの内容(内容の信頼性、正確性、完成度、有用性、ウイルスの有無)に関して、一切の責任を負いません。ユーザーは本サービスを介して送受信されるファイルについて、送信又は受信の過程で、弊社の関知しない種々のネットワークや機器を経由することがあることを理解し、場合によっては、ファイルの内容に異常をきたす可能性があること、意図しない第三者による盗み見、利用等が行われる可能性があることを理解した上で本サービスを利用するものとします。\n \n \n \n \n 第21条(Cookieの使用)\n \n \n 弊社ウェブサイトでは、ユーザーにより良いサービスをご提供するため、Cookieを利用しています。このまま弊社ウェブサイトを利用になる場合、Cookieの使用に同意いただいたものとみなされます。詳細については「個人情報保護方針」をご覧ください。\n \n \n \n \n 第22条(秘密保持)\n \n \n \n 弊社及びユーザーは、本規約又は本サービスに関連して、相手方より書面、口頭もしくは記録媒体等により提供もしくは開示されたか、又は知り得た、相手方に関する技術、営業、業務、財務又は組織に関するすべての情報(ただし、公知の事実を除く)を本サービスのみに利用するとともに、相手方の書面による承諾なしに第三者に相手方のそれらの情報を提供、開示又は漏えいしないものとします。\n \n \n 前項の規定は、本規約の残存期間中はもとより、その終了後においても適用するものとします。\n \n \n \n \n \n 第23条(個人情報及びデータの取り扱い)\n \n \n 弊社は、ユーザーより提供を受けた個人情報等について、弊社が掲げる個人情報保護方針に基づき適切に扱います。\n \n \n \n \n 第24条(反社会的勢力ではないことの表明保証)\n \n \n \n ユーザー及び弊社は、自己が、次の各号のいずれにも該当しないことを表明し、かつ将来にわたっても該当しないことを相互に確約するものとします。\n \n \n \n ユーザー及び弊社は、自ら又は第三者を利用して次の各号いずれの行為も行わないことを相互に表明し、確約するものとします。\n \n 暴力的な要求行為。\n 法的な責任を超えた不当な要求行為。\n \n 取り引きに関して、脅迫的な言動をし、又は暴力を用いる行為。\n \n \n 風説を流布し、偽計又は威力を用いて相手方の信用を毀損する、又は相手方の業務を妨害する行為。\n \n その他前各号に準ずる行為。\n \n \n \n \n \n \n 第25条(本利用規約の解釈)\n \n \n 本利用規約のいずれかの条項又はその一部が、管轄権を有する裁判所により、違法又は無効と判断された場合であっても、その趣旨に最も近い有効な条項を当該無効な条項もしくは部分と置き換えて適用、又は当該条項もしくは部分の趣旨に最も近い有効な条項となるよう、合理的な解釈を加えて適用します。\n \n \n \n \n 第26条(準拠法・合意管轄)\n \n \n 本利用規約に関する事項の一切は、日本法に準拠して解釈されるものとし、本サービスの利用に関し紛争が生じた際には、大阪地方裁判所を第一審の専属合意管轄裁判所とします。\n \n \n \n \n 第27条(協議解決)\n \n \n 弊社及びユーザーは、本規約に定めのない事項又は本規約の解釈に疑義が生じた場合、互いに信義誠実の原則に従って協議の上、速やかに解決を図るものとします。\n \n \n \n \n 第28条(本利用規約の効力)\n \n \n \n 本利用規約は2024年4月1日から発効するものとし、過去の規約に優先して適用されるものとします。\n \n \n 本利用規約の内容と、本利用規約外における本サービスの説明等が異なる場合には、本利用規約の内容が優先して適用されるものとします。\n \n \n \n \n \n \n \n >\n );\n};\n\nexport default TermsOfService;\n","import React from \"react\";\nimport BackgroundBuildingImage from \"../../shared/components/atoms/BackgroundBuildingImage\";\nimport Background from \"../../shared/components/atoms/Background\";\nimport {\n Box,\n Container,\n Heading,\n VStack,\n Text,\n OrderedList,\n ListItem,\n UnorderedList,\n Stack,\n} from \"@chakra-ui/react\";\nimport ShadowCard from \"../../shared/components/atoms/ShadowCard\";\nimport BracketsList from \"../../shared/components/atoms/BracketsList\";\n\nconst TermsOfService = () => {\n return (\n <>\n \n \n \n \n \n 「A-Loop」利用規約\n \n \n \n 本規約は、ユニオンシステム株式会社(以下「弊社」と呼称します)が提供する建築士向けオンラインコミュニティサービス「A-Loop」(以下「本サービス」と呼称します)について、弊社と本サービスを利用いただくすべてのユーザー(以下、「ユーザー」と呼称します)との間で適用される共通の条件を定めるものとします。\n \n \n \n 第1条(適用範囲及び変更)\n \n \n \n 本規約は、すべてのユーザーに適用され、ユーザーはこれを遵守して本サービスを利用します。\n \n \n 弊社は、ユーザーに事前の通知をすることなく、本規約を変更することができます。本規約を変更する場合、弊社はユーザーに対して変更後の規約をWebサイト上での公表又は登録されたメールアドレスへ通知するものとし、当該公表又は通知のいずれかがなされた後、ユーザーが本サービスを利用した場合又は会員が1か月以内に退会手続きを行わなかった場合、ユーザーは変更後の規約に同意したものとみなされます。ユーザーは、本規約を誠実に遵守するものとします。\n \n \n 第2条所定の方法により会員登録した会員が法人を代表する者として意思表示を行ったとき(受発注の利用申請を含みますが、これに限りません)、当該会員は、当該法人を代表して本サービスを利用する正当な権限を有することを表明し保証します。当該会員は、本サービスの認証情報を適切に管理する義務を負い、本サービスの利用によって当該会員に生じた本規約上または法令上の責任は、すべて当該会員または当該会員が意思表示を行った際に示した法人に帰属することを、あらかじめ異議なく承諾します。\n \n \n \n \n \n 第2条(会員登録)\n \n \n 本規約に同意の上で、弊社所定の方法により会員登録をした法人及び自然人(個人)を「会員」とします(ただし、法人が利用できる機能は、本規約の定めるところにより、受発注に限られます)。\n \n \n \n 会員登録申請は必ず本サービスを利用する個人(会員が法人である場合は、当該法人を代表する権限を有する個人)が行わなければならず、代理人による登録申請は認められません。\n \n \n 会員登録申請に際しては、真実、正確かつ最新の情報を記載することとし、虚偽の記載が判明した場合、本サービスの利用停止等の措置をとる場合があります。\n \n \n 弊社は、弊社の基準に従い、登録希望者の会員登録の可否を判断し、弊社が認める場合に限り登録希望者の会員登録を認めることとします。\n \n \n \n \n \n 第3条(本サービスの概要)\n \n \n \n 本サービスは、会員が自身の情報を登録し、本サービスの他の会員に対して登録情報を提供し、また他の会員に関する情報を閲覧できるサービスです。会員登録していない場合、本サービス上の機能を利用することはできません。\n \n \n 会員は、自身の登録情報を編集でき、本サービス上で提供する自身に関するすべての情報(以下「会員情報等」と呼称します)の内容の正確性、真実性、適法性等について、自らが責任を負うものとします。\n \n \n 会員は、本サービス上の機能によって、勉強会の視聴やコメントを行うことができます。\n \n \n 会員は、本サービス上の機能によって、投稿やコメント、ファイルの共有を行うことができます。\n \n \n 会員は、登録されたメールアドレスでお知らせの通知やメールマガジンサービスを受け取ることができます。\n \n \n 会員は、本サービスの利用に際し、自己の判断と責任において他の会員に対応するものとし、弊社はユーザーに対して、本サービスの利用に伴う結果に関する一切の責任を負いません。\n \n \n 会員は、本サービス上の受発注の利用を申請する操作(以下「受発注の利用申請」と呼称します)を行うことにより、会員間で受発注の検討及び連絡等を行う機能(以下「受発注」と呼称します)を利用できます。なお、受発注を利用する場合、会員は法人または個人事業主(以下「法人等」と呼称します。ただし、個人事業主の従業員として利用する場合を含む)より、当該法人等として受発注を利用する旨の意思表示を行う権限を得た上で、法人等として受発注を利用する旨の意思表示(受発注の利用申請を行うことを含みます)をすることにより、受発注を法人等として利用することができます。ただし、この場合であっても、法人等が当事者として利用することができる機能は受発注に限られ、受発注以外の機能については、当該受発注の利用申請を行った自然人たる会員(当該会員が個人事業主の従業員である場合を含みます。)が当事者として利用します。\n \n \n ユーザーは、本サービスの利用に際してダウンロードその他の方法によりファイル等を自身のコンピューター等にダウンロードする場合、自身が保有する情報の消滅、改変、機器の故障、損傷等が生じないよう十分な注意を払うものとし、かかる損害について弊社は一切の責任を負いません。\n \n \n 弊社は、本サービスの提供・運営のために、本サービスにおけるユーザーの行為、登録データ等を閲覧できるものとします。ただし、弊社はユーザーの行動を監視する義務を負うものではありません。\n \n \n \n \n \n 第4条(ユーザーに関する情報の開示と利用)\n \n \n \n ユーザーが会員登録時及び本サービスの利用に際して弊社に開示した当該ユーザーに関する会員情報等のうち、投稿やコメント等において本サービス上で既に開示済みの情報以外の会員情報等については、原則として、事前の同意なく、弊社は他のユーザーや、その他の第三者に対して開示しません。ただし、本規約又は弊社の個人情報保護方針に定める場合、その他法令に基づく公的機関からの照会、又は法令により開示義務がある場合は例外とします。\n \n \n 弊社は、本条に基づく情報の開示に関してユーザーに発生した損害について一切の責任を負いません。\n \n \n 前各項の他、会員情報等は、弊社の本規約及び個人情報保護方針に従い、弊社が管理します。\n \n \n \n \n \n 第5条(業務の委託)\n \n \n サービスの提供にあたり、業務の全部及び一部を第三者に委託する場合があります。\n
\n 第三者に委託する場合、当該第三者に対して、弊社の個人情報保護方針と同等の義務を課し、当該第三者は委託された業務の範囲において、本規約に則って業務を行うこととします。\n \n \n \n \n 第6条(会員の義務)\n \n \n \n 会員は、自らの意思と責任をもって、本サービスを利用するものとします。\n \n \n 会員は、本サービス上で常に最新で正確な情報を提供するものとします。\n \n \n 会員は、本サービスを情報の発信を行う場として利用するものとし、責任をもって行動します。\n \n \n \n \n \n 第7条(会員情報等の変更)\n \n \n 会員は、登録した事項に変更が生じた場合には、弊社の定める方法に従い、速やかに変更登録を行うものとします。変更登録がなされなかったことにより生じた損害について、弊社は一切責任を負いません。\n \n \n \n \n 第8条(ID及びパスワードの管理)\n \n \n \n 弊社は、会員に対して、本サービスを利用するために必要となるID及びパスワード(以下「ID等」と呼称します)を発行します。会員はID等を第三者に貸与し、譲渡し、使用させたり処分したりしてはならないものとします。\n \n \n 会員は、ID等について善良な管理者の注意をもって厳重に管理、保管するものとします。弊社は、ID等を使用して本サービスが利用された場合は、会員本人(会員が法人等である場合においては、当該法人等として本サービスの利用を行う権限を有する個人)による利用とみなし、ID等の盗用、不正使用、その他会員本人以外の第三者による利用であっても、当該利用に起因してユーザー又は第三者に生じる損害及び結果について、損害賠償責任その他一切の責任を負いません。\n \n \n \n \n \n 第9条(特定ユーザーに対するサービスの停止)\n \n \n 本サービスの利用について、ユーザーの故意又は過失に関係なく以下の各項に該当する、又は該当するおそれがあると弊社が判断する行為は禁止されます。かかる禁止事項に違反した場合は、当該違反にかかる投稿等された情報の削除、変更の他、本サービスの一部の利用制限(受発注の全部または一部の利用停止を含みます)、本サービスの利用停止等、弊社が適当と認める措置をとる場合があります。その場合、削除・変更、利用停止、会員資格の剥奪等の措置に関する投稿・苦情は一切受け付けておらず、かかる措置に関し、弊社は一切責任を負わず、その理由についてユーザーに説明する義務を負いません。\n \n \n \n 反社会的勢力に該当すること又は反社会的活動に関する行為。\n \n \n 法令、本規約又は公序良俗に違反する、又は他人に不利益を与える行為。\n \n \n 犯罪行為又はこれを予告、関与、助長する行為。\n \n \n 特定の個人又は企業に対する誹謗中傷をする行為。\n \n \n 虚偽又は誤解を招くような内容を含む情報を掲載、登録する等の行為。\n \n \n 弊社、他のユーザー又は第三者の著作権(著作権法第27条及び第28条の権利を含みます。本規約中において同じ。)、商標権、企業秘密等の知的財産権、肖像権、プライバシーの権利、名誉、その他の権利又は利益を侵害する行為。\n \n 会員の個人情報を第三者に漏えいする行為。\n \n 民族、人種、性別、年齢等による差別につながる表現の掲載行為。\n \n \n 商材の営業目的、勧誘、採用を目的とする情報提供活動。\n \n \n 交際相手を求める行為や出会いを目的とする又は宿泊や居住する相手を探そうとする行為。\n \n \n その他社会的に容認されないと判断される出会い行為。\n \n \n 会員又は弊社に対する、営業目的でのメール送信、その他の営業行為。\n \n \n 出会い系サイト、アダルト関連のすべての業務に関する内容を含む表現の掲載行為。\n \n \n 弊社、他のユーザー又は第三者に損害を生じさせるおそれのある目的又は方法で本サービスを利用する、又は利用しようとする行為。\n \n \n 会員の個人情報を収集、蓄積する行為、又はこれらの行為をしようとする行為。\n \n \n 自動化された手段(ロボット、ボットネット、スクレーパ等)を使用して本サービスにアクセスする行為。\n \n 会員に対し継続的に勧誘又は要求する行為。\n \n コンピューターウイルス、その他の有害なコンピュータープログラムを含む情報を送信する行為。\n \n \n ねずみ講、チェーンメール、MLM(マルチレベルマーケティング)、リードメール等の第三者を勧誘する内容の情報を掲載又は送信する行為。\n \n \n アフィリエイトや招待することでポイント等の利益が発生するサイトへ誘導する情報(ただし、弊社が事前に許可したものを除きます)を掲載又は送信する行為。\n \n \n 通常利用の範囲を超えてサーバーに負担をかける行為、及びそれを助長するような行為、その他本サービスの運営・提供又は他のユーザーによる本サービスの利用を妨害する、又はそれらに支障をきたすおそれのある行為。\n \n 運営者に成りすます行為。\n \n 本サービスに関し、利用できる情報を改ざんする行為。\n \n \n 本サービス上の画像等を含めた情報を無断使用、編集、複製、転載する行為。\n \n \n 弊社又は他者のサーバーに負担をかける行為、又は本サービスの運営やネットワーク・システムに支障を与える行為、又はこれらのおそれのある行為。\n \n \n 登録又は投稿をすることで第三者に成りすます行為。\n \n \n ユーザー本人に許可を受けたか否かを問わず、ユーザー本人以外が登録情報(メールアドレスやパスワード等)を利用して本サービスを利用する行為。\n \n \n 会員資格を第三者に利用させる又は譲渡する行為。\n \n \n 1つの会員資格を複数人で利用する行為又は1人で複数の会員資格を保有する行為。\n \n \n 弊社又はユーザーが所属する業界団体の内部規則に違反する行為。\n \n 弊社の権利を侵害する行為。\n その他、弊社が不適切と判断する行為。\n \n \n \n \n 第10条(免責事項及びユーザーの責任・負担について)\n \n \n ユーザーは、以下の弊社への免責事項及びユーザーの責任・負担についての内容を了解の上、本サービスを利用することとします。\n \n \n \n 弊社が必要と判断した場合には、本規約に従い、ユーザーに通知することなくいつでも本サービスを変更、停止又は中止することができるものとします。弊社が本サービスを変更、停止又は中止した場合や、事件・事故等によりやむを得ず本サービスを変更、停止又は中止せざるを得なかった場合にも、弊社はユーザーに対して一切責任を負いません。なお、本サービスの一部のうち、期限を定めてリリースしたものについては、定められた期限の経過をもってその一部は停止され、この停止に関しても、弊社はユーザーに対して一切責任を負いません。\n \n \n 本サービスは、ユーザー自身の責任に基づく利用によって成り立っています。会員による本サービス上の送信行為(以下「投稿等」と呼称します)によって提供される投稿やコメント等の一切の情報及び本サービスによりユーザーへ提供した情報は設計実務又はこれに準ずる行為ではなく、これらに基づいてユーザーが行った一切の判断、行為並びにそれらの結果に関する責任は、ユーザー自身が負うものとし、弊社は、投稿等の内容に起因してユーザーに損害が生じた場合であっても、一切の責任を負いません。また、弊社は、投稿等の内容及び提供した情報に関して、その正確性・速報性・合法性・完全性・有用性及びその他業界規制等の遵守状況等につき、ユーザーに対していかなる表明または保証をいたしません。投稿等の内容、提供した情報及びリンク設定された外部ウェブサイトによって生じた損害(コンピューターウイルス感染被害等による損害も含みます)や、ユーザーと第三者との間又はユーザー同士の紛争、その他のトラブル等に対し、弊社は一切の補償及び関与をいたしません。ただし、ユーザーの行為により弊社がトラブル等に巻き込まれ、損害又は費用を負担した場合、ユーザーは、当該損害(第三者に損害賠償費用、合理的な弁護士費用の全額を含みますが、これらに限りません)を賠償するものとします。\n \n \n 会員が本サービスにおいて投稿・編集した文章、画像、動画等、ユーザーの本サービスへの接続、ユーザーの本規約への違反もしくはユーザーによる第三者の権利侵害等、ユーザーによる本サービスの利用又はユーザーの行為に起因又は関連して生じたすべてのクレームや請求については、当該ユーザーの責任と費用負担の下で解決するものとします。\n \n \n 本規約第9条のいずれかに該当する場合において、本サービスの利用又はユーザーの行為に起因又は関連して生じたすべてのクレームや請求については、当該ユーザーの責任と費用負担の下で解決するものとし、弊社が損害を被った場合、弊社に損害を与えたユーザーは、弊社に対し当該損害(第三者に損害賠償費用、合理的な弁護士費用の全額を含みますが、これらに限りません)を賠償する義務を負います。\n \n \n 弊社は、会員の投稿等を監視する義務を負わず、また、原因を問わず、それらの情報の消失によってユーザーに生じた損害等について、一切責任を負いません。会員は、投稿等にあたって、当該投稿等の消失により損害を生じるおそれがあるときは、事前に当該投稿等のバックアップを取る等の措置を講じなければなりません。\n \n \n 弊社ウェブサイトから他のウェブサイトへのリンク又は他のウェブサイトから弊社ウェブサイトへのリンクが提供されている場合でも、弊社は、弊社ウェブサイト以外のウェブサイト及びそこから得られる情報に関して一切の責任を負いません。\n \n \n 弊社は、当該会員が建築士であることを弊社が定めた一定の基準に従い確認していますが、必ず建築士であることを保証するものではありません。受発注の利用において直接取引する会員においては、発注する会員において相手方会員が建築士であることを個別に確認するものとし、弊社は、故意の成りすましや建築士証、建築士番号等の盗難、貸与等により、建築士以外の者により建築士として投稿等がなされた場合は、一切の責任を負いません。\n \n \n ユーザーは、(ア)本サービスを利用したこと、又は利用ができなかったこと、(イ)不正アクセスや不正な改変がなされたこと、(ウ)本サービス中の他のユーザーによる投稿等、(エ)その他の行為、第三者の成りすまし行為、(オ)その他本サービスに関連する事項に起因又は関連して生じた損害について、弊社は一切の責任を負いません。\n \n \n 自然人であるユーザーが法人等として本サービスを利用(ただし、個人事業主の従業員として本サービスを利用した場合を含みます。以下同じ。)、または本サービス上で意思表示を行った場合において、当該ユーザーが当該法人等として利用または意思表示を行う正当な権限を有していなかった場合には、当該ユーザーが当該利用行為及び意思表示にかかるすべての責任を負います。\n \n \n 本規約に別途定める場合を除き、本規約のうち、弊社の責任を免責する規定については、管轄権を有する裁判所により、弊社の故意もしくは重過失によってユーザーが損害を被ったことが判断された場合は適用されません。\n \n \n \n \n \n 第11条(会員によって登録、投稿、掲載された内容についての閲覧・変更・削除)\n \n \n \n 弊社は、会員によって投稿等されたすべての内容について、法令又は本規約違反の有無の確認その他の目的のために閲覧する権利を有します。この点は、非公開の内容についても同様となります。\n \n \n 会員は、以下の事由に該当する又は該当するおそれがある情報を投稿等することができません。以下の事由に該当する又は該当するおそれがある情報が投稿等された場合、弊社は、会員に対して予告なく、投稿等された情報の削除、変更等、弊社が必要と認める措置をとる場合があります。なお、当該措置の対象に該当するか否かは、すべて弊社が判断するものとし、理由の開示はいたしません。また、以下の事由に該当する場合や、本サービスの管理運営の都合上、当該情報の変更、削除等をする場合もあります。\n \n \n \n 本サービスの趣旨から逸脱した情報\n \n \n 重大な危険行為もしくは第三者の権利侵害に結びつく、又は助長する情報\n \n \n その他、弊社が不適当と判断した情報又は弊社が不適当と判断した行為により投稿等された情報\n \n \n \n \n \n 投稿等した情報は、投稿した会員本人からの依頼であっても、本規約に定める事由やその他特別の事由がある場合を除き、削除はいたしません。\n \n \n 弊社が、第2項各号に定める事由に該当する、又は該当するおそれがあると判断して投稿等された情報を変更・削除した場合、弊社は、当該変更・削除に関する業務に従事した者にかかる人件費、第三者からの当該変更・削除の請求への対応に要した費用、その他当該変更・削除に関して要した費用に相当する金額を含め、弊社が被った損害を、その会員に賠償するよう請求することができるものとします。\n \n \n 弊社は、会員が投稿した内容、その他の投稿等された情報を運営上一定期間保存することがありますが、かかる情報を保存する義務を負うものではなく、弊社はいつでもこれらの情報を削除できるものとします。\n \n \n \n \n \n 第12条(受発注の利用申請及び受発注の利用)\n \n \n \n 会員は、受発注の利用を希望するときは、弊社に対し、受発注の利用申請を行うものとします。受発注の利用申請においては、会員に関する追加情報の提供や、登記簿謄本その他の資料の提出を求める場合があります。\n \n \n 弊社は、会員から、受発注の利用申請が行われた場合、当該会員について、会員登録及び受発注の利用申請において提出された情報等を参照し、当該会員に受発注の利用を許可するか否かの審査を行い、審査結果を遅滞なく会員のメールアドレスに通知します。なお、弊社は、審査の基準について一切開示する義務を負いません。\n \n \n 受発注は、会員が、相互に、設計業務の全部または一部の業務を受注または発注する機会を提供する機能であり、これ以外の利用目的で受発注を利用すること(例えば、設計業務以外の業務の受発注を検討または実施すること、他のサイトまたはサービスへの誘導を行うこと、採用活動を行うことが含まれますが、これらに限りません)はできません。\n \n \n 会員が前項に違反した場合、弊社は、当該会員について、予告なく、本サービスの全部もしくは一部の利用制限、受発注の利用停止、本サービスの利用停止又は会員資格の停止等の措置をとる場合があります。\n \n \n 受発注においては、会員相互の連絡のためのメッセージ機能を利用することができます。メッセージ機能の利用にあたっては、第10条、第11条その他本規約の規定を遵守するほか(なお、メッセージの送信は、第11条に定める「投稿等」にあたります)、第3項の目的に反するメッセージを投稿してはなりません。\n \n \n 受発注を利用する会員は、相互に、会員が自ら登録した会員の情報(所在地、代表者氏名、ウェブサイト、SNS等。以下「会員登録情報」と呼称します)を閲覧することができます。\n \n \n 会員が、第1項の利用申請において登録した事項または会員登録情報の変更を希望するときは、弊社の所定の方法で変更申請を行うこととし、弊社は、変更申請がなされたときは、遅滞なく変更申請の内容を審査し、審査を通過した場合、変更申請の内容を記録または掲載します。弊社は、変更申請において会員から届出られた内容に疑義があるときは、速やかに当該内容について会員に照会し、疑義が解消するまでの間、変更前の内容を記録または掲載し続けることがあります。照会を受けたときは、会員は速やかに当該照会に応答しなければなりません。\n \n \n 会員は、受発注の利用停止を希望するとき(第5項に定める情報の、受発注を利用している会員への公開を取りやめたい場合を含みます)は、本サービス所定の方法で利用停止を申請することにより、受発注の利用を停止することができます。受発注の利用を停止した会員は、任意の時点で受発注の利用を再開することができますが、受発注の利用状況、受発注を利用停止していた期間に応じて、再度第1項及び第2項所定の審査を行うことがあります。\n \n \n 受発注は、会員間が相互に設計業務の受注・発注を行う機会を提供するものであり、受発注を契機として設計業務の受注・発注が行われた場合であっても、会員間の契約内容、債務の履行状況、成果物の品質その他の個別具体的な会員間の契約に関する事柄について、弊社は一切関知しません。また、弊社は、会員間のやり取りが円滑に行われること(返信が届くことを含みます)を保証するものではなく、会員間のやり取りに起因するトラブルを解決するために必要な措置を取る義務を負いません。\n \n \n 受発注におけるメッセージ機能、及び、メッセージ機能に付随するファイル共有サービスにおいては、一定期間経過後にメッセージ及びファイルが予告なく消去されます。\n \n \n \n \n \n 第13条(情報の無断使用の禁止)\n \n \n ユーザーは、本サービスが予定している利用態様を超えて、本サービス上の画像等を含めた情報(当該ユーザーが自ら投稿等を行った情報を除く)を無断使用することはできません。従って、本サービス上のあらゆる情報について、ユーザーは、別途弊社が明示的に定めた場合又は弊社の事前の書面による承諾を得た場合において、かかる定めや承諾の範囲内で使用することができます。ただし、複製、譲渡、貸与、翻訳、改変、転載、公衆送信(送信可能化を含みます)、伝送、配布、出版、営業使用等は一切許可されていません。ただし、本サービスで開催する勉強会の告知をする場合はこの限りではありません。\n \n \n \n \n 第14条(投稿内容の著作権・利用権)\n \n \n \n 会員は、自身が商標権・著作権・意匠権等の知的財産権の権利者である、又は権利者から許諾を得ている文章、画像、動画等のみ、本サービスを利用して投稿等することができます。\n \n \n 会員が本サービスを利用して投稿した文章、画像、動画等の商標権・著作権・意匠権等の知的財産権については、当該会員その他の既存の権利者に帰属します。ただし、本サービスを通じて投稿された文章、画像、動画等については、弊社は必要と判断する措置を講じた上で、期間の制限を設けずに自由に利用できるものとします(ただし、弊社の指定した非公開方法に基づく投稿やコメント等には適用されません)。これらの利用においても、弊社は会員に対して何らの支払いも要しないものとし、会員は、本項に基づく弊社による著作物の利用に関して著作者人格権を行使しないものとします。\n \n \n 前項に定める会員が本サービスを利用して投稿した文章、画像、動画等についての著作権を除き、本サービス及び本サービスに関連するすべての情報についての著作権及びその他の知的財産権はすべて弊社又は弊社に利用を許諾した権利者に帰属し、会員は、弊社の事前の書面による承諾を得た場合を除き、複製、譲渡、貸与、翻訳、改変、転載、公衆送信(送信可能化を含む)、伝送、配布、出版、営業使用等を行ってはなりません。\n \n \n 第2項に定める内容に加え、弊社は、本サービスを利用して投稿された文章、画像、動画等を、個人を特定できないよう情報を除去した上で、参考資料やマーケティングデータ等として活用する、又は第三者に提供する場合があります(ただし、弊社の指定した非公開の方法に基づく投稿やコメント等には適用されません)。\n \n \n 会員は、文章、画像、動画等を投稿する際に、当該投稿に関連するサービス・機能の仕様をよく確認し、弊社の指定する利用方法・利用条件を守り、投稿画面に表示される注意事項を確認し遵守するものとします。また、当該会員は、自身の投稿物が他のユーザーによって閲覧、ダウンロード、その他の方法で利用されることに同意するものとします。\n \n \n ユーザーは、他の会員が本サービスを利用して投稿した文章、画像、動画等について、弊社の指定した方法及び範囲内でのみ閲覧、ダウンロード、その他の利用ができるものとします。また、当該ユーザーは、当該利用を行う際に、関連するサービス・機能の仕様をよく確認し、弊社の指定する利用方法・利用条件を守り、利用の際に表示される注意事項を確認し遵守するものとします。\n \n \n 会員が投稿した文章、画像、動画等の公開、公開される場合の範囲、その他のユーザーによる利用の範囲等については、本サービスを構成するサービス、機能、コンテンツごとに弊社が定めるところに従います。\n \n \n \n \n \n 第15条(会員資格の譲渡等の禁止)\n \n \n 会員は、会員資格又は本サービスに関連する権利義務を第三者に譲渡、貸与すること、引き受けさせること、又は第三者と共有することはできません。\n \n \n \n \n 第16条(弊社からの通知の効力)\n \n \n \n 弊社がユーザーに対して行う、弊社が運営するインターネット・サービスに関する一切の通知は、原則として、当該サービスに関連するウェブサイト上に表示された時点(メールその他、弊社が適当と判断する方法で通知を行う場合は、弊社がユーザーから受け取ったメールアドレス又はその他の連絡先に対して通知が発信された時点)で、効力を発生させるものとします。\n \n \n 前項に定める通知の効力は、ユーザーが弊社からの通知を実際に受領又は確認したかどうかにかかわらず、発生します。\n \n \n \n \n \n 第17条(退会)\n \n \n \n 会員が本サービスを退会する場合は、弊社所定の退会方法にて届け出るものとし、弊社での退会処理終了後、退会となります。\n \n \n 会員は、退会手続きを行った場合、弊社で利用いただいていた会員の会員資格に関する一切の権利、特典を失うものとします。\n \n \n 会員が本サービスの会員になる際に弊社に届け出た登録情報(変更登録にかかる情報を含みます)は、本規約に違反した会員に関する情報等、本サービスを適切に運営するために必要と弊社が判断する情報を除き、退会後、一定期間経過後に自動削除されます。ただし、本サービスを利用して投稿等した文章、画像、動画等、及び投稿等した際に入力した会員属性情報については削除されず、会員は、第14条に基づき、弊社が引き続き利用できることを予め承諾するものとします。\n \n \n 会員が受発注を利用している場合において、第1項に定める退会の届出を行ったときは、弊社は当該会員の受発注の利用状況(メッセージの送受信状況及びその内容を含む)を確認し、例えば当該会員がメッセージを受信し、送信者が返答を待っている状況であるなど、その時点で直ちに退会を承認することが社会通念上不適当であると認められるときは、当該会員に対して、退会前にメッセージの返信等の必要な措置をとるよう要請することができます。当該要請を受けたときは、当該会員は、可能な限り弊社の要請に従うこととします。\n \n \n \n \n \n 第18条(サイトへの接続等)\n \n \n 本サービスを受けるためのサイトへの接続は、ユーザーが自己の責任と費用負担の下で行うものとします。弊社のサイトへの接続中、回線の都合等で接続が中断した場合にも、弊社では一切責任を負いません。\n \n \n \n \n 第19条(サービスの一時的な中断)\n \n \n 弊社は以下の事由により、ユーザーに事前に連絡することなく、一時的にサービスの提供を中断することがあります。このサービスの中断による損害について、弊社は一切の責任を負いません。\n \n \n \n 弊社のシステムの保守、点検、修理等を定期的又は緊急に行う場合\n \n \n 火災・停電によりサービスの提供ができなくなった場合\n \n \n 天変地異等によりサービスの提供ができなくなった場合\n \n \n その他、運用上又は技術上、本サービス提供の一時的な中断を必要と弊社が判断した場合\n \n \n \n \n \n 第20条(提供サービス及び本規約の変更・廃止)\n \n \n 弊社は、本サービスの内容を、ユーザーへの事前の通知なく変更することがあります。ユーザーはこれに予め同意するものとし、ユーザーに不利益又は損害が発生したとしても、弊社は一切の責任を負いません。一方、本サービスの停止又は廃止は、ユーザーに通知するものとし、弊社がこの手続きを行った後に本サービスを停止又は廃止した場合には、弊社は一切の責任を負いません。\n \n \n \n \n 第21条(ファイル共有サービス)\n \n \n 弊社は、本サービスを介して送受信されるファイルの内容(内容の信頼性、正確性、完成度、有用性、ウイルスの有無)に関して、一切の責任を負いません。ユーザーは本サービスを介して送受信されるファイルについて、送信又は受信の過程で、弊社の関知しない種々のネットワークや機器を経由することがあることを理解し、場合によっては、ファイルの内容に異常をきたす可能性があること、意図しない第三者による盗み見、利用等が行われる可能性があることを理解した上で本サービスを利用するものとします。\n \n \n \n \n 第22条(Cookieの使用)\n \n \n 弊社ウェブサイトでは、ユーザーにより良いサービスをご提供するため、Cookieを利用しています。このまま弊社ウェブサイトを利用になる場合、Cookieの使用に同意いただいたものとみなされます。詳細については「個人情報保護方針」をご覧ください。\n \n \n \n \n 第23条(秘密保持)\n \n \n \n 弊社及びユーザーは、本規約又は本サービスに関連して、相手方より書面、口頭もしくは記録媒体等により提供もしくは開示されたか、又は知り得た、相手方に関する技術、営業、業務、財務又は組織に関するすべての情報(ただし、公知の事実を除く)を本サービスのみに利用するとともに、相手方の書面による承諾なしに第三者に相手方のそれらの情報を提供、開示又は漏えいしないものとします。\n \n \n 前項の規定は、本規約の残存期間中はもとより、その終了後においても適用するものとします。\n \n \n \n \n \n 第24条(個人情報及びデータの取り扱い)\n \n \n 弊社は、ユーザーより提供を受けた個人情報等について、弊社が掲げる個人情報保護方針(https://aloop.jp/privacy_policy)に基づき適切に扱います。\n \n \n \n \n 第25条(反社会的勢力ではないことの表明保証)\n \n \n \n ユーザー及び弊社は、自己が、次の各号のいずれにも該当しないことを表明し、かつ将来にわたっても該当しないことを相互に確約するものとします。\n \n \n \n ユーザー及び弊社は、自ら又は第三者を利用して次の各号いずれの行為も行わないことを相互に表明し、確約するものとします。\n \n 暴力的な要求行為。\n 法的な責任を超えた不当な要求行為。\n \n 取り引きに関して、脅迫的な言動をし、又は暴力を用いる行為。\n \n \n 風説を流布し、偽計又は威力を用いて相手方の信用を毀損する、又は相手方の業務を妨害する行為。\n \n その他前各号に準ずる行為。\n \n \n \n \n \n \n 第26条(本規約の解釈)\n \n \n 本規約のいずれかの条項又はその一部が、管轄権を有する裁判所により、違法又は無効と判断された場合であっても、その趣旨に最も近い有効な条項を当該無効な条項もしくは部分と置き換えて適用、又は当該条項もしくは部分の趣旨に最も近い有効な条項となるよう、合理的な解釈を加えて適用します。\n \n \n \n \n 第27条(準拠法・合意管轄)\n \n \n 本規約に関する事項の一切は、日本法に準拠して解釈されるものとし、本サービスの利用に関し紛争が生じた際には、大阪地方裁判所を第一審の専属的合意管轄裁判所とします。\n \n \n \n \n 第28条(協議解決)\n \n \n 弊社及びユーザーは、本規約に定めのない事項又は本規約の解釈に疑義が生じた場合、互いに信義誠実の原則に従って協議の上、速やかに解決を図るものとします。\n \n \n \n \n 第29条(本規約の効力)\n \n \n \n 本規約は2025年4月1日から旧利用規約に代わり発効するものとし、過去の規約に優先して適用されるものとします。\n \n \n 本規約の内容と、本規約外における本サービスの説明等が異なる場合には、本規約の内容が優先して適用されるものとします。\n \n \n \n \n 制定2024年4月1日\n 改定2025年6月10日\n \n \n \n \n \n >\n );\n};\n\nexport default TermsOfService;\n","import React from \"react\";\nimport BackgroundBuildingImage from \"../shared/components/atoms/BackgroundBuildingImage\";\nimport Background from \"../shared/components/atoms/Background\";\nimport {\n Box,\n Container,\n Heading,\n VStack,\n Text,\n OrderedList,\n ListItem,\n UnorderedList,\n Link,\n} from \"@chakra-ui/react\";\nimport ShadowCard from \"../shared/components/atoms/ShadowCard\";\nimport BracketsList from \"../shared/components/atoms/BracketsList\";\n\nconst PrivacyPolicy = () => {\n return (\n <>\n \n \n \n \n \n 個人情報保護方針\n \n \n \n ユニオンシステム株式会社(以下「弊社」といいます)は、弊社が提供するサービスを通じて収集した個人を特定できる情報の保護について、社会的責務としての認識をもち、個人情報保護に関する法律及び関連するその他の法令・規範を厳守します。以下の方針に基づき、個人情報の取り扱いに努めます。\n \n \n \n 個人情報の取得\n \n 弊社は、適法かつ公正な手段によって、個人を特定する情報(氏名、性別、メールアドレス、建築士番号、所属団体、所属企業その他の記述により当該本人を識別できるもの)(以下「個人情報」といいます)を取得します。また、弊社は個人情報に該当するか否かにかかわらず、以下の情報を取得します。\n \n \n \n \n 個人情報の利用目的\n \n 弊社は、個人情報を次の目的で利用することがあります。\n
\n ただし、それ以外の目的で利用する必要がある場合には、予め本人の承諾を得ることを前提といたします。また、収集した個人情報の取り扱いを外部に委託する場合には、委託先について厳正な調査を行った上、個人情報の漏えい等の事故が発生しないよう適正な監督を行います。\n \n \n 弊社サービスの提供、運営、維持、保護、改善のため\n \n \n 利用者への連絡、お問い合わせに対する返信のため\n \n \n 弊社サービスに関するお知らせやプロモーション情報の提供のため\n \n \n 利用者に対し、弊社又は弊社のグループ会社その他の第三者の商品又はサービスに関する情報の提供、広告又は宣伝等を行うため(弊社は、利用者の弊社サービスの利用状況等を分析し、利用者の行動傾向、属性等を推定した上で、当該分析・推定結果に基づいた利用者に対する広告・マーケティング等に利用することがあります)\n \n \n 第三者のメーリングサービスを利用して利用者にメールを配信するため\n \n 法令に基づく義務の遂行のため\n \n \n \n \n 仮名加工情報\n \n 弊社は、利用者の個人情報を元に、他の情報と照合しない限り特定の個人を識別できないように加工して得られる個人に関する情報である仮名情報を作成しています。\n 作成した仮名加工情報は、以下の利用目的を達するために必要な範囲で利用します。\n \n \n \n 弊社サービスの提供、運営、維持、保護、改善のため\n \n \n 利用者への連絡、お問い合わせに対する返信のため\n \n \n 弊社サービスに関するお知らせやプロモーション情報の提供のため\n \n 法令に基づく義務の遂行のため\n \n \n \n 匿名加工情報\n \n 弊社は、利用者の個人情報を元に、特定の個人を識別できないように加工して得られる情報であり、元の個人情報を復元できないようにしたものである匿名加工情報を継続的に作成し、第三者に提供します。\n \n \n 【作成】\n \n \n 作成する匿名加工情報に含まれる個人に関する情報の項目\n
\n 利用者の個人属性情報(性別、年齢(年代)、職種、居住地の都道府県、サービス利用履歴)\n \n \n \n \n 【提供】\n \n \n 作成する匿名加工情報に含まれる個人に関する情報の項目\n
\n 利用者の個人属性情報(性別、年齢(年代)、職種、居住地の都道府県、サービス利用履歴)\n \n \n 匿名加工情報の提供の方法\n
\n データファイルの暗号化やパスワード保護する等、セキュリティを確保した上で、データの送付もしくは記録媒体での送付又は書類の提供\n \n \n \n \n \n 個人関連情報\n \n 弊社は、第三者から、識別子や行動履歴等、利用者に関連する情報(個人関連情報)の提供を受け、当該情報を弊社が管理する利用者の個人情報と紐付けることで個人情報として取得することがあります。具体的には、Cookie、モバイル広告識別子、メールアドレスや電話番号(不可逆的な変換処理がされたものを含みます)、広告の閲覧回数等の広告配信関連ログ、位置情報その他の行動履歴等を取得し、弊社が管理する個人情報と紐付けて利用する場合があります。この場合は、本個人情報保護指針第2項に規定する利用目的の達成に必要な範囲で、個人情報として適切に取り扱います。\n \n \n \n Cookieの使用\n \n 弊社は、利用者の利便性を高める目的で、Cookieを利用しています。\n Cookieは、利用者のブラウザを識別するために使用され、個人を特定する情報は含まれません。利用者はブラウザの設定でCookieの使用を拒否できますが、その場合、一部の機能が制限される可能性があります。\n \n \n \n 外部送信情報\n \n 弊社のウェブサイトでは、利用者の利便性向上等のために、外部機関のサービスを利用しています。従って、弊社のウェブサイトを利用することにより、利用者の情報が外部機関に送信されることがあります。情報送信先の外部機関の事業者名、送信される利用者の情報、情報の利用目的は以下のとおりです。\n \n \n 外部送信先の事業者名: Google LLC\n
\n 利用目的: Googleアナリティクス\n
\n 利用者の情報:\n 端末又はアプリの情報、ネットワーク情報、アクセス情報\n
\n \n Terms of Service | Google Analytics – Google\n \n
\n \n プライバシー ポリシー – ポリシーと規約 – Google\n \n
\n \n Google\n のサービスを使用するサイトやアプリから収集した情報の\n Google による使用 – ポリシーと規約 – Google\n \n
\n
\n 外部送信先の事業者名: Amazon Web Services, Inc.\n
\n 利用目的: AWS\n
\n 利用者の情報:\n 端末又はアプリの情報、アクセス情報、ユーザーの行動情報\n
\n \n プライバシー通知(Amazon Web Services)\n \n
\n \n データプライバシー - アマゾン ウェブ サービス(AWS)\n \n
\n \n AWS利用規約\n \n \n \n \n 個人情報の管理と安全性\n \n 弊社は個人情報の正確性を保ち、適切な運用・管理を行います。個人情報の紛失、破壊、改ざん、漏えいを防ぐため、適切なセキュリティ対策を講じます。\n \n \n \n 個人情報の第三者提供\n \n 本人の承諾を得ることなく、個人情報を第三者に提供することはありません。ただし、法令に基づく場合や上記2.のいずれかに該当する場合は除きます。\n \n \n \n 情報の開示・訂正・利用停止・消去等\n \n 本人は自身の個人情報について、開示・訂正・利用停止・消去等を求める権利を有しています。これらの要求がある場合には、具体的な方法については、以下お問い合わせ先にお問い合わせください。迅速かつ誠実に対応します。\n \n \n \n 安全管理組織・体制\n \n 弊社は個人情報保護のための組織と体制を整備し、適切な管理を実施します。個人情報保護管理責任者を置き、体制を継続的に見直し、改善に努めます。\n
\n また、弊社は、ジオトラスト社の「トゥルービジネスID」を取得しています。\n
\n ご入力いただくお客様の個人情報は、SSLとよばれる暗号化技術によって保護されていますので、安心してご利用ください。(SSL対応のブラウザでないと正しく動作しない場合がありますので、ご注意ください)\n \n \n \n 個人情報保護方針の変更\n \n 弊社は、必要に応じて個人情報保護方針を変更することがあります。変更がある場合は、ウェブサイト上でお知らせします。\n \n \n \n \n 個人情報方針に関するお問い合わせ先\n
\n 〒542-0012 大阪市中央区谷町6-1-16 ナルカワビル\n
\n ユニオンシステム株式会社\n
\n info@unions.co.jp\n
\n
\n 制定 2024年4月1日\n
\n
\n ユニオンシステム株式会社\n
\n 代表取締役社長 吉田 健一郎\n \n \n \n \n \n >\n );\n};\n\nexport default PrivacyPolicy;\n","import { useState } from \"react\";\n\nconst useFiles = (\n initial: File[],\n {\n maxCount,\n maxTotalBytesize,\n }: {\n maxCount: number;\n maxTotalBytesize: number;\n },\n) => {\n const [files, setFiles] = useState(initial);\n\n const append = (\n newFiles: File[],\n onError: (reason: \"maxCount\" | \"maxTotalBytesize\") => void,\n ) => {\n if (files.length + newFiles.length > maxCount) {\n onError(\"maxCount\");\n return;\n }\n if (\n [...files, ...newFiles].reduce((acc, file) => acc + file.size, 0) >\n maxTotalBytesize\n ) {\n onError(\"maxTotalBytesize\");\n return;\n }\n setFiles((prev) => [...prev, ...newFiles]);\n };\n\n const remove = (index: number) => {\n setFiles((prev) => prev.filter((_, idx) => idx !== index));\n };\n\n return {\n files,\n append,\n remove,\n };\n};\n\nexport default useFiles;\n","import {\n Box,\n CloseButton,\n Container,\n Flex,\n HStack,\n Heading,\n Link,\n List,\n Stack,\n} from \"@chakra-ui/react\";\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport React, { useEffect, useState } from \"react\";\nimport { Controller, UseFormReturn, useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport { contactsPath, privacyPolicyPath, rootPath } from \"../../../routes\";\nimport { Flash } from \"../../shared/lib/types\";\nimport useFiles from \"../shared/lib/useFiles\";\nimport useFlash from \"../shared/lib/useFlash\";\nimport useRequest from \"../shared/lib/useRequest\";\nimport { SharedCurrentUser } from \"../shared/lib/types\";\nimport { Button } from \"../shared/components/atoms\";\nimport Background from \"../shared/components/atoms/Background\";\nimport Dropzone from \"../shared/components/atoms/Dropzone\";\nimport { FormLabel, Input, Textarea } from \"../shared/components/atoms/form\";\nimport Header from \"../shared/components/atoms/Header\";\nimport ShadowCard from \"../shared/components/atoms/ShadowCard\";\nimport DescriptionIcon from \"../shared/components/icons/DescriptionIcon\";\nimport Application from \"../shared/components/layouts/Application\";\nimport { uploadFile } from \"../../shared/lib/uploadFile\";\n\nconst schema = () => {\n return yup.object().shape({\n name: yup.string().trim().required().label(\"氏名\"),\n email: yup.string().trim().email().required().label(\"メールアドレス\"),\n phone_number: yup.string().trim().phoneNumber().ensure().label(\"電話番号\"),\n body: yup.string().trim().required().label(\"お問い合わせ内容\"),\n });\n};\n\ntype SchemaType = yup.InferType>;\n\nconst ContactsNew = ({\n flash,\n currentUser,\n}: {\n flash: Flash;\n currentUser: SharedCurrentUser;\n}) => {\n const methods = useForm({\n defaultValues: {\n name: currentUser?.is_approved ? currentUser.full_name : \"\",\n email: currentUser?.email ?? \"\",\n phone_number: \"\",\n body: \"\",\n },\n resolver: yupResolver(schema()),\n });\n const { control, handleSubmit, reset } = methods;\n const { files, append, remove } = useFiles([], {\n maxCount: 3,\n maxTotalBytesize: 5 * 1000 * 1000,\n });\n const request = useRequest();\n const showFlash = useFlash();\n const [showConfirmation, setShowConfirmation] = useState(false);\n const onSubmit = async (data: SchemaType) => {\n if (showConfirmation) {\n const signed_ids = await Promise.all(\n files.map(async (file) => {\n const res = await uploadFile({ file });\n return res.blob.signed_id;\n }),\n );\n const res = await request(contactsPath(), \"POST\", {\n contact: { ...data, files: signed_ids },\n });\n if (res.ok) {\n reset();\n location.href = rootPath();\n }\n } else {\n setShowConfirmation(true);\n }\n };\n\n useEffect(() => {\n window.scrollTo(0, 0);\n }, [showConfirmation]);\n\n return (\n \n \n \n \n \n \n \n お問い合わせフォーム\n \n {showConfirmation && (\n \n 入力内容確認\n \n )}\n \n \n 受付時間内に届いたお問い合わせは、原則即日対応いたします。\n \n \n <受付時間>\n \n 月曜日~金曜日\n (祝日、弊社休業日を除く)\n\n 10:00~12:00 / 13:00~16:00\n \n ご入力いただきましたお客様の個人情報は、お問い合わせの対応に必要な範囲で使用いたします。\n
\n 詳細につきましては、「\n \n 個人情報保護方針\n \n 」をご覧ください。\n \n \n \n {showConfirmation ? (\n setShowConfirmation(false)}\n />\n ) : (\n \n (\n \n )}\n />\n (\n \n )}\n />\n (\n \n )}\n />\n (\n \n }\n />\n )}\n />\n \n 添付ファイル\n {\n append(acceptedFiles, (reason) => {\n switch (reason) {\n case \"maxCount\":\n showFlash({\n error: \"添付ファイルは3つまでです\",\n });\n break;\n case \"maxTotalBytesize\":\n showFlash({\n error: \"ファイルサイズの上限は5MBです\",\n });\n break;\n }\n });\n }}\n />\n \n PNGもしくはJPGファイルで、5MB未満のサイズでアップロードしてください。\n
\n 添付可能なファイルは3つまでです。\n \n \n \n \n \n )}\n \n \n \n \n \n \n );\n};\n\nconst Confirmation = ({\n methods,\n files,\n onBack,\n}: {\n methods: UseFormReturn;\n files: File[];\n onBack: () => void;\n}) => {\n const data = methods.getValues();\n return (\n <>\n \n {[\n { label: \"氏名\", required: true, value: data.name },\n { label: \"メールアドレス\", required: true, value: data.email },\n { label: \"電話番号\", required: false, value: data.phone_number },\n { label: \"お問い合わせ内容\", required: true, value: data.body },\n {\n label: \"添付ファイル\",\n required: false,\n value: ,\n },\n ].map(({ label, required, value }) => (\n \n {label}\n \n {value}\n \n \n ))}\n \n \n \n \n \n >\n );\n};\n\nconst FileList = ({\n files,\n onRemove,\n}: {\n files: File[];\n onRemove?: (idx: number) => void;\n}) => {\n return (\n // CloseButtonの高さの影響を受けるので調整する\n \n {files.map((file, idx) => (\n \n \n {file.name}\n {onRemove && onRemove(idx)} />}\n \n ))}\n
\n );\n};\n\nexport default ContactsNew;\n","import React from \"react\";\nimport {\n Container,\n Flex,\n Image,\n Text,\n Stack,\n Link,\n VStack,\n HStack,\n} from \"@chakra-ui/react\";\nimport ShadowCard from \"../shared/components/atoms/ShadowCard\";\nimport Application from \"../shared/components/layouts/Application\";\nimport { newUserPersonalInfoApplicationPath, rootPath } from \"../../../routes\";\nimport BackgroundBuildingImage from \"../shared/components/atoms/BackgroundBuildingImage\";\nimport Background from \"../shared/components/atoms/Background\";\nimport iDCardImage from \"./svg/img-idCard.svg\";\nimport ArrowIcon from \"./svg/img-arrow.svg\";\nimport SeeAllMessageImage from \"./svg/img-seeAllMessage.svg\";\nimport talkImage from \"./svg/img-talk.svg\";\nimport watchVideoImage from \"./svg/img-watchVideo.svg\";\nimport { Flash } from \"../../shared/lib/types\";\nimport { LogoSVG } from \"../svg\";\n\nconst ConfirmationDonesShow = ({ flash }: { flash: Flash }) => {\n return (\n \n \n \n \n \n \n \n \n 仮登録が完了しました\n \n \n \n A-Loopでは、建築士の皆さまが安心して\n \n \n 交流できる場をご提供するため、\n \n
\n \n 本人確認が完了したユーザーのみ\n \n \n すべての機能を使用することができます。{\" \"}\n \n \n \n \n \\n \n \n 続けて入力\n \n \n /\n \n \n \n \n \n \n \n 本人確認の登録\n \n に進む\n \n \n \n \n \n \n 本人確認が完了するとできること\n \n \n \n \n \n 勉強会の動画視聴が可能\n \n \n \n \n \n 勉強会で質問や投稿が可能\n \n \n \n \n \n Q&Aに投稿されたすべての質問、コメントが閲覧可能\n \n \n \n \n \n 本人確認の登録をせずA-Loopを利用する\n \n \n \n \n \n \n );\n};\n\nexport default ConfirmationDonesShow;\n","import { Container, Flex, Heading, HStack, Stack } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport { rootPath } from \"../../../routes\";\nimport { Flash, Pagy } from \"../../shared/lib/types\";\nimport Background from \"../shared/components/atoms/Background\";\nimport GoBackLink from \"../shared/components/atoms/GoBackLink\";\nimport Header from \"../shared/components/atoms/Header\";\nimport Pagination from \"../shared/components/atoms/Pagination\";\nimport ShadowCard from \"../shared/components/atoms/ShadowCard\";\nimport AwarenessIcon from \"../shared/components/icons/AwarenessIcon\";\nimport Application from \"../shared/components/layouts/Application\";\nimport { SharedAnnouncements, SharedCurrentUser } from \"../shared/lib/types\";\nimport AnnouncementListItem from \"../shared/components/atoms/AnnouncementListItem\";\n\nconst AnnouncementsIndex = ({\n announcements,\n pagy,\n flash,\n currentUser,\n}: {\n announcements: SharedAnnouncements;\n pagy: Pagy;\n flash: Flash;\n currentUser: SharedCurrentUser;\n}) => {\n return (\n \n \n \n \n \n ホーム画面へ戻る\n \n \n \n \n お知らせ\n \n \n \n {announcements.map((announcement) => (\n \n ))}\n \n \n {\n const params = new URLSearchParams(location.search);\n params.set(\"page\", page.toString());\n location.search = params.toString();\n }}\n />\n \n \n \n \n \n \n );\n};\n\nexport default AnnouncementsIndex;\n","import { Box, Container, Divider, Heading, Stack } from \"@chakra-ui/react\";\nimport dayjs from \"dayjs\";\nimport { default as React } from \"react\";\nimport { Helmet } from \"react-helmet\";\nimport { announcementsPath } from \"../../../routes\";\nimport { Flash } from \"../../shared/lib/types\";\nimport AnnouncementCategory from \"../shared/components/atoms/AnnouncementCategory\";\nimport Background from \"../shared/components/atoms/Background\";\nimport GoBackLink from \"../shared/components/atoms/GoBackLink\";\nimport Header from \"../shared/components/atoms/Header\";\nimport ShadowCard from \"../shared/components/atoms/ShadowCard\";\nimport Application from \"../shared/components/layouts/Application\";\nimport { SharedCurrentUser } from \"../shared/lib/types\";\nimport { Announcement } from \"./lib/types\";\n\nconst AnnouncementsShow = ({\n announcement,\n flash,\n currentUser,\n}: {\n announcement: Announcement;\n flash: Flash;\n currentUser: SharedCurrentUser;\n}) => {\n return (\n \n \n \n \n \n \n \n お知らせ一覧へ戻る\n \n \n \n \n \n {announcement.title}\n \n \n {dayjs(\n announcement.publish_started_at ?? announcement.created_at,\n ).format(\"L\")}\n \n \n \n \n \n \n \n \n \n );\n};\n\nexport default AnnouncementsShow;\n","import { Box, BoxProps, List, ListItem } from \"@chakra-ui/react\";\nimport React from \"react\";\n\nconst EmailNotReceivedMessage = (props: BoxProps) => {\n return (\n \n \n メールが届かない場合は…\n \n \n \n ・迷惑メールのフォルダに振り分けられていないかご確認ください。\n \n ・URL付きのメールを受信許可に設定してください。\n \n ・ドメイン「aloop.jp」を受信許可に設定してください。\n \n ・メールボックスの保存容量をご確認ください。\n \n ・セキュリティソフト、ウイルス対策ソフトの設定をご確認ください。\n \n \n ・正しいメールアドレスでない可能性があります。お手数ですが再度最初から登録をお願いいたします。\n \n
\n \n );\n};\n\nexport default EmailNotReceivedMessage;\n","import { Box, Container, Flex, Image, Link, Text } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport { newUserDatabaseAuthenticationRegistrationPath } from \"../../../routes\";\nimport Background from \"../shared/components/atoms/Background\";\nimport BackgroundBuildingImage from \"../shared/components/atoms/BackgroundBuildingImage\";\nimport ShadowCard from \"../shared/components/atoms/ShadowCard\";\nimport Application from \"../shared/components/layouts/Application\";\nimport { Flash } from \"../../shared/lib/types\";\nimport { LogoSVG } from \"../svg\";\nimport EmailNotReceivedMessage from \"../../shared/components/EmailNotReceivedMessage\";\n\nconst ConfirmationSentsShow = ({ flash }: { flash: Flash }) => {\n return (\n \n \n \n \n \n \n \n \n 確認メールを送信しました\n \n \n \n \n ご登録いただいたメールアドレスに\n \n \n 確認メールを送信しました。\n \n \n \n \n メール内のURLを60分以内に\n \n \n クリックして仮登録を行ってください。\n \n \n \n \n ご登録いただいたメールアドレスに確認メールを送信しました。{\" \"}\n
\n メール内のURLを60分以内\n にクリックして仮登録を行ってください。\n \n \n \n 会員登録画面に戻る\n \n \n \n \n \n \n );\n};\n\nexport default ConfirmationSentsShow;\n","import React from \"react\";\nimport { Box, Container, Flex, Image, Link, Text } from \"@chakra-ui/react\";\nimport ShadowCard from \"../shared/components/atoms/ShadowCard\";\nimport Application from \"../shared/components/layouts/Application\";\nimport { newUserDatabaseAuthenticationSessionPath } from \"../../../routes\";\nimport BackgroundBuildingImage from \"../shared/components/atoms/BackgroundBuildingImage\";\nimport Background from \"../shared/components/atoms/Background\";\nimport { Flash } from \"../../shared/lib/types\";\nimport { LogoSVG } from \"../svg\";\nimport EmailNotReceivedMessage from \"../../shared/components/EmailNotReceivedMessage\";\n\nconst ResetPasswordSentsShow = ({ flash }: { flash: Flash }) => {\n return (\n \n \n \n \n \n \n \n \n パスワード再設定メールを送信しました\n \n \n \n \n ご登録いただいたメールアドレスに\n \n \n 確認メールを送信しました。\n \n \n \n \n メール内のURLを60分以内に\n \n \n クリックして仮登録を行ってください。\n \n \n \n \n ご入力いただいたメールアドレスに確認メールを送信しました。{\" \"}\n
\n メール内のURLを60分以内\n にクリックしてパスワード再設定を行ってください。\n \n \n \n ログイン画面に戻る\n \n \n \n \n \n \n );\n};\n\nexport default ResetPasswordSentsShow;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst ContactSupportIcon = (props: IconProps) => (\n \n \n \n \n \n \n \n \n);\n\nexport default ContactSupportIcon;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst FilledChatBubbleIcon = (props: IconProps) => (\n \n \n \n \n \n \n \n \n);\n\nexport default FilledChatBubbleIcon;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst ModeEditIcon = (props: IconProps) => (\n \n \n \n \n);\n\nexport default ModeEditIcon;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst BusinessCenterIcon = (props: IconProps) => (\n \n \n \n \n \n \n \n \n);\n\nexport default BusinessCenterIcon;\n","import {\n Box,\n Flex,\n Grid,\n GridItem,\n HStack,\n Link,\n Show,\n Stack,\n Text,\n} from \"@chakra-ui/react\";\nimport dayjs from \"dayjs\";\nimport React from \"react\";\nimport { Event } from \"../lib/types\";\n\nconst PointInfo = ({ event }: { event: Event }) => {\n return (\n \n \n \n {event.within_calculation_period && (\n \n \\n イベント参加中\n /\n \n )}\n \n \n \n \n \n \n \n \n \n \n \n イベント\n \n 獲得数\n \n {event.confirmed ? \"確定\" : \"集計中\"}\n \n \n \n \n \n {event.point.toLocaleString()}\n \n \n Loop\n \n \n \n \n \n \n \n \n {event.name}\n \n \n {dayjs(event.calculation_finished_at).format(\"YYYY/MM/DD\")}まで\n \n \n \n \n {!event.confirmed && (\n \n \n *\n \n \n イベント終了後に正式なLoopポイントが確定します。イベントLoopの計算方法は\n こちら\n \n \n )}\n \n );\n};\n\nexport default PointInfo;\n","import {\n Box,\n Container,\n Flex,\n FlexProps,\n Grid,\n GridItem,\n HStack,\n Heading,\n HeadingProps,\n Link,\n Spinner,\n Stack,\n Tab,\n TabIndicator,\n TabList,\n TabPanel,\n TabPanels,\n Tabs,\n Text,\n} from \"@chakra-ui/react\";\nimport { useInfiniteQuery } from \"@tanstack/react-query\";\nimport React, { ReactNode, useContext, useEffect, useState } from \"react\";\nimport { InView } from \"react-intersection-observer\";\nimport {\n editProfileBasicPath,\n userCommentedRepliedPostsPath,\n userPostsPath,\n} from \"../../../routes\";\nimport { Flash } from \"../../shared/lib/types\";\nimport { Button } from \"../shared/components/atoms\";\nimport Background from \"../shared/components/atoms/Background\";\nimport CustomLinkLinkify from \"../shared/components/atoms/CustomLinkLinkify\";\nimport Footer from \"../shared/components/atoms/Footer\";\nimport Header from \"../shared/components/atoms/Header\";\nimport PostSummaryCard from \"../shared/components/atoms/PostSummaryCard\";\nimport UserAvatar from \"../shared/components/atoms/UserAvatar\";\nimport BookmarkedIcon from \"../shared/components/icons/BookmarkedIcon\";\nimport ContactSupportIcon from \"../shared/components/icons/ContactSupportIcon\";\nimport FilledChatBubbleIcon from \"../shared/components/icons/FilledChatBubbleIcon\";\nimport ModeEditIcon from \"../shared/components/icons/ModeEditIcon\";\nimport ThumbUpFilledIcon from \"../shared/components/icons/ThumbUpFilledIcon\";\nimport Application from \"../shared/components/layouts/Application\";\nimport { SharedApprovedCurrentUser } from \"../shared/lib/types\";\nimport useRequest from \"../shared/lib/useRequest\";\nimport BusinessCenterIcon from \"../shared/components/icons/BusinessCenterIcon\";\nimport PointInfo from \"./components/PointInfo\";\nimport { FlipperContext } from \"../../shared/lib/FlipperContext\";\nimport {\n Event,\n UserCommentedRepliedPostsIndex,\n UserPostsIndex,\n} from \"./lib/types\";\n\ntype User = {\n code: string;\n full_name: string;\n full_name_kana: string;\n architect_license: {\n name: string;\n };\n architect_type: {\n name: string;\n };\n bio: string;\n age: string;\n other_architect_license: string;\n profile_image_url: string;\n avatar_bgcolor: string;\n industry: string | null;\n user_position: string | null;\n prefecture: {\n name: string;\n };\n additional_licenses: {\n name: string;\n }[];\n workplace: {\n name: string;\n name_kana: string;\n postal_code: string;\n address1: string;\n address2: string;\n address3: string;\n phone_number: string;\n website_url: string;\n business_detail: string;\n prefecture: {\n name: string;\n } | null;\n };\n};\n\nconst UsersShow = ({\n user,\n isOwn,\n flash,\n currentUser,\n activity,\n event,\n}: {\n user: User;\n isOwn: boolean;\n flash: Flash;\n currentUser: SharedApprovedCurrentUser;\n activity: Record;\n event?: Event;\n}) => {\n const workplaceAddress = (workplace: User[\"workplace\"]) => {\n return `${workplace.prefecture?.name ?? \"\"}${workplace.address1}${workplace.address2}${workplace.address3}`;\n };\n const [tabIndex, setTabIndex] = useState(0);\n const flipper = useContext(FlipperContext);\n\n useEffect(() => {\n const params = new URLSearchParams(location.search);\n const tab = params.get(\"tab\");\n switch (tab) {\n case \"qa\":\n setTabIndex(0);\n break;\n case \"profile\":\n setTabIndex(1);\n break;\n }\n }, []);\n\n return (\n \n \n \n \n \n {isOwn && (\n \n \n \n )}\n \n \n \n \n \n \n \n {!flipper.remove_name_kana_feature && (\n \n {user.full_name_kana}\n \n )}\n \n {user.full_name}\n \n \n \n \n \n {user.architect_license.name}\n \n \n {user.prefecture.name}在住\n /\n {user.age}歳\n \n \n {user.workplace.name !== \"\" && (\n \n \n \n {user.workplace.name}\n {user.workplace.prefecture && (\n <>({user.workplace.prefecture.name})>\n )}\n \n \n )}\n \n \n {user.bio}\n \n {flipper.point_feature && event && (\n \n \n \n )}\n \n \n \n \n {isOwn && (\n \n Q&A情報\n \n )}\n \n プロフィール詳細\n \n \n \n {isOwn && (\n \n \n \n \n アクティビティ\n \n \n \n }\n text=\"質問の投稿数\"\n count={activity.posts_count}\n />\n \n }\n text=\"回答の投稿数\"\n count={activity.comments_replies_count}\n />\n }\n text=\"ブックマーク数\"\n count={activity.bookmarks_count}\n />\n \n }\n text=\"[参考になった]獲得数\"\n count={activity.received_usefuls_count}\n />\n \n \n \n \n 質問した投稿\n \n \n 回答した投稿\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n )}\n \n \n \n \n \n 基本情報\n \n \n 職種\n \n {user.architect_type.name}\n \n \n \n 取得している資格\n \n \n {user.architect_license.name}\n {user.additional_licenses.map(({ name }) => (\n \n {name}\n \n ))}\n \n {user.other_architect_license !== \"\" && (\n \n \n その他\n \n \n {user.other_architect_license}\n \n \n )}\n \n \n {user.industry != null && (\n \n 業種\n {user.industry}\n \n )}\n {user.user_position != null && (\n \n ポジション\n \n {user.user_position}\n \n \n )}\n \n \n {Object.values(user.workplace).some(\n (value) => value !== \"\",\n ) && (\n \n 勤務地情報\n \n {user.workplace.name !== \"\" && (\n \n 会社名\n \n {user.workplace.name}\n \n \n )}\n {user.workplace.name_kana !== \"\" && (\n \n 会社名ふりがな\n \n {user.workplace.name_kana}\n \n \n )}\n {(workplaceAddress(user.workplace) !== \"\" ||\n user.workplace.postal_code !== \"\") && (\n \n 所在地\n \n {user.workplace.postal_code !== \"\" && (\n 〒{user.workplace.postal_code}\n )}\n {workplaceAddress(user.workplace)}\n \n \n )}\n {user.workplace.phone_number !== \"\" && (\n \n 電話番号\n \n {user.workplace.phone_number}\n \n \n )}\n {user.workplace.website_url !== \"\" && (\n \n Webサイト\n \n \n {user.workplace.website_url}\n \n \n \n )}\n {user.workplace.business_detail !== \"\" && (\n \n 事業内容\n \n \n {user.workplace.business_detail}\n \n \n \n )}\n \n \n )}\n \n \n \n \n \n \n \n \n );\n};\n\nconst ActivityItem = ({\n icon,\n text,\n count,\n}: {\n icon: ReactNode;\n text: string;\n count: number;\n}) => {\n return (\n \n \n {icon}\n \n {text}\n \n \n {count}\n \n \n \n );\n};\n\nconst Posts = ({\n currentUser,\n path,\n}: {\n currentUser: SharedApprovedCurrentUser;\n path: string;\n}) => {\n const request = useRequest();\n\n const fetchUserPosts = async ({\n pageParam,\n }: {\n pageParam: number;\n }): Promise => {\n const res = await request(path, \"GET\", {\n page: pageParam,\n });\n return res.json();\n };\n\n const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =\n useInfiniteQuery({\n queryKey: [path],\n queryFn: fetchUserPosts,\n initialPageParam: 1,\n getNextPageParam: (lastPage) => lastPage.page_metadata.next,\n });\n\n return (\n \n {(data?.pages ?? []).map((page) =>\n page.posts.map((post) => (\n \n )),\n )}\n {\n if (hasNextPage && !isFetchingNextPage && inView) {\n await fetchNextPage();\n }\n }}\n >\n {isFetchingNextPage && (\n \n \n \n )}\n \n );\n};\n\nconst ProfileItem = ({ children }: { children: ReactNode }) => {\n return (\n \n {children}\n \n );\n};\n\nconst ProfileItemLeft = ({ children }: { children: ReactNode }) => {\n return (\n \n {children}\n \n );\n};\n\nconst ProfileItemRight = ({ children }: { children: ReactNode }) => {\n return (\n \n {children}\n \n );\n};\n\nconst SectionTitle = ({\n children,\n ...props\n}: { children: ReactNode } & HeadingProps) => {\n return (\n \n {children}\n \n );\n};\n\nconst EditProfileButton = (props: FlexProps) => {\n return (\n \n \n \n );\n};\n\nexport default UsersShow;\n","import { Box, Text } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport useFileState from \"../../lib/useFileState\";\n\nconst DropzonePreview = ({\n fileState,\n editable = true,\n}: {\n fileState: ReturnType;\n editable?: boolean;\n}) => {\n return (\n <>\n \n {/* PDFがあり得るのでobject */}\n \n \n {editable && (\n fileState.setFile(undefined)}\n mt={2}\n w=\"fit-content\"\n ml=\"auto\"\n >\n ファイルを変更\n \n )}\n >\n );\n};\n\nexport default DropzonePreview;\n","import React from \"react\";\nimport useFileState from \"../../lib/useFileState\";\nimport Dropzone from \"./Dropzone\";\nimport DropzonePreview from \"./DropzonePreview\";\nimport { Box } from \"@chakra-ui/react\";\n\nconst ArchitectLicenseImageField = ({\n fileState,\n}: {\n fileState: ReturnType;\n}) => {\n return (\n <>\n {fileState.file == null ? (\n {\n fileState.setFile(files[0]);\n }}\n name=\"architect_license_image\"\n message={\n \n 構造一級建築士、または設備一級建築士をお持ちの方は、一級建築士の免許証画像をアップロードしてください。\n
\n PNG、JPGもしくはPDFファイルで、5MB未満のサイズでアップロードしてください。\n \n }\n error={fileState.error}\n accept={{\n \"image/png\": [],\n \"image/jpeg\": [],\n \"application/pdf\": [],\n }}\n />\n ) : (\n \n )}\n >\n );\n};\n\nexport default ArchitectLicenseImageField;\n","import { Box, BoxProps, Heading } from \"@chakra-ui/react\";\nimport React from \"react\";\n\nconst SectionTitle = (props: BoxProps) => {\n return (\n \n \n {props.children}\n \n \n );\n};\n\nexport default SectionTitle;\n","import React, { useEffect } from \"react\";\nimport { Box, Flex } from \"@chakra-ui/react\";\n\nconst SiteSeal = () => {\n useEffect(() => {\n if (location.host === \"aloop.jp\") {\n const script = document.createElement(\"script\");\n\n script.src = \"/site_seal.js\";\n script.async = true;\n\n document.body.appendChild(script);\n\n return () => {\n document.body.removeChild(script);\n };\n }\n }, []);\n return (\n \n \n \n \n \n お送りいただくお客様の個人情報は\n
\n SSL暗号化通信により保護されます。\n \n \n );\n};\n\nexport default SiteSeal;\n","import {useEffect, useState} from \"react\";\nimport * as AutoKana from \"vanilla-autokana\";\n\nconst useAutokana = (source: string, target: string) => {\n const [autokana, setAutokana] = useState()\n\n useEffect(() => {\n const autokana = AutoKana.bind(source, target)\n setAutokana(autokana);\n }, [source, target]);\n\n return autokana;\n};\n\nexport default useAutokana;\n","import React from \"react\";\nimport { useCallback, useState } from \"react\";\n\nconst useFileState = ({\n formRef = undefined,\n required = false,\n fileSize = 5000,\n firstPreview = undefined,\n}: {\n formRef?: React.RefObject;\n required?: boolean;\n fileSize?: number;\n firstPreview?: string | undefined;\n} = {}) => {\n const [file, _setFile] = useState();\n const [error, setError] = useState();\n const [preview, setPreview] = useState(firstPreview);\n\n const setFile = useCallback(\n (file: File | undefined) => {\n if (file && file.size / 1000 > fileSize) {\n setError(\n `アップロードしたファイルが${fileSize / 1000}MBを超えています`,\n );\n return;\n }\n\n setError(undefined);\n setPreview(file ? URL.createObjectURL(file) : undefined);\n _setFile(file);\n },\n [fileSize],\n );\n\n const validate = useCallback(() => {\n if (required && file == null && preview == null) {\n setError(\"ファイルは必須です\");\n formRef?.current?.scrollIntoView({ block: \"center\" });\n return false;\n }\n\n setError(undefined);\n return true;\n }, [file, required, preview, formRef]);\n\n const isDirty = preview !== firstPreview;\n\n const reset = useCallback(() => {\n setFile(undefined);\n setPreview(firstPreview);\n }, [setPreview, firstPreview, setFile]);\n\n return { file, setFile, validate, preview, error, isDirty, reset };\n};\n\nexport default useFileState;\n","import { Flex, Text } from \"@chakra-ui/react\";\nimport \"cropperjs/dist/cropper.css\";\nimport React, { useState } from \"react\";\nimport Select from \"../../../shared/components/atoms/form/Select\";\nimport dayjs from \"dayjs\";\nimport { InputError } from \"../../../shared/components/atoms/form\";\nimport { calcAge } from \"../../../shared/lib/dateUtils\";\n\nconst BirthdaySelect = ({\n value,\n onChange: dispatchOnChange,\n error,\n}: {\n value: string;\n onChange: (date: string | undefined) => void;\n error?: string;\n}) => {\n const valueDay = dayjs(value);\n\n // valueDayが空文字の時は全てNaNになる\n const [year, setYear] = useState(valueDay.year());\n const [month, setMonth] = useState(valueDay.month() + 1);\n const [day, setDay] = useState(valueDay.date());\n\n const currentYear = dayjs().year();\n const yearOptions = [];\n for (let i = currentYear - 99; i <= currentYear - 18; i++) {\n yearOptions.push(i);\n }\n const monthOptions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];\n const dayOptions = (year: number, month: number) => {\n if (isNaN(year) || isNaN(month))\n return [...Array(28).keys()].map((i) => i + 1);\n\n const days = dayjs(`${year}-${month}`).daysInMonth();\n const result = [];\n for (let i = 1; i <= days; i++) {\n result.push(i);\n }\n return result;\n };\n\n const onChange = (year: number, month: number, day: number) => {\n setYear(year);\n setMonth(month);\n setDay(day);\n\n if (!isNaN(year) && !isNaN(month) && !isNaN(day)) {\n dispatchOnChange(`${year}-${month}-${day}`);\n } else {\n dispatchOnChange(undefined);\n }\n };\n\n return (\n <>\n \n \n \n \n 年\n \n \n \n \n \n 月\n \n \n \n \n \n 日\n \n \n \n {`( ${calcAge(year, month, day)}歳 )`}\n \n \n {error != null && {error}}\n >\n );\n};\n\nexport default BirthdaySelect;\n","import { Box, Flex, HStack, Image, Stack, Text } from \"@chakra-ui/react\";\nimport React, { useContext } from \"react\";\nimport { Button } from \"../../shared/components/atoms\";\nimport { FormLabel } from \"../../shared/components/atoms/form\";\nimport SectionTitle from \"../../shared/components/atoms/SectionTitle\";\nimport { Options } from \"../../shared/lib/types\";\nimport SiteSeal from \"../../shared/components/atoms/SiteSeal\";\nimport { LogoSVG } from \"../../svg\";\nimport dayjs from \"dayjs\";\nimport { UserUserPersonalInfoApplicationsTemporaryUserInput } from \"../lib/types\";\nimport { FlipperContext } from \"../../../shared/lib/FlipperContext\";\n\nconst Confirmation = ({\n values,\n onClickFix,\n architectLicenses,\n architectLicenseImagePreview,\n temporaryUserInput,\n prefectures,\n industries,\n userPositions,\n isSubmitting,\n onSubmit,\n}: {\n values: Record;\n onClickFix: () => void;\n architectLicenses: Options;\n architectLicenseImagePreview: string;\n temporaryUserInput: UserUserPersonalInfoApplicationsTemporaryUserInput;\n prefectures: Options;\n industries: Options;\n userPositions: Options;\n isSubmitting: boolean;\n onSubmit: () => void;\n}) => {\n const flipper = useContext(FlipperContext);\n return (\n <>\n \n \n \n \n \n \n 本人確認登録\n {\" \"}\n \n 入力内容確認\n \n \n\n {temporaryUserInput.nickname == null && (\n <>\n 公開情報\n \n \n ニックネーム\n {values.nickname}\n \n \n >\n )}\n\n {[\n temporaryUserInput.birthday,\n temporaryUserInput.industry_id,\n temporaryUserInput.prefecture_id,\n temporaryUserInput.user_position_id,\n ].some((value) => value == null) && (\n <>\n 非公開情報\n \n {temporaryUserInput.birthday == null && (\n \n 生年月日\n {dayjs(values.birthday).format(\"LL\")}\n \n )}\n {temporaryUserInput.prefecture_id == null && (\n \n 居住地\n \n {\n prefectures.find(\n (prefecture) =>\n prefecture.id.toString() ===\n values.prefecture_id.toString(),\n )!.name\n }\n \n \n )}\n {temporaryUserInput.industry_id == null && (\n \n 業種\n \n {\n industries.find(\n (industry) =>\n industry.id.toString() ===\n values.industry_id.toString(),\n )!.name\n }\n \n \n )}\n {temporaryUserInput.user_position_id == null && (\n \n ポジション\n \n {\n userPositions.find(\n (UserPosition) =>\n UserPosition.id.toString() ===\n values.user_position_id.toString(),\n )!.name\n }\n \n \n )}\n \n >\n )}\n\n ご本人情報\n \n \n \n 氏名\n \n \n 姓\n {values.family_name}\n \n \n 名\n {values.given_name}\n \n \n \n {!flipper.remove_name_kana_feature && (\n \n 氏名ふりがな\n \n \n 姓\n {values.family_name_kana}\n \n \n 名\n {values.given_name_kana}\n \n \n \n )}\n \n \n 建築士免許証明書 情報\n \n \n 取得している建築士資格\n \n {\n architectLicenses.find(\n (it) => String(it.id) === values.architect_license_id,\n )?.name\n }\n \n \n \n 建築士番号\n {values.architect_number}\n \n \n 建築士免許証明書 画像\n \n \n \n \n ご登録氏名について\n \n ご登録の氏名と建築士免許証明書に記載されている名前は同一ですか?\n \n \n \n {\n [\n { value: \"yes\", label: \"はい\" },\n { value: \"no\", label: \"いいえ\" },\n ].find((it) => it.value === values.is_same_name)?.label\n }\n \n \n {values.is_same_name === \"no\" && (\n \n \n ご登録の氏名と建築士証のお名前が異なる理由\n \n {values.different_name_reason}\n \n )}\n \n \n \n \n \n \n \n \n \n >\n );\n};\n\nexport default Confirmation;\n","import React from \"react\";\nimport HelpMessage from \"../../../shared/components/atoms/HelpMessage\";\nimport {\n Box,\n BoxProps,\n Modal,\n ModalBody,\n ModalCloseButton,\n ModalContent,\n ModalHeader,\n ModalOverlay,\n useDisclosure,\n Image,\n Flex,\n} from \"@chakra-ui/react\";\nimport InfoMessage from \"../../../shared/components/atoms/InfoMessage\";\nimport sample1 from \"./sample1.webp\";\nimport sample2 from \"./sample2.webp\";\n\nconst SampleModalButton = ({ ...props }: BoxProps) => {\n const { isOpen, onOpen, onClose } = useDisclosure();\n\n return (\n \n \n 建築士免許証明書の画像例\n \n \n \n \n 建築士免許証明書 画像例\n \n \n \n 以下の画像はイメージです。建築士免許証明書に記載されている内容は、一級、二級、木造建築士によって異なります。\n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n};\n\nexport default SampleModalButton;\n","import {\n Box,\n Container,\n Flex,\n HStack,\n Heading,\n Image,\n Modal,\n ModalBody,\n ModalCloseButton,\n ModalContent,\n ModalFooter,\n ModalHeader,\n ModalOverlay,\n RadioGroup,\n Stack,\n Text,\n useDisclosure,\n} from \"@chakra-ui/react\";\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport \"cropperjs/dist/cropper.css\";\nimport React, { useCallback, useState } from \"react\";\nimport {\n Controller,\n FormProvider,\n useForm,\n useFormContext,\n} from \"react-hook-form\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport * as yup from \"yup\";\nimport {\n doneUserPersonalInfoApplicationPath,\n userPersonalInfoApplicationPath,\n validateUserPersonalInfoApplicationPath,\n} from \"../../../routes\";\nimport { Flash } from \"../../shared/lib/types\";\nimport { uploadFiles } from \"../../shared/lib/uploadFile\";\nimport { Button } from \"../shared/components/atoms\";\nimport ArchitectLicenseImageField from \"../shared/components/atoms/ArchitectLicenseImageField\";\nimport Background from \"../shared/components/atoms/Background\";\nimport BackgroundBuildingImage from \"../shared/components/atoms/BackgroundBuildingImage\";\nimport InfoMessage from \"../shared/components/atoms/InfoMessage\";\nimport SectionTitle from \"../shared/components/atoms/SectionTitle\";\nimport ShadowCard from \"../shared/components/atoms/ShadowCard\";\nimport SiteSeal from \"../shared/components/atoms/SiteSeal\";\nimport {\n FormLabel,\n Input,\n InputError,\n Radio,\n} from \"../shared/components/atoms/form\";\nimport Select from \"../shared/components/atoms/form/Select\";\nimport Textarea from \"../shared/components/atoms/form/Textarea\";\nimport AddIcon from \"../shared/components/icons/AddIcon\";\nimport Application from \"../shared/components/layouts/Application\";\nimport {\n Options,\n} from \"../shared/lib/types\";\nimport useAutokana from \"../shared/lib/useAutokana\";\nimport useFileState from \"../shared/lib/useFileState\";\nimport useRequest from \"../shared/lib/useRequest\";\nimport { LogoSVG } from \"../svg\";\nimport BirthdaySelect from \"../user/shared/components/BirthdaySelect\";\nimport Confirmation from \"./components/Confirmation\";\nimport SampleModalButton from \"../user/shared/components/SampleModalButton\";\nimport { UserUserPersonalInfoApplicationsTemporaryUserInput } from \"./lib/types\";\n\nconst schema = yup.object({\n family_name: yup.string().trim().required().label(\"姓\"),\n given_name: yup.string().trim().required().label(\"名\"),\n family_name_kana: yup\n .string()\n .required()\n .hiragana()\n .trim()\n .label(\"ふりがな姓\"),\n given_name_kana: yup\n .string()\n .required()\n .hiragana()\n .trim()\n .label(\"ふりがな名\"),\n architect_license_id: yup\n .string()\n .trim()\n .required()\n .label(\"取得している建築士資格\"),\n architect_number: yup\n .string()\n .trim()\n .required()\n .matches(/^\\d+$/, \"半角数字で入力してください\")\n .label(\"建築士番号\"),\n is_same_name: yup.string().trim().required().label(\"ご登録氏名について\"),\n different_name_reason: yup\n .string()\n .trim()\n .when(\"is_same_name\", {\n is: \"no\",\n then: (schema) => schema.required(),\n })\n .label(\"ご登録の氏名と建築士証のお名前が異なる理由\"),\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-expect-error\n nickname: yup.string().trim().required().nickname().label(\"ニックネーム\"),\n birthday: yup.string().trim().required().label(\"生年月日\"),\n prefecture_id: yup.string().trim().required().label(\"居住地\"),\n industry_id: yup.string().trim().required().label(\"業種\"),\n user_position_id: yup.string().trim().required().label(\"ポジション\"),\n architect_type_id: yup.string().trim().required().label(\"職種\"),\n});\n\ntype FormData = yup.InferType;\n\nconst UserPersonalInfoApplicationsNew = ({\n architectLicenses,\n temporaryUserInput,\n flash,\n prefectures,\n industries,\n userPositions,\n}: {\n architectLicenses: Options;\n temporaryUserInput: UserUserPersonalInfoApplicationsTemporaryUserInput;\n flash: Flash;\n prefectures: Options;\n industries: Options;\n userPositions: Options;\n}) => {\n const methods = useUserPersonalInfoApplicationForm({ temporaryUserInput });\n\n const {\n control,\n handleSubmit,\n watch,\n getValues,\n formState: { errors },\n setError,\n clearErrors,\n setValue,\n } = methods;\n\n const architectLicenseImageFile = useFileState({ required: true });\n const architectLicenseModalDisclosure = useDisclosure();\n const [showConfirmation, setShowConfirmation] = useState(false);\n\n const onConfirm = async () => {\n if ((await validate({ shouldFocus: true })) === false) {\n return;\n }\n\n setShowConfirmation(true);\n window.scrollTo(0, 0);\n };\n\n const request = useRequest();\n\n const onSubmit = async (data: FormData) => {\n const results = await uploadFiles({\n files: [architectLicenseImageFile.file!],\n });\n const res = await request(userPersonalInfoApplicationPath(), \"POST\", {\n user_personal_info_application: {\n ...data,\n architect_license_image: results[0].blob.signed_id,\n },\n });\n\n if (res.ok) {\n location.href = doneUserPersonalInfoApplicationPath();\n }\n };\n\n const requestValidate = useCallback(async () => {\n const res = await request(\n validateUserPersonalInfoApplicationPath(),\n \"POST\",\n getValues(),\n );\n return res.json();\n }, [getValues, request]);\n\n const validator = {\n validateArchitectNumber: useCallback(\n async ({ shouldFocus = false } = {}) => {\n let isSuccess = true;\n await requestValidate().then((json) => {\n if (json[\"architect_number\"] != null) {\n isSuccess = false;\n json[\"architect_number\"]?.forEach((message: string) => {\n setError(\n \"architect_number\",\n {\n type: \"custom\",\n message,\n },\n { shouldFocus },\n );\n });\n } else {\n clearErrors(\"architect_number\");\n }\n });\n return isSuccess;\n },\n [clearErrors, requestValidate, setError],\n ),\n validateFiles: useCallback(\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n ({ shouldFocus = false } = {}) => {\n let result = true;\n result = architectLicenseImageFile.validate() && result;\n return result;\n },\n [architectLicenseImageFile],\n ),\n };\n\n const validate = async ({ shouldFocus = false } = {}) => {\n let isSuccess = true;\n isSuccess = validator.validateFiles({ shouldFocus }) && isSuccess;\n isSuccess =\n (await validator.validateArchitectNumber({ shouldFocus })) && isSuccess;\n return isSuccess;\n };\n\n const debounceValidateArchitectNumber = useDebouncedCallback(\n validator.validateArchitectNumber,\n 500,\n );\n const familyNameAutokana = useAutokana(\"#family_name\", \"#family_name_kana\");\n const givenNameAutokana = useAutokana(\"#given_name\", \"#given_name_kana\");\n\n const inputMaxW = \"326px\";\n\n return (\n \n \n \n \n \n {showConfirmation && (\n setShowConfirmation(false)}\n architectLicenses={architectLicenses}\n architectLicenseImagePreview={\n architectLicenseImageFile.preview!\n }\n temporaryUserInput={temporaryUserInput}\n prefectures={prefectures}\n industries={industries}\n userPositions={userPositions}\n isSubmitting={methods.formState.isSubmitting}\n onSubmit={methods.handleSubmit(onSubmit)}\n />\n )}\n \n \n validate({ shouldFocus: true }),\n )}\n >\n \n \n \n \n \n 本人確認登録\n \n \n \n 本人確認でいただいた情報を元に実在する建築士かを審査します。\n
\n 審査完了後、A-Loopのすべてのサービスがご利用いただけます。\n \n \n {temporaryUserInput.nickname == null && (\n <>\n 公開情報\n \n (\n \n 使用できる文字は、ひらがな、カタカナ、漢字、数字、アルファベットです。{\" \"}\n
\n 15文字以内で登録してください。
\n Q&A投稿時に匿名での投稿を選択した場合に表示されます。\n >\n }\n />\n )}\n />\n \n >\n )}\n\n {[\n temporaryUserInput.birthday,\n temporaryUserInput.industry_id,\n temporaryUserInput.prefecture_id,\n temporaryUserInput.user_position_id,\n ].some((value) => value == null) && (\n <>\n 非公開情報\n \n {temporaryUserInput.birthday == null && (\n \n 生年月日\n (\n {\n onChange(date);\n }}\n error={error?.message}\n />\n )}\n />\n \n プロフィールには年齢のみ公開されます\n \n \n )}\n {temporaryUserInput.prefecture_id == null && (\n (\n \n )}\n />\n )}\n\n {temporaryUserInput.industry_id == null && (\n (\n \n )}\n />\n )}\n\n {temporaryUserInput.user_position_id == null && (\n (\n \n )}\n />\n )}\n \n >\n )}\n\n ご本人情報\n \n \n 氏名\n \n \n 姓\n (\n {\n setValue(\n \"family_name_kana\",\n familyNameAutokana?.getFurigana(),\n );\n }}\n id=\"family_name\"\n />\n )}\n />\n \n \n 名\n (\n {\n setValue(\n \"given_name_kana\",\n givenNameAutokana?.getFurigana(),\n );\n }}\n id=\"given_name\"\n />\n )}\n />\n \n \n \n \n 氏名ふりがな\n \n \n 姓\n (\n \n )}\n />\n \n \n 名\n (\n \n )}\n />\n \n \n \n \n 建築士免許証明書 情報\n \n \n 取得している建築士資格\n \n \n {\n architectLicenses.find(\n (architectLicense) =>\n architectLicense.id.toString() ===\n watch(\"architect_license_id\"),\n )?.name\n }\n \n \n 変更する\n \n {errors.architect_license_id?.message !== undefined && (\n \n {errors.architect_license_id?.message?.toString()}\n \n )}\n \n \n \n \n 本人確認書類について\n \n \n 以下の項目は、本人確認のために提出いただいております。{\" \"}\n
\n 本人確認にのみ使用し、建築士免許証明書の情報は公開されません。\n \n \n \n (\n {\n field.onChange(e);\n await debounceValidateArchitectNumber();\n }}\n />\n )}\n />\n \n \n 建築士免許証明書 画像\n \n \n \n \n \n アップロードする建築士免許証明書(表面)の写真は、内容がはっきりと読み取れる写真をアップロードしてください。裏面の写真は不要です。\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n 取得している建築士資格\n \n \n \n \n (\n \n {architectLicenses.map(({ id, name }) => (\n \n {name}\n \n ))}\n \n )}\n />\n \n \n \n }\n >\n 変更\n \n \n \n \n \n );\n};\n\nconst IsSameNameField = () => {\n const { control, watch, setValue } = useFormContext();\n\n const isSameName = watch(\"is_same_name\");\n\n return (\n <>\n ご登録氏名について\n \n ご登録の氏名と建築士免許証明書に記載されている名前は同一ですか?\n \n (\n <>\n {\n field.onChange(value);\n if (value === \"yes\") {\n setValue(\"different_name_reason\", \"\");\n }\n }}\n >\n {[\n { value: \"yes\", label: \"はい\" },\n { value: \"no\", label: \"いいえ\" },\n ].map(({ value, label }) => (\n \n {label}\n \n ))}\n \n {fieldState.error?.message !== undefined && (\n {fieldState.error?.message}\n )}\n >\n )}\n />\n {isSameName === \"no\" && (\n \n (\n \n )}\n />\n \n )}\n >\n );\n};\n\nconst useUserPersonalInfoApplicationForm = ({\n temporaryUserInput,\n}: {\n temporaryUserInput: UserUserPersonalInfoApplicationsTemporaryUserInput;\n}) => {\n return useForm({\n defaultValues: {\n family_name: \"\",\n given_name: \"\",\n family_name_kana: \"\",\n given_name_kana: \"\",\n architect_license_id: temporaryUserInput.architect_license_id.toString(),\n architect_number: \"\",\n is_same_name: \"\",\n different_name_reason: \"\",\n nickname: temporaryUserInput.nickname,\n birthday: temporaryUserInput.birthday,\n prefecture_id: temporaryUserInput.prefecture_id,\n industry_id: temporaryUserInput.industry_id,\n user_position_id: temporaryUserInput.user_position_id,\n architect_type_id: temporaryUserInput.architect_type_id,\n },\n resolver: yupResolver(schema),\n });\n};\n\nexport default UserPersonalInfoApplicationsNew;\n","import {\n Box,\n Container,\n Flex,\n HStack,\n Heading,\n Image,\n Modal,\n ModalBody,\n ModalCloseButton,\n ModalContent,\n ModalFooter,\n ModalHeader,\n ModalOverlay,\n RadioGroup,\n Stack,\n Text,\n useDisclosure,\n} from \"@chakra-ui/react\";\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport \"cropperjs/dist/cropper.css\";\nimport React, { useCallback, useState } from \"react\";\nimport {\n Controller,\n FormProvider,\n useForm,\n useFormContext,\n} from \"react-hook-form\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport * as yup from \"yup\";\nimport {\n doneUserPersonalInfoApplicationPath,\n userPersonalInfoApplicationPath,\n validateUserPersonalInfoApplicationPath,\n} from \"../../../routes\";\nimport { Flash } from \"../../shared/lib/types\";\nimport { uploadFiles } from \"../../shared/lib/uploadFile\";\nimport { Button } from \"../shared/components/atoms\";\nimport ArchitectLicenseImageField from \"../shared/components/atoms/ArchitectLicenseImageField\";\nimport Background from \"../shared/components/atoms/Background\";\nimport BackgroundBuildingImage from \"../shared/components/atoms/BackgroundBuildingImage\";\nimport InfoMessage from \"../shared/components/atoms/InfoMessage\";\nimport SectionTitle from \"../shared/components/atoms/SectionTitle\";\nimport ShadowCard from \"../shared/components/atoms/ShadowCard\";\nimport SiteSeal from \"../shared/components/atoms/SiteSeal\";\nimport {\n FormLabel,\n Input,\n InputError,\n Radio,\n} from \"../shared/components/atoms/form\";\nimport Select from \"../shared/components/atoms/form/Select\";\nimport Textarea from \"../shared/components/atoms/form/Textarea\";\nimport AddIcon from \"../shared/components/icons/AddIcon\";\nimport Application from \"../shared/components/layouts/Application\";\nimport {\n Options,\n} from \"../shared/lib/types\";\nimport useFileState from \"../shared/lib/useFileState\";\nimport useRequest from \"../shared/lib/useRequest\";\nimport { LogoSVG } from \"../svg\";\nimport BirthdaySelect from \"../user/shared/components/BirthdaySelect\";\nimport Confirmation from \"./components/Confirmation\";\nimport SampleModalButton from \"../user/shared/components/SampleModalButton\";\nimport { UserUserPersonalInfoApplicationsTemporaryUserInput } from \"./lib/types\";\n\nconst schema = yup.object({\n family_name: yup.string().trim().required().label(\"姓\"),\n given_name: yup.string().trim().required().label(\"名\"),\n architect_license_id: yup\n .string()\n .trim()\n .required()\n .label(\"取得している建築士資格\"),\n architect_number: yup\n .string()\n .trim()\n .required()\n .matches(/^\\d+$/, \"半角数字で入力してください\")\n .label(\"建築士番号\"),\n is_same_name: yup.string().trim().required().label(\"ご登録氏名について\"),\n different_name_reason: yup\n .string()\n .trim()\n .when(\"is_same_name\", {\n is: \"no\",\n then: (schema) => schema.required(),\n })\n .label(\"ご登録の氏名と建築士証のお名前が異なる理由\"),\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-expect-error\n nickname: yup.string().trim().required().nickname().label(\"ニックネーム\"),\n birthday: yup.string().trim().required().label(\"生年月日\"),\n prefecture_id: yup.string().trim().required().label(\"居住地\"),\n industry_id: yup.string().trim().required().label(\"業種\"),\n user_position_id: yup.string().trim().required().label(\"ポジション\"),\n architect_type_id: yup.string().trim().required().label(\"職種\"),\n});\n\ntype FormData = yup.InferType;\n\nconst UserPersonalInfoApplicationsNew = ({\n architectLicenses,\n temporaryUserInput,\n flash,\n prefectures,\n industries,\n userPositions,\n}: {\n architectLicenses: Options;\n temporaryUserInput: UserUserPersonalInfoApplicationsTemporaryUserInput;\n flash: Flash;\n prefectures: Options;\n industries: Options;\n userPositions: Options;\n}) => {\n const methods = useUserPersonalInfoApplicationForm({ temporaryUserInput });\n\n const {\n control,\n handleSubmit,\n watch,\n getValues,\n formState: { errors },\n setError,\n clearErrors,\n } = methods;\n\n const architectLicenseImageFile = useFileState({ required: true });\n const architectLicenseModalDisclosure = useDisclosure();\n const [showConfirmation, setShowConfirmation] = useState(false);\n\n const onConfirm = async () => {\n if ((await validate({ shouldFocus: true })) === false) {\n return;\n }\n\n setShowConfirmation(true);\n window.scrollTo(0, 0);\n };\n\n const request = useRequest();\n\n const onSubmit = async (data: FormData) => {\n const results = await uploadFiles({\n files: [architectLicenseImageFile.file!],\n });\n const res = await request(userPersonalInfoApplicationPath(), \"POST\", {\n user_personal_info_application: {\n ...data,\n architect_license_image: results[0].blob.signed_id,\n },\n });\n\n if (res.ok) {\n location.href = doneUserPersonalInfoApplicationPath();\n }\n };\n\n const requestValidate = useCallback(async () => {\n const res = await request(\n validateUserPersonalInfoApplicationPath(),\n \"POST\",\n getValues(),\n );\n return res.json();\n }, [getValues, request]);\n\n const validator = {\n validateArchitectNumber: useCallback(\n async ({ shouldFocus = false } = {}) => {\n let isSuccess = true;\n await requestValidate().then((json) => {\n if (json[\"architect_number\"] != null) {\n isSuccess = false;\n json[\"architect_number\"]?.forEach((message: string) => {\n setError(\n \"architect_number\",\n {\n type: \"custom\",\n message,\n },\n { shouldFocus },\n );\n });\n } else {\n clearErrors(\"architect_number\");\n }\n });\n return isSuccess;\n },\n [clearErrors, requestValidate, setError],\n ),\n validateFiles: useCallback(\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n ({ shouldFocus = false } = {}) => {\n let result = true;\n result = architectLicenseImageFile.validate() && result;\n return result;\n },\n [architectLicenseImageFile],\n ),\n };\n\n const validate = async ({ shouldFocus = false } = {}) => {\n let isSuccess = true;\n isSuccess = validator.validateFiles({ shouldFocus }) && isSuccess;\n isSuccess =\n (await validator.validateArchitectNumber({ shouldFocus })) && isSuccess;\n return isSuccess;\n };\n\n const debounceValidateArchitectNumber = useDebouncedCallback(\n validator.validateArchitectNumber,\n 500,\n );\n const inputMaxW = \"326px\";\n\n return (\n \n \n \n \n \n {showConfirmation && (\n setShowConfirmation(false)}\n architectLicenses={architectLicenses}\n architectLicenseImagePreview={\n architectLicenseImageFile.preview!\n }\n temporaryUserInput={temporaryUserInput}\n prefectures={prefectures}\n industries={industries}\n userPositions={userPositions}\n isSubmitting={methods.formState.isSubmitting}\n onSubmit={methods.handleSubmit(onSubmit)}\n />\n )}\n \n \n validate({ shouldFocus: true }),\n )}\n >\n \n \n \n \n \n 本人確認登録\n \n \n \n 本人確認でいただいた情報を元に実在する建築士かを審査します。\n
\n 審査完了後、A-Loopのすべてのサービスがご利用いただけます。\n \n \n {temporaryUserInput.nickname == null && (\n <>\n 公開情報\n \n (\n \n 使用できる文字は、ひらがな、カタカナ、漢字、数字、アルファベットです。{\" \"}\n
\n 15文字以内で登録してください。
\n Q&A投稿時に匿名での投稿を選択した場合に表示されます。\n >\n }\n />\n )}\n />\n \n >\n )}\n\n {[\n temporaryUserInput.birthday,\n temporaryUserInput.industry_id,\n temporaryUserInput.prefecture_id,\n temporaryUserInput.user_position_id,\n ].some((value) => value == null) && (\n <>\n 非公開情報\n \n {temporaryUserInput.birthday == null && (\n \n 生年月日\n (\n {\n onChange(date);\n }}\n error={error?.message}\n />\n )}\n />\n \n プロフィールには年齢のみ公開されます\n \n \n )}\n {temporaryUserInput.prefecture_id == null && (\n (\n \n )}\n />\n )}\n\n {temporaryUserInput.industry_id == null && (\n (\n \n )}\n />\n )}\n\n {temporaryUserInput.user_position_id == null && (\n (\n \n )}\n />\n )}\n \n >\n )}\n\n ご本人情報\n \n \n 氏名\n \n \n 姓\n (\n \n )}\n />\n \n \n 名\n (\n \n )}\n />\n \n \n \n \n 建築士免許証明書 情報\n \n \n 取得している建築士資格\n \n \n {\n architectLicenses.find(\n (architectLicense) =>\n architectLicense.id.toString() ===\n watch(\"architect_license_id\"),\n )?.name\n }\n \n \n 変更する\n \n {errors.architect_license_id?.message !== undefined && (\n \n {errors.architect_license_id?.message?.toString()}\n \n )}\n \n \n \n \n 本人確認書類について\n \n \n 以下の項目は、本人確認のために提出いただいております。{\" \"}\n
\n 本人確認にのみ使用し、建築士免許証明書の情報は公開されません。\n \n \n \n (\n {\n field.onChange(e);\n await debounceValidateArchitectNumber();\n }}\n />\n )}\n />\n \n \n 建築士免許証明書 画像\n \n \n \n \n \n アップロードする建築士免許証明書(表面)の写真は、内容がはっきりと読み取れる写真をアップロードしてください。裏面の写真は不要です。\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n 取得している建築士資格\n \n \n \n \n (\n \n {architectLicenses.map(({ id, name }) => (\n \n {name}\n \n ))}\n \n )}\n />\n \n \n \n }\n >\n 変更\n \n \n \n \n \n );\n};\n\nconst IsSameNameField = () => {\n const { control, watch, setValue } = useFormContext();\n\n const isSameName = watch(\"is_same_name\");\n\n return (\n <>\n ご登録氏名について\n \n ご登録の氏名と建築士免許証明書に記載されている名前は同一ですか?\n \n (\n <>\n {\n field.onChange(value);\n if (value === \"yes\") {\n setValue(\"different_name_reason\", \"\");\n }\n }}\n >\n {[\n { value: \"yes\", label: \"はい\" },\n { value: \"no\", label: \"いいえ\" },\n ].map(({ value, label }) => (\n \n {label}\n \n ))}\n \n {fieldState.error?.message !== undefined && (\n {fieldState.error?.message}\n )}\n >\n )}\n />\n {isSameName === \"no\" && (\n \n (\n \n )}\n />\n \n )}\n >\n );\n};\n\nconst useUserPersonalInfoApplicationForm = ({\n temporaryUserInput,\n}: {\n temporaryUserInput: UserUserPersonalInfoApplicationsTemporaryUserInput;\n}) => {\n return useForm({\n defaultValues: {\n family_name: \"\",\n given_name: \"\",\n architect_license_id: temporaryUserInput.architect_license_id.toString(),\n architect_number: \"\",\n is_same_name: \"\",\n different_name_reason: \"\",\n nickname: temporaryUserInput.nickname,\n birthday: temporaryUserInput.birthday,\n prefecture_id: temporaryUserInput.prefecture_id,\n industry_id: temporaryUserInput.industry_id,\n user_position_id: temporaryUserInput.user_position_id,\n architect_type_id: temporaryUserInput.architect_type_id,\n },\n resolver: yupResolver(schema),\n });\n};\n\nexport default UserPersonalInfoApplicationsNew;\n","import React, { useContext } from \"react\";\nimport { Box, Container, Flex, Image, Text } from \"@chakra-ui/react\";\nimport ShadowCard from \"../shared/components/atoms/ShadowCard\";\nimport { Button } from \"../shared/components/atoms\";\nimport Application from \"../shared/components/layouts/Application\";\nimport {\n newBusinessMatchingOwnedBusinessEntityPath,\n rootPath,\n} from \"../../../routes\";\nimport BackgroundBuildingImage from \"../shared/components/atoms/BackgroundBuildingImage\";\nimport Background from \"../shared/components/atoms/Background\";\nimport { Flash } from \"../../shared/lib/types\";\nimport { LogoSVG } from \"../svg\";\nimport { FlipperContext } from \"../../shared/lib/FlipperContext\";\n\nconst UserPersonalInfoApplicationsDone = ({ flash }: { flash: Flash }) => {\n const flipper = useContext(FlipperContext);\n\n return (\n \n \n \n \n \n \n \n \n 本人確認の申請が完了しました\n \n \n A-Loopでは、会員登録でいただいた情報を元に実在する建築士かを審査します。\n
\n \n 審査結果は2営業日以内にメールでお送りいたします。\n \n
\n
\n 本人認証が完了した会員様にはメールで本人認証完了のご案内とともに\n
\n A-Loopのすべてのサービスをご利用いただけます。\n \n \n \n {flipper.business_matching_beta_feature && (\n \n )}\n \n \n \n \n \n \n );\n};\n\nexport default UserPersonalInfoApplicationsDone;\n","import { Box, Flex } from \"@chakra-ui/react\";\nimport React, { ReactNode } from \"react\";\n\nexport const ProfileItem = ({ children }: { children: ReactNode }) => {\n return (\n \n {children}\n \n );\n};\n\nexport const ProfileItemLeft = ({ children }: { children: ReactNode }) => {\n return {children};\n};\n\nexport const ProfileItemRight = ({ children }: { children: ReactNode }) => {\n return {children};\n};\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst ArrowRightIcon = (props: IconProps) => {\n return (\n \n \n \n \n \n );\n};\n\nexport default ArrowRightIcon;\n","import { Box, Flex, List, ListItem, Stack } from \"@chakra-ui/react\";\nimport React, { useEffect } from \"react\";\nimport ArrowRightIcon from \"../icons/ArrowRightIcon\";\n\nconst SideMenu = ({\n items,\n}: {\n items: {\n label: string;\n href?: string;\n onClick?: () => void;\n }[];\n}) => {\n const [pathnameHash, setPathnameHash] = React.useState(\"\");\n\n useEffect(() => {\n setPathnameHash(`${location.pathname}${location.hash}`);\n }, []);\n\n useEffect(() => {\n window.addEventListener(\"hashchange\", () => {\n setPathnameHash(`${location.pathname}${location.hash}`);\n });\n }, []);\n return (\n \n {items.map((item) => {\n const isActive = pathnameHash === item.href;\n const style = isActive\n ? {\n color: \"white\",\n bgColor: \"primary\",\n _hover: { bgColor: \"primary\" },\n }\n : { color: \"primary\" };\n const props = item.href\n ? ({ as: \"a\", href: item.href } as const)\n : ({ as: \"button\", onClick: item.onClick } as const);\n return (\n \n \n \n \n {item.label}\n \n \n \n );\n })}\n \n );\n};\n\nexport default SideMenu;\n","import { Box, Container, Flex, Heading } from \"@chakra-ui/react\";\nimport React, { ReactNode } from \"react\";\nimport { userPath } from \"../../../../routes\";\nimport { Flash } from \"../../../shared/lib/types\";\nimport Background from \"../../shared/components/atoms/Background\";\nimport Footer from \"../../shared/components/atoms/Footer\";\nimport GoBackLink from \"../../shared/components/atoms/GoBackLink\";\nimport Header from \"../../shared/components/atoms/Header\";\nimport SideMenu from \"../../shared/components/atoms/SideMenu\";\nimport Application from \"../../shared/components/layouts/Application\";\nimport { SharedCurrentUser } from \"../../shared/lib/types\";\n\nconst ProfilePageLayout = ({\n children,\n current_user,\n flash = {},\n}: {\n children: ReactNode;\n current_user: NonNullable;\n flash: Flash;\n}) => {\n return (\n \n \n \n \n \n プロフィール編集\n \n \n \n \n \n \n \n プロフィールに戻る\n \n {children}\n \n \n \n \n \n \n );\n};\n\nexport default ProfilePageLayout;\n","import { Box, CheckboxGroup, Heading, Stack, Text } from \"@chakra-ui/react\";\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport \"cropperjs/dist/cropper.css\";\nimport React from \"react\";\nimport { Controller, useForm } from \"react-hook-form\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport * as yup from \"yup\";\nimport {\n editProfileBasicPath,\n profileBasicPath,\n userPath,\n validateProfileBasicPath,\n} from \"../../../../routes\";\nimport { Flash } from \"../../../shared/lib/types\";\nimport { uploadFile } from \"../../../shared/lib/uploadFile\";\nimport { Button } from \"../../shared/components/atoms\";\nimport {\n ImageFileDropzone,\n useImageFileDropzoneState,\n} from \"../../shared/components/atoms/ImageFileDropzone\";\nimport SectionTitle from \"../../shared/components/atoms/SectionTitle\";\nimport SiteSeal from \"../../shared/components/atoms/SiteSeal\";\nimport { FormLabel, Input } from \"../../shared/components/atoms/form\";\nimport Checkbox from \"../../shared/components/atoms/form/Checkbox\";\nimport Select from \"../../shared/components/atoms/form/Select\";\nimport Textarea from \"../../shared/components/atoms/form/Textarea\";\nimport {\n SharedCurrentUser,\n Options,\n} from \"../../shared/lib/types\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport BirthdaySelect from \"../../user/shared/components/BirthdaySelect\";\nimport {\n ProfileItem,\n ProfileItemLeft,\n ProfileItemRight,\n} from \"../components/ProfileItem\";\nimport ProfilePageLayout from \"../components/ProfilePageLayout\";\nimport { ProfileBasicsProfile } from \"./lib/types\";\n\nconst schema = yup.object({\n birthday: yup.string().trim().required().label(\"生年月日\"),\n prefecture_id: yup.string().trim().required().label(\"居住地\"),\n nickname: yup\n .string()\n .required()\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n .nickname()\n .label(\"ニックネーム\"),\n architect_type_id: yup.string().trim().required().label(\"職種\"),\n additional_license_ids: yup.array(yup.string().trim()),\n is_check_other_architect_license: yup.bool(),\n other_architect_license: yup.string().trim().max(500),\n bio: yup.string().trim(),\n industry_id: yup.string().trim().required().label(\"職種\"),\n user_position_id: yup.string().trim().required().label(\"ポジション\"),\n});\n\ntype FormData = yup.InferType;\n\nconst Page = ({\n profile,\n prefectures,\n architect_types,\n additional_licenses,\n industries,\n userPositions,\n currentUser,\n}: {\n profile: ProfileBasicsProfile;\n prefectures: Options;\n architect_types: Options;\n additional_licenses: Options;\n industries: Options;\n userPositions: Options;\n currentUser: NonNullable;\n}) => {\n const profileImageState = useImageFileDropzoneState({\n firstPreview: profile.profile_image_url,\n });\n const anonymousImageState = useImageFileDropzoneState({\n firstPreview: profile.anonymous_image_url,\n });\n\n const {\n control,\n handleSubmit,\n watch,\n setError,\n clearErrors,\n formState: { isDirty, isSubmitting },\n } = useForm({\n defaultValues: {\n ...profile,\n birthday: profile.birthday,\n architect_type_id: profile.architect_type_id.toString(),\n additional_license_ids: profile.additional_license_ids.map((id) =>\n id.toString(),\n ),\n is_check_other_architect_license: profile.other_architect_license !== \"\",\n },\n resolver: yupResolver(schema),\n });\n\n const request = useRequest();\n const validateNickname = async (\n data: yup.InferType,\n shouldFocus = false,\n ) => {\n const res = await request(validateProfileBasicPath(), \"POST\", data);\n const json = await res.json();\n if (json.nickname) {\n json.nickname.forEach((message: string) => {\n setError(\"nickname\", { message }, { shouldFocus });\n });\n } else {\n clearErrors(\"nickname\");\n }\n return json.nickname == null;\n };\n\n const debouncedValidateNickname = useDebouncedCallback(validateNickname, 500);\n\n const onSubmit = async (data: yup.InferType) => {\n const requestData: { user_profile: FormData } = {\n user_profile: {\n ...data,\n other_architect_license: data.is_check_other_architect_license\n ? data.other_architect_license\n : \"\",\n },\n };\n\n if (profileImageState.file) {\n const { blob } = await uploadFile({ file: profileImageState.file! });\n requestData.user_profile.profile_image = blob.signed_id;\n }\n\n if (anonymousImageState.file) {\n const { blob } = await uploadFile({ file: anonymousImageState.file! });\n requestData.user_profile.anonymous_image = blob.signed_id;\n } else if (anonymousImageState.preview === \"\") {\n requestData.user_profile.anonymous_image = \"\";\n }\n\n if (!(await validateNickname(requestData, true))) {\n return;\n }\n\n const res = await request(profileBasicPath(), \"PUT\", requestData);\n\n if (res.ok) {\n location.href = currentUser.is_approved\n ? userPath(currentUser.code, { tab: \"profile\" })\n : editProfileBasicPath();\n }\n };\n\n const isCheckOtherLicense = watch(\"is_check_other_architect_license\");\n\n return (\n \n \n \n 基本情報\n \n 公開情報\n \n \n \n \n ニックネーム\n \n \n \n (\n {\n field.onChange(e);\n await debouncedValidateNickname({\n user_profile: { nickname: e.target.value },\n });\n }}\n id=\"name\"\n error={fieldState.error?.message}\n message={\n <>\n 使用できる文字は、ひらがな、カタカナ、漢字、数字、アルファベットです。\n
\n 15文字以内で登録してください。
\n Q&A投稿時に、匿名での投稿を選択した場合に表示されます。\n >\n }\n />\n )}\n />\n \n \n \n \n プロフィール写真\n \n \n \n \n \n \n \n \n \n 匿名投稿用プロフィール写真\n \n \n \n \n \n Q&A投稿時に、匿名での投稿を選択した場合に表示されます。\n \n \n \n \n \n \n 職種\n \n \n (\n \n )}\n />\n \n \n \n \n その他に取得している資格\n \n \n (\n \n \n {additional_licenses.map(({ id, name }) => (\n \n {name}\n \n ))}\n \n (\n \n その他\n \n )}\n />\n {isCheckOtherLicense && (\n (\n \n )}\n />\n )}\n \n )}\n />\n \n \n \n \n 自己紹介\n \n \n }\n />\n \n あなたの経歴や実績、趣味・好きなこと、人物像などを入力していただくことを推奨します。\n
\n プロフィールを充実させることで、新たな建築士とのつながりや、仕事のお問い合わせに繋がる可能性があります。\n \n \n \n \n 非公開情報\n \n \n \n 生年月日\n \n プロフィールには年齢のみ公開されます。\n \n \n \n \n (\n \n )}\n />\n \n \n \n \n \n \n 居住地\n \n \n \n (\n \n )}\n />\n \n \n \n \n \n 業種\n \n \n \n (\n \n )}\n />\n \n \n \n \n \n ポジション\n \n \n \n (\n \n )}\n />\n \n \n \n \n \n \n \n \n );\n};\n\nconst ProfileBasicsEdit = ({\n profile,\n prefectures,\n architectTypes,\n additionalLicenses,\n industries,\n userPositions,\n flash,\n currentUser,\n}: {\n profile: ProfileBasicsProfile;\n prefectures: Options;\n architectTypes: Options;\n additionalLicenses: Options;\n industries: Options;\n userPositions: Options;\n flash: Flash;\n currentUser: NonNullable;\n}) => {\n return (\n \n \n \n \n \n \n );\n};\n\nexport default ProfileBasicsEdit;\n","import { Box, Heading, Stack } from \"@chakra-ui/react\";\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport \"cropperjs/dist/cropper.css\";\nimport React from \"react\";\nimport { Controller, useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport {\n editProfileOfficialUserPath,\n profileOfficialUserPath,\n} from \"../../../../routes\";\nimport { Flash } from \"../../../shared/lib/types\";\nimport { uploadFile } from \"../../../shared/lib/uploadFile\";\nimport { Button } from \"../../shared/components/atoms\";\nimport {\n ImageFileDropzone,\n useImageFileDropzoneState,\n} from \"../../shared/components/atoms/ImageFileDropzone\";\nimport SiteSeal from \"../../shared/components/atoms/SiteSeal\";\nimport { FormLabel, Input } from \"../../shared/components/atoms/form\";\nimport { SharedApprovedCurrentUser } from \"../../shared/lib/types\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport {\n ProfileItem,\n ProfileItemLeft,\n ProfileItemRight,\n} from \"../components/ProfileItem\";\nimport ProfilePageLayout from \"../components/ProfilePageLayout\";\n\nconst ProfileOfficialUsersEdit = ({\n flash,\n currentUser,\n}: {\n flash: Flash;\n currentUser: SharedApprovedCurrentUser;\n}) => {\n const profileImageState = useImageFileDropzoneState({\n firstPreview: currentUser.profile_image_url,\n required: true,\n });\n\n const schema = yup.object().shape({\n name: yup.string().trim().required().label(\"公式アカウント名\"),\n });\n\n const {\n handleSubmit,\n formState: { isDirty, isSubmitting },\n control,\n } = useForm({\n defaultValues: { name: currentUser.full_name },\n resolver: yupResolver(schema),\n });\n\n const request = useRequest();\n\n const onSubmit = async (data: yup.InferType) => {\n const formData = new FormData();\n\n formData.append(\"official_user[name]\", data.name);\n\n if (profileImageState.file) {\n const { blob } = await uploadFile({ file: profileImageState.file });\n formData.append(\"official_user[profile_image]\", blob.signed_id);\n }\n\n const res = await request(profileOfficialUserPath(), \"PUT\", formData);\n\n if (res.ok) {\n location.href = editProfileOfficialUserPath();\n }\n };\n\n return (\n \n \n \n \n 公式アカウント情報\n \n \n \n \n \n 公式アカウント名\n \n \n \n (\n \n )}\n />\n \n \n \n \n プロフィール写真\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n};\n\nexport default ProfileOfficialUsersEdit;\n","import { Box, Heading, Stack } from \"@chakra-ui/react\";\nimport \"cropperjs/dist/cropper.css\";\nimport React from \"react\";\nimport { Controller, useForm } from \"react-hook-form\";\nimport {\n editProfileGuestUserPath,\n profileGuestUserPath,\n} from \"../../../../routes\";\nimport { Button } from \"../../shared/components/atoms\";\nimport {\n ImageFileDropzone,\n useImageFileDropzoneState,\n} from \"../../shared/components/atoms/ImageFileDropzone\";\nimport { FormLabel, Input } from \"../../shared/components/atoms/form\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport {\n ProfileItem,\n ProfileItemLeft,\n ProfileItemRight,\n} from \"../components/ProfileItem\";\nimport ProfilePageLayout from \"../components/ProfilePageLayout\";\nimport * as yup from \"yup\";\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport { Flash } from \"../../../shared/lib/types\";\nimport { SharedApprovedCurrentUser } from \"../../shared/lib/types\";\nimport SiteSeal from \"../../shared/components/atoms/SiteSeal\";\nimport { uploadFile } from \"../../../shared/lib/uploadFile\";\n\nconst ProfileGuestUsersEdit = ({\n flash,\n currentUser,\n}: {\n flash: Flash;\n currentUser: SharedApprovedCurrentUser;\n}) => {\n const profileImageState = useImageFileDropzoneState({\n firstPreview: currentUser.profile_image_url,\n required: true,\n });\n\n const schema = yup.object().shape({\n name: yup.string().trim().required().label(\"ゲストユーザー名\"),\n });\n\n const {\n handleSubmit,\n formState: { isDirty, isSubmitting },\n control,\n } = useForm({\n defaultValues: { name: currentUser.full_name },\n resolver: yupResolver(schema),\n });\n\n const request = useRequest();\n\n const onSubmit = async (data: yup.InferType) => {\n const formData = new FormData();\n\n formData.append(\"guest_user[name]\", data.name);\n\n if (profileImageState.file) {\n const { blob } = await uploadFile({ file: profileImageState.file! });\n formData.append(\"guest_user[profile_image]\", blob.signed_id);\n }\n\n const res = await request(profileGuestUserPath(), \"PUT\", formData);\n\n if (res.ok) {\n location.href = editProfileGuestUserPath();\n }\n };\n\n return (\n \n \n \n \n ゲストユーザー情報\n \n \n \n \n \n ゲストユーザー名\n \n \n \n (\n \n )}\n />\n \n \n \n \n プロフィール写真\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n};\n\nexport default ProfileGuestUsersEdit;\n","const prefCode: Record = {\n 北海道: 1,\n 青森県: 2,\n 岩手県: 3,\n 宮城県: 4,\n 秋田県: 5,\n 山形県: 6,\n 福島県: 7,\n 茨城県: 8,\n 栃木県: 9,\n 群馬県: 10,\n 埼玉県: 11,\n 千葉県: 12,\n 東京都: 13,\n 神奈川県: 14,\n 新潟県: 15,\n 山梨県: 16,\n 長野県: 17,\n 富山県: 18,\n 石川県: 19,\n 福井県: 20,\n 岐阜県: 21,\n 静岡県: 22,\n 愛知県: 23,\n 三重県: 24,\n 滋賀県: 25,\n 京都府: 26,\n 大阪府: 27,\n 兵庫県: 28,\n 奈良県: 29,\n 和歌山県: 30,\n 鳥取県: 31,\n 島根県: 32,\n 岡山県: 33,\n 広島県: 34,\n 山口県: 35,\n 徳島県: 36,\n 香川県: 37,\n 愛媛県: 38,\n 高知県: 39,\n 福岡県: 40,\n 佐賀県: 41,\n 長崎県: 42,\n 熊本県: 43,\n 大分県: 44,\n 宮崎県: 45,\n 鹿児島県: 46,\n 沖縄県: 47,\n};\n\nexport default prefCode;\n","import { Box, Heading, Link, Stack, Text } from \"@chakra-ui/react\";\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport \"cropperjs/dist/cropper.css\";\nimport React from \"react\";\nimport { Controller, useForm } from \"react-hook-form\";\nimport YubinBango from \"yubinbango.js\";\nimport * as yup from \"yup\";\nimport {\n editProfileWorkplacePath,\n profileWorkplacePath,\n userPath,\n} from \"../../../../routes\";\nimport prefCode from \"../../../shared/lib/prefCode\";\nimport { Flash } from \"../../../shared/lib/types\";\nimport { Button } from \"../../shared/components/atoms\";\nimport InfoMessage from \"../../shared/components/atoms/InfoMessage\";\nimport SiteSeal from \"../../shared/components/atoms/SiteSeal\";\nimport { FormLabel, Input } from \"../../shared/components/atoms/form\";\nimport Select from \"../../shared/components/atoms/form/Select\";\nimport Textarea from \"../../shared/components/atoms/form/Textarea\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport {\n Options,\n SharedCurrentUser,\n} from \"../../shared/lib/types\";\nimport {\n ProfileItem,\n ProfileItemLeft,\n ProfileItemRight,\n} from \"../components/ProfileItem\";\nimport ProfilePageLayout from \"../components/ProfilePageLayout\";\nimport useAutokana from \"../../shared/lib/useAutokana\";\nimport { ProfileWorkplace } from \"./lib/types\";\n\nconst Page = ({\n workplace,\n prefectures,\n currentUser,\n}: {\n workplace: ProfileWorkplace;\n prefectures: Options;\n currentUser: NonNullable;\n}) => {\n const schema = yup.object({\n name: yup.string().trim(),\n // https://github.com/jquense/yup#extending-built-in-schema-with-new-methods\n // うまくいかないので苦肉の策\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n name_kana: yup.string().trim().hiragana(),\n postal_code: yup\n .string()\n .trim()\n .matches(/^\\d{7}$/, {\n message: \"ハイフン無しの7桁の数字を入力してください\",\n excludeEmptyString: true,\n }),\n prefecture_id: yup.string().trim(),\n address1: yup.string().trim(),\n address2: yup.string().trim(),\n address3: yup.string().trim(),\n phone_number: yup.string().trim().phoneNumber(),\n website_url: yup.string().trim().url(),\n business_detail: yup.string().trim(),\n });\n\n const nameAutoKana = useAutokana(\"#name\", \"#name_kana\");\n\n type FormData = yup.InferType;\n\n const {\n control,\n handleSubmit,\n setValue,\n formState: { isDirty, isSubmitting },\n } = useForm({\n defaultValues: {\n ...workplace,\n prefecture_id: (workplace.prefecture_id ?? \"\").toString(),\n },\n resolver: yupResolver(schema),\n });\n const request = useRequest();\n\n const onSubmit = async (data: yup.InferType) => {\n const res = await request(profileWorkplacePath(), \"PUT\", data);\n\n if (res.ok) {\n location.href = currentUser.is_approved\n ? userPath(currentUser.code, { tab: \"profile\" })\n : editProfileWorkplacePath();\n }\n };\n\n const fillAddressByPostalCode = async (postalCode: string) => {\n if (postalCode.length !== 7) return;\n\n const address = await YubinBango.getAddress(postalCode);\n setValue(\"prefecture_id\", prefCode[address.prefecture].toString());\n setValue(\"address1\", `${address.locality}${address.street}`);\n setValue(\"address2\", address.extended);\n };\n\n return (\n \n \n \n 勤務地情報\n \n \n ここで入力した情報はプロフィールで公開されます。\n \n \n \n \n 会社名\n \n \n (\n {\n setValue(\"name_kana\", nameAutoKana?.getFurigana());\n }}\n />\n )}\n />\n \n \n \n \n 会社名ふりがな\n \n \n (\n \n )}\n />\n \n \n \n \n 郵便番号\n \n \n (\n {\n void fillAddressByPostalCode(e[0].target.value);\n field.onChange(...e);\n }}\n maxW={{ md: \"50%\" }}\n id=\"postal_code\"\n error={error?.message}\n />\n )}\n />\n \n 例)1600022\n
\n ※ハイフンはいりません。\n
\n ※海外在住の方は「0000000」と入力してください。\n
\n ※郵便番号を入力すると住所が入力されます。\n \n \n 郵便番号がわからない方は\n \n こちら\n \n \n \n \n \n \n 都道府県\n \n \n (\n \n )}\n />\n \n \n \n \n 市区町村\n \n \n (\n \n )}\n />\n \n \n \n \n 番地\n \n \n }\n />\n \n \n \n \n ビル・マンション名\n \n \n (\n \n )}\n />\n \n \n \n \n 電話番号\n \n \n (\n \n )}\n />\n \n \n \n \n Webサイト\n \n \n (\n \n )}\n />\n \n \n \n \n 事業内容\n \n \n (\n \n )}\n />\n \n \n \n \n \n \n \n \n );\n};\n\nconst ProfileWorkplacesEdit = ({\n workplace,\n prefectures,\n flash,\n currentUser,\n}: {\n workplace: ProfileWorkplace;\n prefectures: Options;\n flash: Flash;\n currentUser: NonNullable;\n}) => {\n return (\n \n \n \n \n \n \n );\n};\n\nexport default ProfileWorkplacesEdit;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst VisibilityIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default VisibilityIcon;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst VisibilityOffIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default VisibilityOffIcon;\n","import React, { useState } from \"react\";\nimport { Input } from \"./form\";\nimport { InputProps } from \"./form/Input\";\nimport { forwardRef, InputRightElement } from \"@chakra-ui/react\";\nimport VisibilityIcon from \"../icons/VisibilityIcon\";\nimport VisibilityOffIcon from \"../icons/VisibilityOffIcon\";\n\nconst PasswordInput = forwardRef(({ ...props }, ref) => {\n const [show, setShow] = useState(false);\n return (\n (\n setShow(!show)}\n color=\"#99A9B0\"\n aria-label={show ? \"パスワードを非表示\" : \"パスワードを表示\"}\n >\n {show ? : }\n \n )}\n />\n );\n});\n\nexport default PasswordInput;\n","import {\n Box,\n Flex,\n Heading,\n Modal,\n ModalBody,\n ModalCloseButton,\n ModalContent,\n ModalOverlay,\n Stack,\n Text,\n useDisclosure,\n} from \"@chakra-ui/react\";\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport { Controller, useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport useFlash from \"../../shared/lib/useFlash\";\nimport { userDatabaseAuthenticationRegistrationPath } from \"../../../../routes\";\nimport React from \"react\";\nimport { FormLabel, Input } from \"../../shared/components/atoms/form\";\nimport Button from \"../../shared/components/atoms/Button\";\nimport PasswordInput from \"../../shared/components/atoms/PasswordInput\";\n\nconst EmailEdit = ({ currentEmail }: { currentEmail: string }) => {\n const schema = yup.object({\n email: yup.string().trim().email().required().label(\"メールアドレス\"),\n current_password: yup.string().required().label(\"パスワード\"),\n });\n\n type FormData = yup.InferType;\n\n const {\n control,\n formState: { isSubmitting },\n handleSubmit,\n reset,\n setError,\n trigger,\n watch,\n } = useForm({\n defaultValues: {\n email: \"\",\n current_password: \"\",\n },\n resolver: yupResolver(schema),\n });\n\n const { isOpen, onOpen, onClose } = useDisclosure();\n const request = useRequest();\n const showFlash = useFlash();\n\n const onSubmit = async (data: yup.InferType) => {\n const res = await request(\n userDatabaseAuthenticationRegistrationPath(),\n \"PUT\",\n {\n user_database_authentication: data,\n },\n );\n if (res.ok) {\n showFlash({ success: \"新しいメールアドレスに確認メールを送信しました\" });\n onClose();\n reset();\n } else {\n const json = await res.json();\n if (json.errors.email) {\n showFlash({ error: json.errors.email[0] });\n }\n if (json.errors.current_password) {\n setError(\"current_password\", {\n type: \"custom\",\n message: json.errors.current_password[0],\n });\n }\n }\n };\n\n const onClick = async () => {\n const isEmailValid = await trigger(\"email\");\n if (isEmailValid) {\n onOpen();\n }\n };\n\n return (\n <>\n \n \n メールアドレス\n \n \n \n \n 現在のメールアドレス\n \n {currentEmail}\n \n (\n \n )}\n />\n \n \n \n\n \n \n \n \n \n \n (\n \n )}\n />\n \n \n \n \n \n \n \n \n >\n );\n};\n\nexport default EmailEdit;\n","import React from \"react\";\nimport * as yup from \"yup\";\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport { Controller, useForm } from \"react-hook-form\";\nimport { Box, Heading, Stack } from \"@chakra-ui/react\";\nimport Button from \"../../shared/components/atoms/Button\";\nimport { userDatabaseAuthenticationRegistrationPath } from \"../../../../routes\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport useFlash from \"../../shared/lib/useFlash\";\nimport PasswordInput from \"../../shared/components/atoms/PasswordInput\";\n\nconst PasswordEdit = ({ ...boxProps }) => {\n const schema = yup.object({\n current_password: yup.string().required().label(\"現在のパスワード\"),\n password: yup\n .string()\n .required()\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n .password()\n .label(\"新しいパスワード\"),\n password_confirmation: yup\n .string()\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n .confirmation({\n key: \"password\",\n message: \"パスワードの入力が一致しません\",\n }),\n });\n\n type FormData = yup.InferType;\n\n const {\n control,\n formState: { isDirty, isSubmitting },\n handleSubmit,\n reset,\n setError,\n } = useForm({\n defaultValues: {\n current_password: \"\",\n password: \"\",\n password_confirmation: \"\",\n },\n resolver: yupResolver(schema),\n });\n\n const request = useRequest();\n const showFlash = useFlash();\n\n const onSubmit = async (data: yup.InferType) => {\n const res = await request(\n userDatabaseAuthenticationRegistrationPath(),\n \"PUT\",\n { user_database_authentication: data },\n );\n if (res.ok) {\n showFlash({ success: \"パスワードを変更しました\" });\n reset();\n } else {\n const json = await res.json();\n setError(\"current_password\", {\n type: \"custom\",\n message: json.errors.current_password[0],\n });\n }\n };\n\n return (\n \n \n パスワード\n \n \n (\n \n )}\n />\n (\n \n )}\n />\n (\n \n )}\n />\n \n \n \n );\n};\n\nexport default PasswordEdit;\n","import { Box, Flex, Link } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport { newProfileCancelPath } from \"../../../../routes\";\nimport { Flash } from \"../../../shared/lib/types\";\nimport SiteSeal from \"../../shared/components/atoms/SiteSeal\";\nimport { SharedCurrentUser } from \"../../shared/lib/types\";\nimport ProfilePageLayout from \"../components/ProfilePageLayout\";\nimport EmailEdit from \"./EmailEdit\";\nimport PasswordEdit from \"./PasswordEdit\";\n\nconst ProfileRegistrationsEdit = ({\n flash,\n currentUser,\n}: {\n flash: Flash;\n currentUser: NonNullable;\n}) => {\n return (\n \n \n \n \n \n {currentUser.is_withdrawable && (\n A-Loopから退会する\n )}\n \n \n \n \n \n \n );\n};\n\nexport default ProfileRegistrationsEdit;\n","import {\n Box,\n CheckboxGroup,\n Divider,\n Flex,\n HStack,\n Heading,\n RadioGroup,\n Stack,\n Text,\n} from \"@chakra-ui/react\";\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport React, { ReactNode } from \"react\";\nimport { Controller, useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport { profileEmailNotificationSettingPath } from \"../../../../routes\";\nimport { Flash, SharedTag } from \"../../../shared/lib/types\";\nimport { Button } from \"../../shared/components/atoms\";\nimport SiteSeal from \"../../shared/components/atoms/SiteSeal\";\nimport {\n FormLabel,\n InputError,\n Radio,\n} from \"../../shared/components/atoms/form\";\nimport Checkbox from \"../../shared/components/atoms/form/Checkbox\";\nimport { SharedCurrentUser } from \"../../shared/lib/types\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport ProfilePageLayout from \"../components/ProfilePageLayout\";\nimport { ProfileEmailNotificationSettingsUserEmailNotificationSetting } from \"./lib/types\";\n\nconst ProfileEmailNotificationSettingsEdit = ({\n flash,\n userEmailNotificationSetting,\n fromManagementOptions,\n postChildOptions,\n tags,\n currentUser,\n}: {\n flash: Flash;\n userEmailNotificationSetting: ProfileEmailNotificationSettingsUserEmailNotificationSetting;\n fromManagementOptions: { [s: string]: string };\n postChildOptions: { [s: string]: string };\n tags: SharedTag[];\n currentUser: NonNullable;\n}) => {\n const schema = yup.object().shape({\n from_management: yup\n .string()\n .required()\n .oneOf(Object.keys(fromManagementOptions)),\n post_comment: yup.string().required().oneOf(Object.keys(postChildOptions)),\n post_comment_reply: yup\n .string()\n .required()\n .oneOf(Object.keys(postChildOptions)),\n q_and_a_recommend_notification_tag_ids: yup\n .array(yup.string().required())\n .required()\n .label(\"Q&Aのメール通知を受信するタグ\"),\n });\n\n const {\n control,\n handleSubmit,\n trigger,\n formState: { isSubmitting, isValid },\n } = useForm({\n defaultValues: userEmailNotificationSetting,\n resolver: yupResolver(schema),\n });\n\n const request = useRequest();\n\n const onSubmit = async (data: yup.InferType) => {\n const res = await request(profileEmailNotificationSettingPath(), \"PUT\", {\n user_email_notification_setting: data,\n });\n\n if (res.ok) {\n location.reload();\n }\n };\n\n return (\n \n \n \n メール受信設定\n \n \n \n \n \n A-Loopからのお知らせ\n \n (\n \n \n {Object.keys(fromManagementOptions).map((key) => (\n \n {fromManagementOptions[key]}\n \n ))}\n \n \n )}\n />\n \n \n \n Q&Aの受信通知\n \n \n \n 投稿へ回答コメントが届いた場合のお知らせ\n \n (\n \n \n {Object.keys(postChildOptions).map((key) => (\n \n {postChildOptions[key]}\n \n ))}\n \n \n )}\n />\n \n \n \n 回答コメントへ返信が届いた場合のお知らせ\n \n (\n \n \n {Object.keys(postChildOptions).map((key) => (\n \n {postChildOptions[key]}\n \n ))}\n \n \n )}\n />\n \n \n \n \n \n \n Q&Aのメール通知\n \n (\n \n \n {\n field.onChange(e);\n await trigger(\n \"q_and_a_recommend_notification_tag_ids\",\n );\n }}\n >\n {tags.map(({ id, name }) => (\n \n {name}\n \n ))}\n \n \n {error && {error.message}}\n \n )}\n />\n \n \n \n \n \n \n \n \n \n \n \n \n );\n};\n\nconst FormLabelWithMemo = ({\n children,\n memo = \"\",\n}: {\n children: ReactNode;\n memo?: string;\n}) => {\n return (\n \n {children}\n {memo !== \"\" && (\n \n {memo}\n \n )}\n \n );\n};\n\nexport default ProfileEmailNotificationSettingsEdit;\n","import * as yup from \"yup\";\n\nexport const schema = yup.object().shape({\n current_password: yup.string().required().label(\"パスワード\"),\n cancel_detail_attributes: yup\n .object()\n .required()\n .shape({\n cancel_reason_ids: yup\n .array(yup.string().required())\n .required()\n .min(1, \"最低1つ選択してください\"),\n other_reason: yup.string().max(500).when([\"cancel_reason_ids\", \"$otherReasonId\"], {\n is: (cancel_reason_ids: string[], otherReasonId: string) => {\n return cancel_reason_ids.includes(otherReasonId.toString());\n },\n then: (schema) => schema.trim().required(\"必須です\"),\n otherwise: (schema) => schema,\n }).label(\"その他の理由\"),\n other_opnion: yup.string().ensure().max(1000).label(\"A-Loopへのご意見\"),\n }),\n});\n","import {\n Box,\n CheckboxGroup,\n Container,\n Flex,\n Heading,\n Link,\n Stack,\n Text,\n} from \"@chakra-ui/react\";\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport React from \"react\";\nimport { Controller, useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport {\n editProfileEmailNotificationSettingPath,\n editProfileRegistrationPath,\n rootPath,\n userDatabaseAuthenticationRegistrationPath,\n} from \"../../../../routes\";\nimport { Flash } from \"../../../shared/lib/types\";\nimport { Button, Header } from \"../../shared/components/atoms\";\nimport Background from \"../../shared/components/atoms/Background\";\nimport PasswordInput from \"../../shared/components/atoms/PasswordInput\";\nimport ShadowCard from \"../../shared/components/atoms/ShadowCard\";\nimport {\n FormLabel,\n Input,\n InputError,\n Textarea,\n} from \"../../shared/components/atoms/form\";\nimport Checkbox from \"../../shared/components/atoms/form/Checkbox\";\nimport Application from \"../../shared/components/layouts/Application\";\nimport {\n Options,\n SharedCurrentUser,\n} from \"../../shared/lib/types\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport { schema } from \"./lib/schema\";\n\ntype FormType = yup.InferType;\n\nconst ProfileCancelsNew = ({\n cancelReasons,\n flash,\n currentUser,\n}: {\n cancelReasons: Options;\n flash: Flash;\n currentUser: NonNullable;\n}) => {\n const otherReasonId = cancelReasons.find((it) => it.name == \"その他\")?.id;\n const {\n control,\n handleSubmit,\n setError,\n formState: { isSubmitting },\n } = useForm({\n defaultValues: {\n cancel_detail_attributes: {\n cancel_reason_ids: [] as string[],\n other_reason: \"\",\n other_opnion: \"\",\n },\n current_password: \"\",\n },\n resolver: yupResolver(schema),\n context: { otherReasonId },\n });\n\n const request = useRequest({ isIgnoreUnexpectedError: true });\n\n const onSubmit = async (data: FormType) => {\n const res = await request(\n userDatabaseAuthenticationRegistrationPath(),\n \"DELETE\",\n data,\n );\n\n if (res.ok) {\n location.href = rootPath();\n } else {\n const body = await res.json();\n if (body.errors.current_password != null) {\n setError(\"current_password\", { message: body.errors.current_password[0] }, { shouldFocus: true })\n }\n }\n };\n\n return (\n \n \n \n \n \n \n A-Loopを退会する\n \n \n \n \n 退会は即時反映され、\n \n \n 一度退会すると復活できません。\n \n \n \n \n よろしければ以下から\n \n \n 退会手続きを進めてください。\n \n \n \n \n (\n \n 退会理由\n \n \n {cancelReasons.map((cancelReason) => (\n \n \n {cancelReason.name}\n \n {cancelReason.name == \"メール通知が多かった\" &&\n value.includes(cancelReason.id.toString()) && (\n \n )}\n {cancelReason.id == otherReasonId &&\n value.includes(otherReasonId.toString()) && (\n (\n \n )}\n />\n )}\n \n ))}\n \n {error && {error.message}}\n \n \n )}\n />\n \n {/* Safariの自動入力機能でパスワード欄の上のフォームにユーザー名が入力されるのを防ぐためのダミーフォーム */}\n \n (\n \n )}\n />\n \n \n (\n \n )}\n />\n \n \n \n \n \n \n \n \n \n \n );\n};\n\nconst EmailSettingLink = () => (\n \n 「\n \n メール受信設定\n \n 」からメールの配信停止を行うことができます。\n \n);\n\nexport default ProfileCancelsNew;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst ErrorIcon = (props: IconProps) => (\n \n \n \n \n \n \n \n \n);\n\nexport default ErrorIcon;\n","import { Flex, FlexProps, Text } from \"@chakra-ui/react\";\nimport React, { ReactNode } from \"react\";\nimport ErrorIcon from \"../icons/ErrorIcon\";\n\nconst WarningMessage = ({\n children,\n ...props\n}: {\n children: ReactNode;\n} & FlexProps) => {\n return (\n \n \n {children}\n \n );\n};\n\nexport default WarningMessage;\n","import {\n Box,\n Flex,\n Heading,\n InputProps,\n RadioGroup,\n Stack,\n Text,\n} from \"@chakra-ui/react\";\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport \"cropperjs/dist/cropper.css\";\nimport React, { useCallback, useEffect } from \"react\";\nimport { Control, Controller, useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport {\n newProfilePersonalInfoApplicationPath,\n profilePersonalInfoApplicationPath,\n validateProfilePersonalInfoApplicationPath,\n} from \"../../../../routes\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport { Button } from \"../../shared/components/atoms\";\nimport {\n FormLabel,\n Input,\n InputError,\n Radio,\n} from \"../../shared/components/atoms/form\";\nimport Textarea from \"../../shared/components/atoms/form/Textarea\";\nimport {\n ProfileItem,\n ProfileItemLeft,\n ProfileItemRight,\n} from \"../components/ProfileItem\";\nimport ProfilePageLayout from \"../components/ProfilePageLayout\";\nimport {\n Options,\n SharedCurrentUser,\n} from \"../../shared/lib/types\";\nimport { Flash } from \"../../../shared/lib/types\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport SiteSeal from \"../../shared/components/atoms/SiteSeal\";\nimport useFileState from \"../../shared/lib/useFileState\";\nimport ArchitectLicenseImageField from \"../../shared/components/atoms/ArchitectLicenseImageField\";\nimport { uploadFile } from \"../../../shared/lib/uploadFile\";\nimport useAutokana from \"../../shared/lib/useAutokana\";\nimport SampleModalButton from \"../../user/shared/components/SampleModalButton\";\nimport WarningMessage from \"../../shared/components/atoms/WarningMessage\";\nimport { ProfilePersonalInfoApplicationsPersonalInfo } from \"./lib/types\";\n\nconst schema = yup.object({\n family_name: yup.string().trim().required().label(\"姓\"),\n given_name: yup.string().trim().required().label(\"名\"),\n family_name_kana: yup.string().trim().required().hiragana().label(\"姓\"),\n given_name_kana: yup.string().trim().required().hiragana().label(\"名\"),\n architect_license_id: yup.string().trim().required().label(\"建築士所有資格\"),\n architect_number: yup\n .string()\n .trim()\n .required()\n .matches(/^\\d+$/, \"半角数字で入力してください\")\n .label(\"建築士番号\"),\n is_same_name: yup.string().trim().required().label(\"ご登録氏名について\"),\n different_name_reason: yup\n .string()\n .trim()\n .ensure()\n .when(\"is_same_name\", {\n is: \"no\",\n then: (schema) => schema.required(),\n })\n .label(\"ご登録の氏名と建築士証のお名前が異なる理由\"),\n});\n\ntype FormData = yup.InferType;\n\nconst NameField = ({\n control,\n isFormDisabled,\n label,\n name,\n ...props\n}: {\n control: Control;\n isFormDisabled: boolean;\n label: string;\n name: \"family_name\" | \"family_name_kana\" | \"given_name\" | \"given_name_kana\";\n} & InputProps) => {\n return (\n \n {label}\n (\n \n )}\n />\n \n );\n};\n\nconst Page = ({\n architect_licenses,\n personal_info,\n active_personal_info_application,\n current_user,\n}: {\n architect_licenses: Options;\n personal_info: ProfilePersonalInfoApplicationsPersonalInfo;\n active_personal_info_application: ProfilePersonalInfoApplicationsPersonalInfo;\n current_user: NonNullable;\n}) => {\n const defaultValuesSource = active_personal_info_application ??\n personal_info ?? {\n family_name: \"\",\n given_name: \"\",\n family_name_kana: \"\",\n given_name_kana: \"\",\n architect_license_id: \"\",\n architect_number: \"\",\n is_same_name: \"\",\n different_name_reason: \"\",\n };\n\n const defaultValues = {\n ...defaultValuesSource,\n architect_license_id: defaultValuesSource.architect_license_id.toString(),\n is_same_name:\n defaultValuesSource.different_name_reason === \"\" ? \"yes\" : \"no\",\n };\n\n const {\n control,\n handleSubmit,\n watch,\n setValue,\n getValues,\n setError,\n formState: { isDirty, isSubmitting },\n } = useForm({\n defaultValues,\n resolver: yupResolver(schema),\n });\n\n const architectLicenseImageFile = useFileState({ required: true });\n\n const request = useRequest();\n\n const validate = useCallback(async () => {\n let result = true;\n const res = await request(\n validateProfilePersonalInfoApplicationPath(),\n \"POST\",\n {\n personal_info_application: getValues(),\n },\n );\n\n await res.json().then((json) => {\n if (json.architect_number != null) {\n setError(\"architect_number\", {\n type: \"custom\",\n message: json.architect_number,\n });\n result = false;\n }\n });\n if (!architectLicenseImageFile.validate()) {\n result = false;\n }\n return result;\n }, [architectLicenseImageFile, getValues, request, setError]);\n\n const onSubmit = async (data: FormData) => {\n if (!(await validate())) {\n return;\n }\n\n const { blob } = await uploadFile({\n file: architectLicenseImageFile.file!,\n });\n\n const res = await request(profilePersonalInfoApplicationPath(), \"POST\", {\n personal_info_application: {\n ...data,\n architect_license_image: blob.signed_id,\n },\n });\n if (res.ok) {\n location.href = newProfilePersonalInfoApplicationPath();\n }\n };\n\n const onCancel = async () => {\n const res = await request(profilePersonalInfoApplicationPath(), \"DELETE\");\n if (res.ok) {\n location.href = newProfilePersonalInfoApplicationPath();\n }\n };\n if (watch(\"is_same_name\") === \"yes\" && watch(\"different_name_reason\") !== \"\")\n setValue(\"different_name_reason\", \"\");\n\n const debounceValidate = useDebouncedCallback(validate, 500);\n\n const architectNumber = watch(\"architect_number\");\n\n useEffect(() => {\n void debounceValidate();\n }, [architectNumber, debounceValidate]);\n\n const isFormDisabled = active_personal_info_application !== null;\n\n const familyNameAutokana = useAutokana(\"#family_name\", \"#family_name_kana\");\n const givenNameAutokana = useAutokana(\"#given_name\", \"#given_name_kana\");\n\n return (\n \n \n \n 本人確認情報\n \n \n {isFormDisabled ? (\n <>\n 現在、審査中のため一時的に編集が不可になっています。\n
\n 内容を変更したい場合はページ下部の「申請を取り下げて変更」より変更手続きを行ってください。\n >\n ) : (\n <>\n 本人確認情報を更新した場合、弊社で内容を審査します。\n
\n 審査終了後、更新いただいた内容に反映されます。\n >\n )}\n \n \n \n \n 氏名\n \n \n \n \n {\n setValue(\n \"family_name_kana\",\n familyNameAutokana?.getFurigana() ?? \"\",\n );\n }}\n aria-label=\"姓(漢字)\"\n />\n \n \n {\n setValue(\n \"given_name_kana\",\n givenNameAutokana?.getFurigana() ?? \"\",\n );\n }}\n aria-label=\"名(漢字)\"\n />\n \n \n \n \n \n \n 氏名ふりがな\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n 建築士所有資格\n \n \n \n (\n <>\n \n {architect_licenses.map(({ id, name }) => (\n \n {name}\n \n ))}\n \n {error && {error.message}}\n >\n )}\n />\n \n \n \n \n \n 建築士番号\n \n \n \n (\n \n )}\n />\n \n \n {!current_user.personal_info_applicationing && (\n \n \n \n 建築士免許証明書 画像\n \n \n \n \n \n \n \n \n \n )}\n \n \n 登録氏名と建築士証名について\n \n ご登録の氏名と建築士免許証明書に記載されている名前は同一ですか?\n \n \n \n (\n <>\n \n {[\n { value: \"yes\", label: \"同一である\" },\n { value: \"no\", label: \"異なっている\" },\n ].map(({ value, label }) => (\n \n {label}\n \n ))}\n \n {fieldState.error?.message !== undefined && (\n {fieldState.error?.message}\n )}\n >\n )}\n />\n \n \n {watch(\"is_same_name\") === \"no\" && (\n \n \n 登録氏名と建築士証名が異なる理由\n \n \n (\n \n )}\n />\n \n \n )}\n \n \n \n {isFormDisabled ? (\n \n 上記内容に誤りがある場合は\n \n 申請を取り下げて変更\n \n \n ) : (\n \n )}\n \n \n );\n};\n\nconst PersonalInfoApplicationsNew = ({\n architectLicenses,\n personalInfo,\n activePersonalInfoApplication,\n flash,\n currentUser,\n}: {\n architectLicenses: Options;\n personalInfo: ProfilePersonalInfoApplicationsPersonalInfo;\n activePersonalInfoApplication: ProfilePersonalInfoApplicationsPersonalInfo;\n flash: Flash;\n currentUser: NonNullable;\n}) => {\n return (\n \n \n \n \n \n \n );\n};\n\nexport default PersonalInfoApplicationsNew;\n","import {\n Box,\n Flex,\n Heading,\n InputProps,\n RadioGroup,\n Stack,\n Text,\n} from \"@chakra-ui/react\";\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport \"cropperjs/dist/cropper.css\";\nimport React, { useCallback, useEffect } from \"react\";\nimport { Control, Controller, useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport {\n newProfilePersonalInfoApplicationPath,\n profilePersonalInfoApplicationPath,\n validateProfilePersonalInfoApplicationPath,\n} from \"../../../../routes\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport { Button } from \"../../shared/components/atoms\";\nimport {\n FormLabel,\n Input,\n InputError,\n Radio,\n} from \"../../shared/components/atoms/form\";\nimport Textarea from \"../../shared/components/atoms/form/Textarea\";\nimport {\n ProfileItem,\n ProfileItemLeft,\n ProfileItemRight,\n} from \"../components/ProfileItem\";\nimport ProfilePageLayout from \"../components/ProfilePageLayout\";\nimport { Options, SharedCurrentUser } from \"../../shared/lib/types\";\nimport { Flash } from \"../../../shared/lib/types\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport SiteSeal from \"../../shared/components/atoms/SiteSeal\";\nimport useFileState from \"../../shared/lib/useFileState\";\nimport ArchitectLicenseImageField from \"../../shared/components/atoms/ArchitectLicenseImageField\";\nimport { uploadFile } from \"../../../shared/lib/uploadFile\";\nimport SampleModalButton from \"../../user/shared/components/SampleModalButton\";\nimport WarningMessage from \"../../shared/components/atoms/WarningMessage\";\nimport { ProfilePersonalInfoApplicationsPersonalInfo } from \"./lib/types\";\n\nconst schema = yup.object({\n family_name: yup.string().trim().required().label(\"姓\"),\n given_name: yup.string().trim().required().label(\"名\"),\n architect_license_id: yup.string().trim().required().label(\"建築士所有資格\"),\n architect_number: yup\n .string()\n .trim()\n .required()\n .matches(/^\\d+$/, \"半角数字で入力してください\")\n .label(\"建築士番号\"),\n is_same_name: yup.string().trim().required().label(\"ご登録氏名について\"),\n different_name_reason: yup\n .string()\n .trim()\n .ensure()\n .when(\"is_same_name\", {\n is: \"no\",\n then: (schema) => schema.required(),\n })\n .label(\"ご登録の氏名と建築士証のお名前が異なる理由\"),\n});\n\ntype FormData = yup.InferType;\n\nconst NameField = ({\n control,\n isFormDisabled,\n label,\n name,\n ...props\n}: {\n control: Control;\n isFormDisabled: boolean;\n label: string;\n name: \"family_name\" | \"given_name\";\n} & InputProps) => {\n return (\n \n {label}\n (\n \n )}\n />\n \n );\n};\n\nconst Page = ({\n architect_licenses,\n personal_info,\n active_personal_info_application,\n current_user,\n}: {\n architect_licenses: Options;\n personal_info: ProfilePersonalInfoApplicationsPersonalInfo;\n active_personal_info_application: ProfilePersonalInfoApplicationsPersonalInfo;\n current_user: NonNullable;\n}) => {\n const defaultValuesSource = active_personal_info_application ??\n personal_info ?? {\n family_name: \"\",\n given_name: \"\",\n architect_license_id: \"\",\n architect_number: \"\",\n is_same_name: \"\",\n different_name_reason: \"\",\n };\n\n const defaultValues = {\n ...defaultValuesSource,\n architect_license_id: defaultValuesSource.architect_license_id.toString(),\n is_same_name:\n defaultValuesSource.different_name_reason === \"\" ? \"yes\" : \"no\",\n };\n\n const {\n control,\n handleSubmit,\n watch,\n setValue,\n getValues,\n setError,\n formState: { isDirty, isSubmitting },\n } = useForm({\n defaultValues,\n resolver: yupResolver(schema),\n });\n\n const architectLicenseImageFile = useFileState({ required: true });\n\n const request = useRequest();\n\n const validate = useCallback(async () => {\n let result = true;\n const res = await request(\n validateProfilePersonalInfoApplicationPath(),\n \"POST\",\n {\n personal_info_application: getValues(),\n },\n );\n\n await res.json().then((json) => {\n if (json.architect_number != null) {\n setError(\"architect_number\", {\n type: \"custom\",\n message: json.architect_number,\n });\n result = false;\n }\n });\n if (!architectLicenseImageFile.validate()) {\n result = false;\n }\n return result;\n }, [architectLicenseImageFile, getValues, request, setError]);\n\n const onSubmit = async (data: FormData) => {\n if (!(await validate())) {\n return;\n }\n\n const { blob } = await uploadFile({\n file: architectLicenseImageFile.file!,\n });\n\n const res = await request(profilePersonalInfoApplicationPath(), \"POST\", {\n personal_info_application: {\n ...data,\n architect_license_image: blob.signed_id,\n },\n });\n if (res.ok) {\n location.href = newProfilePersonalInfoApplicationPath();\n }\n };\n\n const onCancel = async () => {\n const res = await request(profilePersonalInfoApplicationPath(), \"DELETE\");\n if (res.ok) {\n location.href = newProfilePersonalInfoApplicationPath();\n }\n };\n if (watch(\"is_same_name\") === \"yes\" && watch(\"different_name_reason\") !== \"\")\n setValue(\"different_name_reason\", \"\");\n\n const debounceValidate = useDebouncedCallback(validate, 500);\n\n const architectNumber = watch(\"architect_number\");\n\n useEffect(() => {\n void debounceValidate();\n }, [architectNumber, debounceValidate]);\n\n const isFormDisabled = active_personal_info_application !== null;\n\n return (\n \n \n \n 本人確認情報\n \n \n {isFormDisabled ? (\n <>\n 現在、審査中のため一時的に編集が不可になっています。\n
\n 内容を変更したい場合はページ下部の「申請を取り下げて変更」より変更手続きを行ってください。\n >\n ) : (\n <>\n 本人確認情報を更新した場合、弊社で内容を審査します。\n
\n 審査終了後、更新いただいた内容に反映されます。\n >\n )}\n \n \n \n \n 氏名\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n 建築士所有資格\n \n \n \n (\n <>\n \n {architect_licenses.map(({ id, name }) => (\n \n {name}\n \n ))}\n \n {error && {error.message}}\n >\n )}\n />\n \n \n \n \n \n 建築士番号\n \n \n \n (\n \n )}\n />\n \n \n {!current_user.personal_info_applicationing && (\n \n \n \n 建築士免許証明書 画像\n \n \n \n \n \n \n \n \n \n )}\n \n \n 登録氏名と建築士証名について\n \n ご登録の氏名と建築士免許証明書に記載されている名前は同一ですか?\n \n \n \n (\n <>\n \n {[\n { value: \"yes\", label: \"同一である\" },\n { value: \"no\", label: \"異なっている\" },\n ].map(({ value, label }) => (\n \n {label}\n \n ))}\n \n {fieldState.error?.message !== undefined && (\n {fieldState.error?.message}\n )}\n >\n )}\n />\n \n \n {watch(\"is_same_name\") === \"no\" && (\n \n \n 登録氏名と建築士証名が異なる理由\n \n \n (\n \n )}\n />\n \n \n )}\n \n \n \n {isFormDisabled ? (\n \n 上記内容に誤りがある場合は\n \n 申請を取り下げて変更\n \n \n ) : (\n \n )}\n \n \n );\n};\n\nconst PersonalInfoApplicationsNew = ({\n architectLicenses,\n personalInfo,\n activePersonalInfoApplication,\n flash,\n currentUser,\n}: {\n architectLicenses: Options;\n personalInfo: ProfilePersonalInfoApplicationsPersonalInfo;\n activePersonalInfoApplication: ProfilePersonalInfoApplicationsPersonalInfo;\n flash: Flash;\n currentUser: NonNullable;\n}) => {\n return (\n \n \n \n \n \n \n );\n};\n\nexport default PersonalInfoApplicationsNew;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst WarningAmberIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default WarningAmberIcon;\n","import { Flex, Text } from \"@chakra-ui/react\";\nimport React, { ReactNode } from \"react\";\nimport WarningAmberIcon from \"../icons/WarningAmberIcon\";\n\ntype Props = {\n children: ReactNode;\n};\n\nconst ErrorMessage = ({ children }: Props) => {\n return (\n \n \n \n {children}\n \n \n );\n};\n\nexport default ErrorMessage;\n","import { Box, Container, Heading, Link, Stack, Text } from \"@chakra-ui/react\";\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport React, { useState } from \"react\";\nimport { Controller, useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport {\n newUserDatabaseAuthenticationPasswordPath,\n newUserDatabaseAuthenticationRegistrationPath,\n newUserSessionPath,\n privacyPolicyPath,\n termsOfServicePath,\n} from \"../../../../routes\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport { Button } from \"../../shared/components/atoms\";\nimport Background from \"../../shared/components/atoms/Background\";\nimport ErrorMessage from \"../../shared/components/atoms/ErrorMessage\";\nimport BackgroundBuildingImage from \"../../shared/components/atoms/BackgroundBuildingImage\";\nimport Header from \"../../shared/components/atoms/Header\";\nimport ShadowCard from \"../../shared/components/atoms/ShadowCard\";\nimport Checkbox from \"../../shared/components/atoms/form/Checkbox\";\nimport Input from \"../../shared/components/atoms/form/Input\";\nimport OpenInNewIcon from \"../../shared/components/icons/OpenInNewIcon\";\nimport Application from \"../../shared/components/layouts/Application\";\nimport { Flash } from \"../../../shared/lib/types\";\nimport PasswordInput from \"../../shared/components/atoms/PasswordInput\";\n\nconst AuthSessionsNew = ({ flash }: { flash: Flash }) => {\n const schema = yup.object({\n email: yup.string().trim().required().label(\"メールアドレス\"),\n password: yup.string().required().label(\"パスワード\"),\n remember_me: yup.bool().required(),\n });\n\n type FormData = yup.InferType;\n\n const [errorMessage, setErrorMessage] = useState(\"\");\n\n const {\n handleSubmit,\n formState: { errors, isSubmitting },\n control,\n } = useForm({\n defaultValues: {\n email: \"\",\n password: \"\",\n remember_me: false,\n },\n resolver: yupResolver(schema),\n });\n\n const request = useRequest();\n const onSubmit = async (data: FormData) => {\n const res = await request(newUserSessionPath(), \"POST\", {\n user_database_authentication: data,\n });\n if (res.ok) {\n location.href = res.headers.get(\"Location\")!;\n return;\n } else {\n const json = await res.json();\n setErrorMessage(json[\"error\"]);\n return;\n }\n };\n\n return (\n \n \n \n \n \n \n \n ログイン\n \n {errorMessage !== \"\" && (\n \n {errorMessage}\n \n )}\n \n (\n \n )}\n />\n (\n \n )}\n />\n (\n \n ログイン状態を維持する\n \n )}\n />\n \n \n ログインすることにより、\n \n 利用規約\n \n \n および\n \n 個人情報保護方針\n \n \n に同意したものとします。\n \n \n \n \n \n アカウントをお持ちでない方は\n \n 新規登録\n \n \n \n パスワードを忘れた方は\n \n こちら\n \n \n \n \n \n \n );\n};\n\nexport default AuthSessionsNew;\n","import * as yup from \"yup\";\n\nexport const schema = yup.object().shape({\n temporary_user_input: yup.object().shape({\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-expect-error\n nickname: yup.string().trim().required().nickname().label(\"ニックネーム\"),\n architect_license_id: yup.string().trim().required().label(\"建築士所有資格\"),\n architect_type_id: yup.string().trim().required().label(\"職種\"),\n birthday: yup.string().trim().required().label(\"生年月日\"),\n prefecture_id: yup.string().trim().required().label(\"居住地\"),\n industry_id: yup.string().trim().required().label(\"業種\"),\n user_position_id: yup.string().trim().required().label(\"ポジション\"),\n }),\n user_service_referral_source_attributes: yup.object().shape({\n service_referral_source_ids: yup\n .array(yup.string().required())\n .required()\n .min(1, \"最低1つ選択してください\")\n .label(\"A-Loopを知ったきっかけ\"),\n other_reason: yup.string().when([\"service_referral_source_ids\", \"$otherReasonId\"], {\n is: (service_referral_source_ids: string[], otherReasonId: string) =>\n service_referral_source_ids.includes(otherReasonId),\n then: (schema) => schema.trim().required(\"必須です\"),\n otherwise: (schema) => schema,\n }),\n referral_code: yup.string().when([\"service_referral_source_ids\", \"$fromSalesId\"], {\n is: (service_referral_source_ids: string[], fromSalesId: string) => {\n return service_referral_source_ids.includes(fromSalesId);\n },\n then: (schema) => schema.trim().required(\"必須です\"),\n otherwise: (schema) => schema,\n }),\n }),\n email: yup.string().trim().email().required().label(\"メールアドレス\"),\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-expect-error\n password: yup.string().required().password().label(\"パスワード\"),\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-expect-error\n password_confirmation: yup.string().confirmation({\n key: \"password\",\n message: \"パスワードの入力が一致しません\",\n }),\n agreement: yup.bool().is([true], \"「利用規約」および「個人情報保護方針」に同意してください\"),\n});\n","import {\n Box,\n CheckboxGroup,\n Container,\n Flex,\n Heading,\n Image,\n Link,\n RadioGroup,\n Stack,\n Text,\n} from \"@chakra-ui/react\";\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport \"cropperjs/dist/cropper.css\";\nimport React, { useEffect } from \"react\";\nimport { Controller, FormProvider, useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport {\n newUserSessionPath,\n privacyPolicyPath,\n termsOfServicePath,\n userDatabaseAuthenticationRegistrationPath,\n validateUserDatabaseAuthenticationRegistrationPath,\n} from \"../../../../routes\";\nimport { Flash } from \"../../../shared/lib/types\";\nimport { uploadFile } from \"../../../shared/lib/uploadFile\";\nimport { Button } from \"../../shared/components/atoms\";\nimport Background from \"../../shared/components/atoms/Background\";\nimport ErrorMessage from \"../../shared/components/atoms/ErrorMessage\";\nimport BackgroundBuildingImage from \"../../shared/components/atoms/BackgroundBuildingImage\";\nimport {\n ImageFileDropzone,\n useImageFileDropzoneState,\n} from \"../../shared/components/atoms/ImageFileDropzone\";\nimport SectionTitle from \"../../shared/components/atoms/SectionTitle\";\nimport ShadowCard from \"../../shared/components/atoms/ShadowCard\";\nimport SiteSeal from \"../../shared/components/atoms/SiteSeal\";\nimport {\n FormLabel,\n Input,\n InputError,\n Radio,\n} from \"../../shared/components/atoms/form\";\nimport Checkbox from \"../../shared/components/atoms/form/Checkbox\";\nimport Select from \"../../shared/components/atoms/form/Select\";\nimport Application from \"../../shared/components/layouts/Application\";\nimport {\n Options,\n} from \"../../shared/lib/types\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport { LogoSVG } from \"../../svg\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport BirthdaySelect from \"../../user/shared/components/BirthdaySelect\";\nimport { schema } from \"./lib/schema\";\nimport PasswordInput from \"../../shared/components/atoms/PasswordInput\";\n\ntype FormType = yup.InferType;\n\nconst inputMaxW = { base: \"none\", sm: 80 };\n\nconst AuthRegistrationsNew = ({\n architectLicenses,\n architectTypes,\n prefectures,\n industries,\n userPositions,\n serviceReferralSources,\n flash,\n}: {\n architectLicenses: Options;\n architectTypes: Options;\n prefectures: Options;\n industries: Options;\n userPositions: Options;\n serviceReferralSources: Options;\n flash: Flash;\n}) => {\n const otherReasonId = serviceReferralSources\n .find((it) => it.name === \"その他\")\n ?.id?.toString();\n const fromSalesId = serviceReferralSources\n .find((it) => it.name === \"営業からの紹介\")\n ?.id?.toString();\n\n const methods = useForm({\n defaultValues: {\n temporary_user_input: {\n nickname: \"\",\n architect_license_id: \"\",\n architect_type_id: \"\",\n birthday: \"\",\n prefecture_id: \"\",\n industry_id: \"\",\n user_position_id: \"\",\n },\n user_service_referral_source_attributes: {\n service_referral_source_ids: [],\n other_reason: \"\",\n referral_code: \"\",\n },\n email: \"\",\n password: \"\",\n password_confirmation: \"\",\n agreement: false,\n },\n resolver: yupResolver(schema),\n context: { otherReasonId, fromSalesId },\n });\n const { control, handleSubmit, setError, clearErrors } = methods;\n const anonymousImageFieldState = useImageFileDropzoneState();\n\n const request = useRequest();\n const validateNickname = async (data: FormType) => {\n const res = await request(\n validateUserDatabaseAuthenticationRegistrationPath(),\n \"POST\",\n data,\n );\n const json = await res.json();\n if (json[\"nickname\"] != null) {\n json[\"nickname\"].forEach((message: string) => {\n setError(\"temporary_user_input.nickname\", {\n type: \"custom\",\n message,\n });\n });\n } else {\n clearErrors(\"temporary_user_input.nickname\");\n }\n\n return json[\"nickname\"] == null;\n };\n\n const debouncedValidateNickname = useDebouncedCallback(validateNickname, 500);\n\n const validate = async (data: FormType) => {\n let isSuccess = true;\n isSuccess = anonymousImageFieldState.validate() && isSuccess;\n isSuccess =\n (await validateNickname({ user_database_authentication: data })) &&\n isSuccess;\n return isSuccess;\n };\n\n const onSubmit = async (data: FormType) => {\n if (!(await validate(data))) return;\n\n if (anonymousImageFieldState.file != null) {\n const { blob } = await uploadFile({\n file: anonymousImageFieldState.file,\n });\n data.temporary_user_input.anonymous_image = blob.signed_id;\n }\n const res = await request(\n userDatabaseAuthenticationRegistrationPath(),\n \"POST\",\n {\n user_database_authentication: data,\n },\n );\n\n if (res.ok) {\n location.href = res.headers.get(\"Location\")!;\n return;\n } else {\n const json = await res.json();\n setError(\"email\", { type: \"custom\", message: json.errors.email[0] });\n }\n };\n\n useEffect(() => {\n const searchParams = new URLSearchParams(location.search);\n const referral_code = searchParams.get(\"referral_code\");\n if (referral_code) {\n methods.setValue(\n \"user_service_referral_source_attributes.service_referral_source_ids\",\n [fromSalesId],\n );\n methods.setValue(\n \"user_service_referral_source_attributes.referral_code\",\n referral_code,\n );\n }\n }, [fromSalesId, methods]);\n\n return (\n \n \n \n \n \n \n \n anonymousImageFieldState.validate(),\n )}\n >\n \n \n \n \n \n A-Loopは、建築士の方が無料でご利用いただける
\n コミュニティサービスです。\n \n \n 会員登録(無料)\n \n 公開情報\n \n \n 取得している建築士資格\n {\n const { ref, ...rest } = field;\n return (\n <>\n \n \n {architectLicenses.map((architectLicense) => (\n \n {architectLicense.name}\n \n ))}\n \n \n {fieldState.error?.message !== undefined && (\n \n {fieldState.error.message}\n \n )}\n >\n );\n }}\n />\n \n A-Loopは建築士向けのサービスです。\n
\n 建築士資格をお持ちでない方は登録できません。\n \n \n (\n \n )}\n />\n (\n {\n field.onChange(e);\n await debouncedValidateNickname({\n user_database_authentication: {\n temporary_user_input: {\n nickname: e.target.value,\n },\n },\n });\n }}\n message={\n <>\n 使用できる文字は、ひらがな、カタカナ、漢字、数字、アルファベットです。{\" \"}\n
\n 15文字以内で登録してください。
\n Q&A投稿時に匿名での投稿を選択した場合に表示されます。\n >\n }\n />\n )}\n />\n \n 匿名投稿用プロフィール写真\n \n \n Q&Aに匿名で質問やコメントした際に表示されるプロフィール写真です。{\" \"}\n
\n 個人を特定されない建物写真や風景写真を推奨します。\n >\n }\n />\n \n \n \n 非公開情報\n \n (\n \n )}\n />\n (\n \n )}\n />\n (\n \n )}\n />\n \n 生年月日\n (\n {\n onChange(date);\n }}\n error={error?.message}\n />\n )}\n />\n \n プロフィールには年齢のみ公開されます。\n \n \n (\n \n )}\n />\n (\n \n )}\n />\n (\n \n )}\n />\n \n A-Loopを知ったきっかけ\n \n (\n \n \n \n {serviceReferralSources.map(\n ({ id, name }) => (\n \n \n {name}\n \n {id.toString() === otherReasonId &&\n field.value.includes(\n otherReasonId,\n ) && (\n (\n \n )}\n />\n )}\n {id.toString() === fromSalesId &&\n field.value.includes(fromSalesId) && (\n (\n \n 紹介コードをお持ちの場合は入力してください。\n
\n 紹介コードが分からない場合は営業担当の名前を入力してください。\n >\n }\n error={error?.message}\n />\n )}\n />\n )}\n \n ),\n )}\n \n \n {error && (\n {error.message}\n )}\n \n )}\n />\n \n \n \n \n \n \n (\n <>\n \n \n 「利用規約」および「個人情報保護方針」に同意する\n \n \n {fieldState.error !== undefined && (\n {fieldState.error.message}\n )}\n >\n )}\n />\n \n 「\n \n 利用規約\n \n 」「\n \n 個人情報保護方針\n \n 」をご確認いただき、チェックをお願いします。\n \n \n \n \n \n \n すでに会員の方は\n \n こちらからログイン\n \n \n \n \n \n \n \n \n \n \n \n );\n};\n\nexport default AuthRegistrationsNew;\n","import React, { useState } from \"react\";\nimport { Controller, useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport {\n newUserSessionPath,\n userDatabaseAuthenticationPasswordPath,\n} from \"../../../../routes\";\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport { Container, Heading, Link, Stack, Text, Box } from \"@chakra-ui/react\";\nimport ShadowCard from \"../../shared/components/atoms/ShadowCard\";\nimport Input from \"../../shared/components/atoms/form/Input\";\nimport { Button } from \"../../shared/components/atoms\";\nimport ErrorMessage from \"../../shared/components/atoms/ErrorMessage\";\nimport Application from \"../../shared/components/layouts/Application\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport BackgroundBuildingImage from \"../../shared/components/atoms/BackgroundBuildingImage\";\nimport Background from \"../../shared/components/atoms/Background\";\nimport Header from \"../../shared/components/atoms/Header\";\nimport { Flash } from \"../../../shared/lib/types\";\n\nconst AuthPasswordsNew = ({ flash }: { flash: Flash }) => {\n const schema = yup.object({\n email: yup.string().trim().required().label(\"メールアドレス\"),\n });\n\n type FormData = yup.InferType;\n\n const [errorMessage, setErrorMessage] = useState(\"\");\n\n const {\n handleSubmit,\n formState: { isSubmitting },\n control,\n } = useForm({\n defaultValues: {\n email: \"\",\n },\n resolver: yupResolver(schema),\n });\n\n const request = useRequest();\n const onSubmit = async (data: FormData) => {\n const res = await request(\n userDatabaseAuthenticationPasswordPath(),\n \"POST\",\n {\n user_database_authentication: data,\n },\n );\n if (res.ok) {\n location.href = res.headers.get(\"Location\")!;\n return;\n } else {\n const json = await res.json();\n setErrorMessage(json.errors.email[0]);\n return;\n }\n };\n\n return (\n \n \n \n \n \n \n \n パスワードを再設定\n \n {errorMessage !== \"\" && (\n \n {errorMessage}\n \n )}\n \n (\n \n )}\n />\n \n \n \n \n \n \n ログインページに戻る\n \n \n \n \n \n \n );\n};\n\nexport default AuthPasswordsNew;\n","import React from \"react\";\nimport { Controller, useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport {\n newUserDatabaseAuthenticationPasswordPath,\n userDatabaseAuthenticationPasswordPath,\n} from \"../../../../routes\";\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport { Container, Heading, Stack } from \"@chakra-ui/react\";\nimport ShadowCard from \"../../shared/components/atoms/ShadowCard\";\nimport { Button } from \"../../shared/components/atoms\";\nimport Application from \"../../shared/components/layouts/Application\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport BackgroundBuildingImage from \"../../shared/components/atoms/BackgroundBuildingImage\";\nimport Background from \"../../shared/components/atoms/Background\";\nimport Header from \"../../shared/components/atoms/Header\";\nimport { Flash } from \"../../../shared/lib/types\";\nimport PasswordInput from \"../../shared/components/atoms/PasswordInput\";\n\nconst AuthPasswordsEdit = ({\n resetPasswordToken,\n flash,\n}: {\n resetPasswordToken: string;\n flash: Flash;\n}) => {\n const schema = yup.object({\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-expect-error\n password: yup.string().required().password().label(\"パスワード\"),\n password_confirmation: yup\n .string()\n .oneOf(\n [yup.ref(\"password\"), undefined],\n \"パスワードの入力が一致しません\",\n ),\n });\n\n type FormData = yup.InferType;\n\n const {\n handleSubmit,\n formState: { isSubmitting },\n control,\n } = useForm({\n defaultValues: {\n password: \"\",\n password_confirmation: \"\",\n },\n resolver: yupResolver(schema),\n });\n\n const request = useRequest();\n const onSubmit = async (data: FormData) => {\n const res = await request(userDatabaseAuthenticationPasswordPath(), \"PUT\", {\n user_database_authentication: {\n ...data,\n reset_password_token: resetPasswordToken,\n },\n });\n\n if (res.ok) {\n location.href = res.headers.get(\"Location\")!;\n return;\n } else if (400 <= res.status && res.status <= 499) {\n location.href = newUserDatabaseAuthenticationPasswordPath();\n }\n };\n\n return (\n \n \n \n \n \n \n \n パスワード再設定\n \n \n (\n \n )}\n />\n (\n \n )}\n />\n \n \n \n \n \n \n \n \n );\n};\n\nexport default AuthPasswordsEdit;\n","import React, { useEffect, useState } from \"react\";\nimport * as yup from \"yup\";\nimport {\n Box,\n Checkbox,\n CheckboxGroup,\n Flex,\n FormLabel,\n InputRightElement,\n Stack,\n Text,\n} from \"@chakra-ui/react\";\nimport {\n Controller,\n FormProvider,\n useForm,\n useFormContext,\n} from \"react-hook-form\";\nimport { Input } from \"../../../shared/components/atoms/form\";\nimport { Button } from \"../../../shared/components/atoms\";\nimport SearchIcon from \"../../../shared/components/icons/SearchIcon\";\nimport {\n SharedOwnedBusinessEntity,\n Options,\n} from \"../../../shared/lib/types\";\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport { businessMatchingRootPath } from \"../../../../../routes\";\nimport ArrowDropUpIcon from \"../../../shared/components/icons/ArrowDropUpIcon\";\nimport ArrowDropDownIcon from \"../../../shared/components/icons/ArrowDropDownIcon\";\n\nconst DetailSearchForm = ({\n industries,\n prefectures,\n}: {\n industries: Options;\n prefectures: Options;\n}) => {\n const form = useFormContext();\n\n return (\n \n \n 主業種\n \n (\n \n {industries.map(({ id, name }) => (\n \n {name}\n \n ))}\n \n )}\n />\n \n \n \n 所在地\n \n (\n \n {prefectures.map(({ id, name }) => (\n \n {name}\n \n ))}\n \n )}\n />\n \n \n \n \n \n \n );\n};\n\nconst SearchForm = ({\n industries,\n prefectures,\n currentBusinessEntity,\n}: {\n industries: Options;\n prefectures: Options;\n currentBusinessEntity: SharedOwnedBusinessEntity | undefined;\n}) => {\n const schema = yup.object().shape({\n q: yup.object().shape({\n name_or_industry_name_or_prefecture_name_cont: yup.string().ensure(),\n industry_id_eq_any: yup.array(yup.string().ensure()).ensure(),\n prefecture_id_eq_any: yup.array(yup.string().ensure()).ensure(),\n }),\n });\n type FormData = yup.InferType;\n const form = useForm({\n defaultValues: {\n q: {\n name_or_industry_name_or_prefecture_name_cont: \"\",\n industry_id_eq_any: [],\n prefecture_id_eq_any: [],\n },\n },\n resolver: yupResolver(schema),\n });\n const { handleSubmit, control, setValue } = form;\n const onSubmit = (data: FormData) => {\n location.href = businessMatchingRootPath(data);\n };\n\n const [isDetailOpen, setIsDetailOpen] = useState(false);\n\n useEffect(() => {\n const query = new URLSearchParams(location.search);\n setValue(\n \"q.name_or_industry_name_or_prefecture_name_cont\",\n query.get(\"q[name_or_industry_name_or_prefecture_name_cont]\") ?? \"\",\n );\n setValue(\n \"q.industry_id_eq_any\",\n query.getAll(\"q[industry_id_eq_any][]\") ?? [],\n );\n setValue(\n \"q.prefecture_id_eq_any\",\n query.getAll(\"q[prefecture_id_eq_any][]\") ?? [],\n );\n }, [setValue]);\n\n return (\n \n \n {currentBusinessEntity?.is_active ? (\n <>\n (\n (\n \n \n \n \n \n )}\n {...field}\n />\n )}\n />\n : \n }\n iconSpacing={0.5}\n borderRadius={2}\n py={1}\n pl={1}\n pr={2}\n onClick={() => setIsDetailOpen((prev) => !prev)}\n >\n \n 詳細検索\n \n \n {isDetailOpen && (\n \n \n \n 詳細検索\n setIsDetailOpen(false)}\n >\n 閉じる\n \n \n \n \n \n )}\n >\n ) : (\n \n \n 検索\n \n \n \n )}\n \n \n );\n};\n\nexport default SearchForm;\n","import React from \"react\";\nimport { Flash, Pagy } from \"../../../shared/lib/types\";\nimport {\n Box,\n Container,\n Flex,\n Heading,\n HStack,\n Image,\n Stack,\n Text,\n} from \"@chakra-ui/react\";\nimport Application from \"../../shared/components/layouts/Application\";\nimport {\n SharedCurrentUser,\n Options,\n} from \"../../shared/lib/types\";\nimport PagyPagination from \"../../shared/components/atoms/PagyPagination\";\nimport {\n businessMatchingBusinessEntityPath,\n newBusinessMatchingOwnedBusinessEntityPath,\n} from \"../../../../routes\";\nimport { Button, Footer, Header } from \"../../shared/components/atoms\";\nimport Background from \"../../shared/components/atoms/Background\";\nimport HelpMessage from \"../../shared/components/atoms/HelpMessage\";\nimport SearchForm from \"./components/SearchForm\";\nimport { BusinessMatchingBusinessEntities } from \"./lib/types\";\n\nconst BusinessEntitiesIndex = ({\n flash,\n businessEntities,\n industries,\n prefectures,\n currentUser,\n pagy,\n}: {\n flash: Flash;\n businessEntities: BusinessMatchingBusinessEntities;\n industries: Options;\n prefectures: Options;\n currentUser: SharedCurrentUser | null;\n pagy: Pagy;\n}) => {\n const ownedBusinessEntities = currentUser?.owned_business_entities;\n return (\n \n \n \n {!currentUser?.is_business_matching_user && (\n \n \n \n 受発注機能とは?\n \n \n )}\n \n \n ビジネスパートナー一覧\n {ownedBusinessEntities?.current?.is_active && (\n \n 受発注機能とは?\n \n )}\n \n\n \n\n \n {businessEntities.map((businessEntity) => (\n \n \n \n \n \n \n {ownedBusinessEntities?.current?.is_active && (\n {businessEntity.name}\n )}\n {businessEntity.prefecture_name}\n 主業種:{businessEntity.industry_name}\n \n \n \n ))}\n \n \n \n \n \n \n \n \n );\n};\n\nexport default BusinessEntitiesIndex;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst FacebookIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default FacebookIcon;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst InstagramIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default InstagramIcon;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst NoteIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default NoteIcon;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst PinterestIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default PinterestIcon;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst ThreadsIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default ThreadsIcon;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst TikTokIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default TikTokIcon;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst XIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default XIcon;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst YoutubeIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default YoutubeIcon;\n","import React, { ReactNode } from \"react\";\nimport { Flex, forwardRef } from \"@chakra-ui/react\";\n\nconst InfoItem = forwardRef(({ children }: { children: ReactNode }, ref) => {\n return (\n \n {children}\n \n );\n});\n\nexport default InfoItem;\n","import React, { ReactNode } from \"react\";\nimport { Box } from \"@chakra-ui/react\";\n\nconst InfoItemLeft = ({ children }: { children: ReactNode }) => {\n return {children};\n};\n\nexport default InfoItemLeft;\n","import React, { ReactNode } from \"react\";\nimport { Box, BoxProps } from \"@chakra-ui/react\";\n\nconst InfoItemRight = ({\n children,\n ...props\n}: { children: ReactNode } & BoxProps) => {\n return (\n \n {children}\n \n );\n};\n\nexport default InfoItemRight;\n","import React, { ReactNode } from \"react\";\nimport { Flash } from \"../../../shared/lib/types\";\nimport Application from \"../../shared/components/layouts/Application\";\nimport GoBackLink from \"../../shared/components/atoms/GoBackLink\";\nimport {\n businessMatchingOwnedBusinessEntityTalkRoomsPath,\n businessMatchingRootPath,\n} from \"../../../../routes\";\nimport { SharedCurrentUser } from \"../../shared/lib/types\";\nimport {\n Box,\n Card,\n Container,\n Divider,\n Flex,\n Heading,\n HStack,\n Image,\n Link,\n Modal,\n ModalBody,\n ModalCloseButton,\n ModalContent,\n ModalOverlay,\n Stack,\n Text,\n useDisclosure,\n VStack,\n} from \"@chakra-ui/react\";\nimport FacebookIcon from \"../../shared/components/icons/FacebookIcon\";\nimport InstagramIcon from \"../../shared/components/icons/InstagramIcon\";\nimport NoteIcon from \"../../shared/components/icons/NoteIcon\";\nimport PinterestIcon from \"../../shared/components/icons/PinterestIcon\";\nimport ThreadsIcon from \"../../shared/components/icons/ThreadsIcon\";\nimport TikTokIcon from \"../../shared/components/icons/TikTokIcon\";\nimport XIcon from \"../../shared/components/icons/XIcon\";\nimport YoutubeIcon from \"../../shared/components/icons/YoutubeIcon\";\nimport { Button, Footer, Header } from \"../../shared/components/atoms\";\nimport Background from \"../../shared/components/atoms/Background\";\nimport SmsIcon from \"../../shared/components/icons/SmsIcon\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport useFlash from \"../../shared/lib/useFlash\";\nimport { useForm } from \"react-hook-form\";\nimport InfoItem from \"../shared/components/InfoItem\";\nimport InfoItemLeft from \"../shared/components/InfoItemLeft\";\nimport InfoItemRight from \"../shared/components/InfoItemRight\";\nimport { BusinessMatchingBusinessEntity } from \"./lib/types\";\n\nconst BusinessEntitiesShow = ({\n flash,\n businessEntity,\n currentUser,\n}: {\n flash: Flash;\n businessEntity: BusinessMatchingBusinessEntity;\n currentUser: SharedCurrentUser | null;\n}) => {\n const request = useRequest();\n const showFlash = useFlash();\n const {\n handleSubmit,\n formState: { isSubmitting },\n } = useForm();\n\n const snsLinkBoxSizing = { base: \"44px\", md: 8 };\n const hiddenTextForInactiveUser = \"受発注ご利用中のお客様のみ閲覧できます\";\n const TalkRoomRestrictionModal = useDisclosure();\n const ownedBusinessEntities = currentUser?.owned_business_entities;\n\n const ContentsForCurrentUser = ({ children }: { children: ReactNode }) => {\n return (\n <>\n {businessEntity.show_all_info ? (\n children\n ) : (\n {hiddenTextForInactiveUser}\n )}\n >\n );\n };\n\n const redirectToTalkRoom = async () => {\n const currentBusinessEntityCode = ownedBusinessEntities?.current.code;\n if (!currentBusinessEntityCode) {\n return;\n }\n\n const res = await request(\n businessMatchingOwnedBusinessEntityTalkRoomsPath(\n currentBusinessEntityCode,\n ),\n \"POST\",\n {\n partner_code: businessEntity.code,\n },\n );\n if (res.ok) {\n const json = await res.json();\n location.href = businessMatchingOwnedBusinessEntityTalkRoomsPath(\n currentBusinessEntityCode,\n { code: json.talk_room_code },\n );\n } else if (res.status === 404) {\n showFlash({\n error: (\n <>\n 事業者が見つかりませんでした。\n
\n ページをリロードしてください。\n >\n ),\n });\n }\n };\n\n return (\n \n \n \n \n \n \n 事業者一覧に戻る\n \n \n \n \n \n {businessEntity.show_all_info\n ? businessEntity.name_kana\n : hiddenTextForInactiveUser}\n \n \n {businessEntity.show_all_info\n ? businessEntity.name\n : hiddenTextForInactiveUser}\n \n \n \n {!businessEntity.is_owned && (\n \n \n \n )}\n \n \n \n \n 所在地\n \n \n \n \n 〒{businessEntity.postal_code}\n
\n {businessEntity.prefecture_name}\n {businessEntity.address1}\n {businessEntity.address2}\n {businessEntity.address3}\n \n \n \n \n \n \n \n 主業種\n \n \n {businessEntity.industry_name}\n \n \n \n \n \n 従業員数\n \n \n {businessEntity.number_of_employees}名\n \n \n \n \n \n 設立\n \n \n \n {businessEntity.establishment_year_month.split(\"/\")[0]}年\n {businessEntity.establishment_year_month.split(\"/\")[1]}月\n \n \n \n \n \n \n 資本金\n \n \n \n {businessEntity.capital_ten_thousand_yen === 0\n ? \"-\"\n : `${businessEntity.capital_ten_thousand_yen}円`}\n \n \n \n \n \n \n 代表者指名\n \n \n \n {businessEntity.president_name}\n \n \n \n \n \n \n Webサイト\n \n \n \n \n {businessEntity.website_url}\n \n \n \n \n \n \n \n SNS\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n {currentUser == null\n ? \"ご利用になるには会員登録が必要です\"\n : !ownedBusinessEntities?.current.is_approved\n ? \"受発注未利用の方は利用できません。\"\n : !ownedBusinessEntities?.current.is_active\n ? \"受発注一時停止中の方は利用できません。\"\n : \"\"}\n \n \n \n \n \n \n );\n};\n\nexport default BusinessEntitiesShow;\n","import React from \"react\";\nimport { Box, Container, Flex, Link, Stack, Text } from \"@chakra-ui/react\";\nimport { Flash } from \"../../../shared/lib/types\";\nimport Application from \"../../shared/components/layouts/Application\";\nimport BackgroundBuildingImage from \"../../shared/components/atoms/BackgroundBuildingImage\";\nimport Header from \"../../shared/components/atoms/Header\";\nimport Background from \"../../shared/components/atoms/Background\";\nimport ShadowCard from \"../../shared/components/atoms/ShadowCard\";\nimport { SharedApprovedCurrentUser } from \"../../shared/lib/types\";\nimport { rootPath } from \"../../../../routes\";\n\nexport const PreApplicationDone = ({\n flash,\n currentUser,\n}: {\n flash: Flash;\n currentUser: SharedApprovedCurrentUser;\n}) => {\n return (\n \n \n \n \n \n \n \n \n 受発注サービスの事前お申し込み\n
\n ありがとうございます。\n \n \n \n 受発注サービスは現在準備中です。\n \n \n \n 2025年6月10日(火)11:00\n \n \n よりご利用いただけます。\n \n \n \n \n ※ 申請内容の修正が必要な場合は、\n
\n 画面右上の事業者名をクリックすると、専用画面に遷移します。\n \n \n A-Loopトップに戻る\n \n \n \n \n \n \n );\n};\n\nexport default PreApplicationDone;\n","import { Box, Container, Flex } from \"@chakra-ui/react\";\nimport React, { ReactNode, useContext } from \"react\";\nimport { Header } from \"../../../shared/components/atoms\";\nimport Background from \"../../../shared/components/atoms/Background\";\nimport SideMenu from \"../../../shared/components/atoms/SideMenu\";\nimport Application from \"../../../shared/components/layouts/Application\";\nimport { SharedApprovedCurrentUser } from \"../../../shared/lib/types\";\nimport {\n editBusinessMatchingOwnedBusinessEntityActivationPath,\n editBusinessMatchingOwnedBusinessEntityPath,\n} from \"../../../../../routes\";\nimport { Flash } from \"../../../../shared/lib/types\";\nimport { FlipperContext } from \"../../../../shared/lib/FlipperContext\";\n\nconst OwnedBusinessEntityLayout = ({\n flash,\n currentUser,\n currentBusinessEntity,\n children,\n}: {\n flash: Flash;\n currentUser: SharedApprovedCurrentUser;\n currentBusinessEntity: { code: string; is_approved: boolean };\n children: ReactNode;\n}) => {\n const flipper = useContext(FlipperContext);\n const items = [\n {\n label: \"事業者情報\",\n href: editBusinessMatchingOwnedBusinessEntityPath(\n currentBusinessEntity.code,\n { anchor: \"info\" },\n ),\n },\n {\n label: \"事業者情報詳細\",\n href: editBusinessMatchingOwnedBusinessEntityPath(\n currentBusinessEntity.code,\n { anchor: \"detail\" },\n ),\n },\n ];\n\n if (\n currentBusinessEntity.is_approved &&\n flipper.business_matching_beta_feature\n ) {\n items.push({\n label: \"サービス利用設定\",\n href: editBusinessMatchingOwnedBusinessEntityActivationPath(\n currentBusinessEntity.code,\n ),\n });\n }\n\n return (\n <>\n \n \n \n \n \n \n \n \n {children}\n \n \n \n \n >\n );\n};\n\nexport default OwnedBusinessEntityLayout;\n","import {\n Box,\n Heading,\n Modal,\n ModalBody,\n ModalCloseButton,\n ModalContent,\n ModalFooter,\n ModalHeader,\n ModalOverlay,\n Text,\n useDisclosure,\n} from \"@chakra-ui/react\";\nimport React from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { businessMatchingOwnedBusinessEntityActivationPath } from \"../../../../routes\";\nimport useRequest from \"../../../admin/shared/lib/useRequest\";\nimport { Button } from \"../../shared/components/atoms\";\nimport InfoMessage from \"../../shared/components/atoms/InfoMessage\";\nimport SiteSeal from \"../../shared/components/atoms/SiteSeal\";\nimport WarningMessage from \"../../shared/components/atoms/WarningMessage\";\nimport {\n SharedApprovedCurrentUser,\n} from \"../../shared/lib/types\";\nimport useFlash from \"../../shared/lib/useFlash\";\nimport { Flash } from \"../../../shared/lib/types\";\nimport OwnedBusinessEntityLayout from \"../shared/components/OwnedBusinessEntityLayout\";\nimport { BusinessMatchingActiveBusinessEntitiesBusinessEntity } from \"./lib/types\";\n\nconst ActiveBusinessEntitiesEdit = ({\n businessEntity,\n flash,\n currentUser,\n}: {\n businessEntity: BusinessMatchingActiveBusinessEntitiesBusinessEntity;\n flash: Flash;\n currentUser: SharedApprovedCurrentUser;\n}) => {\n return (\n \n \n \n サービス利用設定\n \n \n \n 現在の利用設定\n \n \n {currentActiveStatus(businessEntity)}\n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n};\n\nconst currentActiveStatus = (\n businessEntity: BusinessMatchingActiveBusinessEntitiesBusinessEntity,\n) => (businessEntity.is_active ? \"掲載中\" : \"掲載一時停止中\");\n\nconst MessageForCurrentActiveStatus = ({\n businessEntity,\n}: {\n businessEntity: BusinessMatchingActiveBusinessEntitiesBusinessEntity;\n}) =>\n businessEntity.is_active ? (\n 受発注のすべての機能をご利用いただける状態です。\n ) : (\n \n 現在、ビジネスパートナー一覧に事業者情報は表示されていない状態です。\n
\n そのため新規パートナーとのやりとりを開始できません。\n
\n 新たなパートナーとやりとりしたい場合は、下部のボタンから掲載を再開してください。\n \n );\n\nconst ToggleActiveButton = ({\n businessEntity,\n}: {\n businessEntity: BusinessMatchingActiveBusinessEntitiesBusinessEntity;\n}) => {\n const { isOpen, onClose, onOpen } = useDisclosure();\n const request = useRequest();\n const showFlash = useFlash();\n\n const {\n handleSubmit,\n formState: { isSubmitting },\n } = useForm();\n\n const toggleActive = async (active: boolean) => {\n const res = await request(\n businessMatchingOwnedBusinessEntityActivationPath(businessEntity.code),\n \"PUT\",\n { active },\n );\n\n if (res.ok) {\n location.reload();\n } else {\n showFlash({ error: \"エラーが発生しました\" });\n }\n };\n\n return businessEntity.is_active ? (\n <>\n \n \n \n \n \n ビジネスパートナー一覧への\n \n 掲載を一時停止する\n \n \n \n \n 事業者情報ページを非表示にします。
\n 掲載一時停止中は、新規パートナーとのメッセージのやりとりが開始できなくなりますのでご注意ください。{\" \"}\n
\n ※既存のパートナーとは、引き続きやりとりが可能です。\n \n \n \n \n \n \n \n \n >\n ) : (\n \n );\n};\n\nexport default ActiveBusinessEntitiesEdit;\n","import React from \"react\";\nimport {\n Box,\n Flex,\n HStack,\n Popover,\n PopoverArrow,\n PopoverBody,\n PopoverContent,\n PopoverTrigger,\n Stack,\n Text,\n useBreakpointValue,\n} from \"@chakra-ui/react\";\nimport { Textarea } from \"../../../shared/components/atoms/form\";\nimport { Button } from \"../../../shared/components/atoms\";\nimport * as yup from \"yup\";\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport { Controller, useForm } from \"react-hook-form\";\nimport useRequest from \"../../../shared/lib/useRequest\";\nimport { businessMatchingTalkRoomMessagesPath } from \"../../../../../routes\";\nimport UploadFiles, {\n useUploadFiles,\n} from \"../../../../shared/components/UploadFiles\";\nimport FileUploadButton from \"../../../../shared/components/FileUploadButton\";\nimport AddIcon from \"../../../shared/components/icons/AddIcon\";\nimport { uploadFiles as directUploadFiles } from \"../../../../shared/lib/uploadFile\";\nimport SendIcon from \"../../../shared/components/icons/SendIcon\";\nimport HelpMessage from \"../../../shared/components/atoms/HelpMessage\";\n\nconst schema = yup.object({\n content: yup.string().trim().required().label(\"メッセージ\"),\n});\ntype FormData = yup.InferType;\n\nconst TalkMessageForm = ({ talkRoomCode }: { talkRoomCode: string }) => {\n const request = useRequest();\n const {\n handleSubmit,\n control,\n reset,\n formState: { isSubmitting },\n } = useForm({\n defaultValues: {\n content: \"\",\n },\n resolver: yupResolver(schema),\n });\n const uploadFilesState = useUploadFiles();\n const [uploadFiles, addUploadFiles, removeUploadFile, clearUploadFiles] =\n uploadFilesState;\n\n const onSubmit = async (data: FormData) => {\n const results = await directUploadFiles({ files: uploadFiles as File[] });\n const blobIds = results.map((result) => result.blob.signed_id);\n await request(businessMatchingTalkRoomMessagesPath(talkRoomCode), \"POST\", {\n talk_message: { ...data, files: blobIds },\n });\n clearUploadFiles();\n reset();\n };\n\n const leftIcon = useBreakpointValue({ base: undefined, sm: });\n return (\n \n \n (\n \n )}\n />\n \n \n {uploadFiles.length > 0 && (\n \n \n \n )}\n \n \n \n \n ファイル添付\n \n \n \n \n ファイル添付について\n \n \n \n \n \n ・添付できるファイル容量の上限は5MB未満です。\n
\n ・jpeg,jpg,png,gifの形式のファイルのみプレビュー表示され、それ以外はダウンロード形式になります。\n
\n ・最大で5つまでアップロード可能です。\n \n \n \n \n \n \n );\n};\n\nexport default TalkMessageForm;\n","import { InfiniteData } from \"@tanstack/react-query\";\nimport { useEffect, useRef, useState } from \"react\";\nimport {\n BusinessMatchingSharedTalkMessage,\n BusinessMatchingTalkMessagesIndex,\n BusinessMatchingTalkRoomsBusinessEntity,\n} from \"./types\";\n\nconst useControlScrollPosition = ({\n data,\n addedTalkMessages,\n currentBusinessEntity: ownedBusinessEntity,\n}: {\n data: InfiniteData | undefined;\n addedTalkMessages: BusinessMatchingSharedTalkMessage[];\n currentBusinessEntity: BusinessMatchingTalkRoomsBusinessEntity;\n}) => {\n const scrollContainerRef = useRef(null);\n const [isFirstScrollFinished, setIsFirstScrollFinished] = useState(false);\n const [previousScrollHeight, setPreviousScrollHeight] = useState(0);\n\n useEffect(() => {\n if (scrollContainerRef.current == null) return;\n if (addedTalkMessages.at(-1)?.sender.code !== ownedBusinessEntity.code)\n return;\n\n scrollContainerRef.current.scrollTop =\n scrollContainerRef.current.scrollHeight;\n }, [addedTalkMessages, ownedBusinessEntity]);\n\n useEffect(() => {\n if (scrollContainerRef.current === null || data == null) return;\n\n scrollContainerRef.current.scrollTop =\n scrollContainerRef.current.scrollHeight - previousScrollHeight;\n }, [data, previousScrollHeight]);\n\n useEffect(() => {\n if (data != null && scrollContainerRef.current && !isFirstScrollFinished) {\n scrollContainerRef.current.scrollTop =\n scrollContainerRef.current.scrollHeight;\n setIsFirstScrollFinished(true);\n }\n }, [data, isFirstScrollFinished]);\n\n return {\n scrollContainerRef,\n setPreviousScrollHeight,\n };\n};\n\nexport default useControlScrollPosition;\n","import {\n Box,\n CloseButton,\n Flex,\n Grid,\n HStack,\n Image,\n Modal,\n ModalContent,\n ModalOverlay,\n StackProps,\n useDisclosure,\n} from \"@chakra-ui/react\";\nimport React, { ReactNode, useEffect, useState } from \"react\";\nimport { Swiper, SwiperSlide } from \"swiper/react\";\nimport { Swiper as TSwiper } from \"swiper/types\";\nimport \"swiper/css\";\nimport ChevronLeftIcon from \"../icons/ChevronLeftIcon\";\nimport ChevronRightIcon from \"../icons/ChevronRightIcon\";\n\nconst SliderButton = ({\n onClick,\n children,\n}: {\n onClick: () => void;\n children: ReactNode;\n}) => {\n return (\n \n {children}\n \n );\n};\n\nconst ImagesAttachedWithPreview = ({\n urls,\n onRemove = undefined,\n ...props\n}: {\n urls: string[];\n onRemove?: (idx: number) => void;\n} & StackProps) => {\n const { isOpen: isOpenSlider, onOpen, onClose } = useDisclosure();\n const [initialIndex, setInitialIndex] = useState();\n\n const onOpenSlider = (index: number) => {\n setInitialIndex(index);\n onOpen();\n };\n\n const onCloseSlider = () => {\n onClose();\n setInitialIndex(undefined);\n };\n\n const [swiper, setSwiper] = useState();\n const [activeIndex, setActiveIndex] = useState(0);\n\n useEffect(() => {\n if (swiper != null && initialIndex != null) {\n swiper.slideTo(initialIndex, 0);\n }\n }, [initialIndex, swiper]);\n\n const PrevButton = () => {\n return (\n \n {activeIndex > 0 && (\n swiper?.slidePrev()}>\n \n \n )}\n \n );\n };\n\n const NextButton = () => {\n return (\n \n {activeIndex < urls.length - 1 && (\n swiper?.slideNext()}>\n \n \n )}\n \n );\n };\n\n if (urls.length == 0) return <>>;\n\n return (\n <>\n \n {urls.map((url, idx) => (\n \n {onRemove && (\n onRemove(idx)}\n />\n )}\n onOpenSlider(idx)}\n objectFit=\"cover\"\n loading=\"lazy\"\n aspectRatio={1 / 1}\n w={28}\n />\n \n ))}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n 閉じる\n \n \n \n setSwiper(s)}\n onSlideChange={(s) => setActiveIndex(s.activeIndex)}\n >\n {urls.map((url) => (\n \n \n \n \n \n ))}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n >\n );\n};\n\nexport default ImagesAttachedWithPreview;\n","import { Avatar, Box, Flex, Stack, Text } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport dayjs from \"dayjs\";\nimport FilesWithoutImage from \"../../../shared/components/atoms/FilesWithoutImage\";\nimport ImagesAttachedWithPreview from \"../../../shared/components/atoms/ImagesAttachedWithPreview\";\nimport { BusinessMatchingSharedTalkMessage, TalkRooms } from \"../lib/types\";\n\nfunction getPrValue(is_own: boolean) {\n if (!is_own) {\n return { base: 12, sm: \"7.5rem\" };\n } else {\n return { base: 0, sm: 0 };\n }\n}\n\nfunction getPlValue(is_own: boolean) {\n if (is_own) {\n return { base: 12, sm: \"7.5rem\" };\n } else {\n return { base: 0, sm: 0 };\n }\n}\n\nconst TalkMessageBubble = ({\n is_own,\n is_consecutive,\n partner,\n message,\n}: {\n is_own: boolean;\n is_consecutive: boolean;\n partner: TalkRooms[number][\"partner\"];\n message: BusinessMatchingSharedTalkMessage;\n}) => {\n const prValue = getPrValue(is_own);\n const plValue = getPlValue(is_own);\n\n return (\n \n {!is_own && !is_consecutive && (\n \n )}\n \n \n \n {!is_own && !is_consecutive && (\n \n {partner.name}\n \n )}\n \n {message.content}\n \n \n {dayjs(message.created_at).format(\"LLL\")}\n \n \n \n image_file.url)}\n justifyContent={is_own ? \"flex-end\" : \"flex-start\"}\n />\n \n \n \n \n \n );\n};\n\nexport default TalkMessageBubble;\n","import { Avatar, Box, HStack, Spinner, Stack, Text } from \"@chakra-ui/react\";\nimport { createConsumer } from \"@rails/actioncable\";\nimport { useInfiniteQuery, useQueryClient } from \"@tanstack/react-query\";\nimport React, { useEffect, useState } from \"react\";\nimport { InView } from \"react-intersection-observer\";\nimport { businessMatchingTalkRoomMessagesPath } from \"../../../../../routes\";\nimport {\n SharedOwnedBusinessEntity,\n} from \"../../../shared/lib/types\";\nimport useRequest from \"../../../shared/lib/useRequest\";\nimport TalkMessageForm from \"./TalkMessageForm\";\nimport useControlScrollPosition from \"../lib/useControlScrollPosition\";\nimport TalkMessageBubble from \"./TalkMessageBubble\";\nimport { BusinessMatchingSharedTalkMessage, BusinessMatchingTalkMessagesIndex, TalkRooms } from \"../lib/types\";\n\nconst TalkMessages = ({\n talkRoom,\n currentBusinessEntity,\n}: {\n talkRoom: TalkRooms[number];\n currentBusinessEntity: SharedOwnedBusinessEntity;\n}) => {\n const [addedTalkMessages, setAddedTalkMessages] = useState<\n BusinessMatchingSharedTalkMessage[]\n >([]);\n\n const request = useRequest();\n const queryClient = useQueryClient();\n\n const fetchTalkMessages =\n async (): Promise => {\n const params: { last_talk_message_code?: string } = {};\n\n const lastTalkMessage = data?.pages.at(-1)?.talk_messages?.at(-1);\n if (lastTalkMessage != null) {\n params.last_talk_message_code = lastTalkMessage.code;\n }\n\n const res = await request(\n businessMatchingTalkRoomMessagesPath(talkRoom.code),\n \"GET\",\n params,\n );\n return res.json();\n };\n\n const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isPending } =\n useInfiniteQuery({\n queryKey: [\"talk_rooms\", talkRoom.code],\n queryFn: fetchTalkMessages,\n initialPageParam: 1,\n getNextPageParam: (lastPage) => lastPage.page_metadata.next,\n });\n\n const onChangeInView = async (inView: boolean) => {\n if (scrollContainerRef.current === null) return;\n if (!inView || isFetchingNextPage) return;\n\n setPreviousScrollHeight(scrollContainerRef.current.scrollHeight);\n await fetchNextPage();\n };\n\n const { scrollContainerRef, setPreviousScrollHeight } =\n useControlScrollPosition({\n data,\n addedTalkMessages,\n currentBusinessEntity: currentBusinessEntity,\n });\n\n useEffect(() => {\n return () => {\n queryClient.removeQueries({ queryKey: [\"talk_rooms\", talkRoom.code] });\n };\n }, [queryClient, talkRoom.code]);\n\n useEffect(() => {\n const consumer = createConsumer();\n consumer.subscriptions.create(\n { channel: \"TalkRoomChannel\", talk_room_code: talkRoom.code },\n {\n received: (data) => {\n setAddedTalkMessages((prev) => [...prev, data]);\n },\n },\n );\n return () => {\n consumer.disconnect();\n };\n }, [talkRoom.code]);\n\n return (\n <>\n \n {isPending || data == null ? (\n \n \n \n ) : (\n <>\n onChangeInView(inView)}\n skip={!hasNextPage}\n >\n {isFetchingNextPage && (\n \n \n \n )}\n \n {addedTalkMessages.length === 0 &&\n data.pages[0].talk_messages.length === 0 ? (\n メッセージがありません\n ) : (\n <>\n {data.pages\n .map((page) => page.talk_messages)\n .flat()\n .reverse()\n .map((talkMessage, i, talkMessages) => (\n \n ))}\n {addedTalkMessages.map((talkMessage, i, talkMessages) => (\n \n ))}\n >\n )}\n >\n )}\n \n \n \n \n \n {currentBusinessEntity.name}\n \n \n \n \n >\n );\n};\n\nexport default TalkMessages;\n","import { Avatar, Box, HStack, Stack, Text } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport dayjs from \"dayjs\";\nimport { TalkRooms } from \"../lib/types\";\n\nconst TalkMessageListItem = ({\n message,\n last_sent_at,\n onClick,\n partner,\n isActive,\n}: {\n message: string;\n last_sent_at: string;\n onClick: () => void;\n isActive: boolean;\n partner: TalkRooms[number][\"partner\"]\n}) => {\n return (\n \n \n \n \n \n \n {partner.name}\n \n \n {message}\n \n \n {dayjs(last_sent_at).format(\"LLL\")}\n \n \n \n );\n};\n\nexport default TalkMessageListItem;\n","import React from \"react\";\nimport {\n Modal,\n ModalOverlay,\n ModalContent,\n ModalHeader,\n ModalBody,\n ModalCloseButton,\n HStack,\n Avatar,\n Text,\n} from \"@chakra-ui/react\";\nimport { SharedOwnedBusinessEntity } from \"../../../shared/lib/types\";\nimport TalkMessages from \"./TalkMessages\";\nimport { TalkRooms } from \"../lib/types\";\n\nconst TalkMessagesModal = ({\n talkRoom,\n currentBusinessEntity,\n isOpen,\n onClose,\n}: {\n talkRoom: TalkRooms[number];\n currentBusinessEntity: SharedOwnedBusinessEntity;\n isOpen: boolean;\n onClose: () => void;\n}) => {\n return (\n \n \n \n \n \n \n \n {talkRoom.partner.name}\n \n \n \n \n \n \n \n \n \n );\n};\n\nexport default TalkMessagesModal;\n","import React, { useCallback, useEffect, useState } from \"react\";\nimport { Flash } from \"../../../shared/lib/types\";\nimport Application from \"../../shared/components/layouts/Application\";\nimport {\n SharedApprovedCurrentUser,\n SharedOwnedBusinessEntity,\n} from \"../../shared/lib/types\";\nimport { Footer, Header } from \"../../shared/components/atoms\";\nimport TalkMessages from \"./components/TalkMessages\";\nimport {\n HStack,\n Stack,\n Text,\n Avatar,\n Box,\n useDisclosure,\n useBreakpointValue,\n Show,\n} from \"@chakra-ui/react\";\nimport TalkMessageListItem from \"./components/TalkMessageListItem\";\nimport { businessMatchingBusinessEntityPath } from \"../../../../routes\";\nimport TalkMessagesModal from \"./components/TalkMessagesModal\";\nimport { TalkRooms } from \"./lib/types\";\n\nconst TalkRoomsIndex = ({\n flash,\n talkRooms,\n currentBusinessEntity,\n currentUser,\n}: {\n flash: Flash;\n talkRooms: TalkRooms;\n currentBusinessEntity: SharedOwnedBusinessEntity;\n currentUser: SharedApprovedCurrentUser;\n}) => {\n const [selectedTalkRoom, setSelectedTalkRoom] = useState();\n\n const talkRoomByQueryCode = useCallback(() => {\n const code = new URLSearchParams(location.search).get(\"code\");\n return code\n ? talkRooms.find((talkRoom) => talkRoom.code === code)\n : undefined;\n }, [talkRooms]);\n\n useEffect(() => {\n const talkRoom = talkRoomByQueryCode() ?? talkRooms[0];\n setSelectedTalkRoom(talkRoom);\n }, [talkRooms, setSelectedTalkRoom, talkRoomByQueryCode]);\n\n const { onOpen, onClose, isOpen } = useDisclosure();\n\n const onClickTalkMessageListItem = useBreakpointValue({\n base: (talkRoom: TalkRooms[number]) => {\n setSelectedTalkRoom(talkRoom);\n onOpen();\n },\n lg: (talkRoom: TalkRooms[number]) => {\n setSelectedTalkRoom(talkRoom);\n },\n });\n\n return (\n \n \n \n \n \n {talkRooms.map((talkRoom) => (\n \n onClickTalkMessageListItem &&\n onClickTalkMessageListItem(talkRoom)\n }\n key={talkRoom.code}\n partner={talkRoom.partner}\n message={talkRoom.last_message.content}\n last_sent_at={talkRoom.last_message.created_at}\n isActive={selectedTalkRoom?.code === talkRoom.code}\n />\n ))}\n \n \n \n {selectedTalkRoom && (\n <>\n \n \n \n \n \n {selectedTalkRoom.partner.name}\n \n \n \n \n\n \n >\n )}\n \n \n {selectedTalkRoom && (\n \n )}\n \n \n \n \n );\n};\n\nexport default TalkRoomsIndex;\n","import React from \"react\";\nimport { Flash } from \"../../../shared/lib/types\";\nimport Application from \"../../shared/components/layouts/Application\";\nimport BackgroundBuildingImage from \"../../shared/components/atoms/BackgroundBuildingImage\";\nimport Background from \"../../shared/components/atoms/Background\";\nimport { Box, Container, Flex, Link, Text } from \"@chakra-ui/react\";\nimport ShadowCard from \"../../shared/components/atoms/ShadowCard\";\nimport { businessMatchingRootPath } from \"../../../../routes\";\nimport { SharedApprovedCurrentUser } from \"../../shared/lib/types\";\nimport { Header } from \"../../shared/components/atoms\";\nimport EmailNotReceivedMessage from \"../../../shared/components/EmailNotReceivedMessage\";\n\nconst VerificationSentsShow = ({\n flash,\n currentUser,\n}: {\n flash: Flash;\n currentUser: SharedApprovedCurrentUser;\n}) => {\n return (\n \n \n \n \n \n \n \n \n 確認メールを送信しました\n \n \n \n 現在ご利用いただいているメールアドレスとは異なるメールアドレスで申請を頂いたため、\n \n \n 改めてご登録いただいたメールアドレスに確認メールを送信しました。\n \n \n メール内のURLを60分以内にクリックして登録を行ってください。\n \n \n \n \n 現在ご利用いただいているメールアドレスとは\n 異なるメールアドレスで申請を頂いたため、\n \n \n 改めてご登録いただいたメールアドレスに\n 確認メールを送信しました。\n \n \n メール内のURLを60分以内に\n クリックして登録を行ってください。\n \n \n \n \n 事業者情報一覧へ\n \n \n \n \n \n \n );\n};\n\nexport default VerificationSentsShow;\n","import React from \"react\";\nimport { Flash } from \"../../../shared/lib/types\";\nimport Application from \"../../shared/components/layouts/Application\";\nimport BackgroundBuildingImage from \"../../shared/components/atoms/BackgroundBuildingImage\";\nimport Background from \"../../shared/components/atoms/Background\";\nimport { Box, Container, Flex, Link, Stack, Text } from \"@chakra-ui/react\";\nimport ShadowCard from \"../../shared/components/atoms/ShadowCard\";\nimport { businessMatchingRootPath, newContactPath } from \"../../../../routes\";\nimport { Button, Header } from \"../../shared/components/atoms\";\nimport { SharedApprovedCurrentUser } from \"../../shared/lib/types\";\n\nconst ApplicationDonesShow = ({\n flash,\n currentUser,\n}: {\n flash: Flash;\n currentUser: SharedApprovedCurrentUser;\n}) => {\n return (\n \n \n \n \n \n \n \n \n 受発注サービスの申し込みを承りました\n \n {/* TODO: 以下、6/10リリース後に差し替え */}\n \n \n 受発注サービスのお申し込みを承りました。\n 審査結果が出るまで、今しばらくお待ちください。\n \n \n \n 審査を通過された場合は、\n \n 6月10日(火)11時(予定)\n \n に事業者情報を掲載し、\n \n 受発注機能をご利用いただけます。 \n \n \n {/* TODO: ここまで */}\n {/* TODO: 以下、6/10リリース後に差し替え */}\n \n \n 受発注サービスの申し込みを承りました。\n \n \n 審査結果が出るまで、\n 今しばらくお待ちください。\n \n \n \n 審査を通過された場合は、\n \n 6月10日(火)11時(予定)\n \n に事業者情報を掲載し、\n \n 受発注機能をご利用いただけます。 \n \n \n {/* TODO: ここまで */}\n \n 事業者情報一覧へ\n \n \n \n \n 受発注サービスに関するお問い合わせ\n \n \n 以下のお問い合わせフォームからお気軽にご連絡ください。\n \n \n 以下のお問い合わせフォームから\n お気軽にご連絡ください。\n \n \n \n \n \n \n \n \n \n \n \n );\n};\n\nexport default ApplicationDonesShow;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst ArrowLeftAltIcon = (props: IconProps) => {\n return (\n \n \n \n \n \n \n \n \n );\n};\n\nexport default ArrowLeftAltIcon;\n","import React, { useState, useRef, useEffect, useCallback } from \"react\";\nimport { Box, Heading, Flex } from \"@chakra-ui/react\";\nimport ArrowDropUpIcon from \"../../../shared/components/icons/ArrowDropUpIcon\";\n\ntype AccordionProps = {\n title: string;\n children: React.ReactNode;\n initialOpen?: boolean;\n};\n\nconst Accordion = ({\n title,\n children,\n initialOpen = false,\n}: AccordionProps) => {\n const [isOpen, setIsOpen] = useState(initialOpen);\n const contentRef = useRef(null);\n const innerContentRef = useRef(null);\n const updateContentHeight = useCallback(() => {\n if (!isOpen) {\n contentRef.current!.style.height = `0px`;\n return;\n }\n\n if (innerContentRef.current && contentRef.current) {\n contentRef.current.style.height = `${innerContentRef.current.getBoundingClientRect().height}px`;\n }\n }, [isOpen]);\n\n useEffect(() => {\n updateContentHeight();\n }, [isOpen, updateContentHeight]);\n\n useEffect(() => {\n const handleResize = () => {\n if (isOpen) {\n updateContentHeight();\n }\n };\n\n window.addEventListener(\"resize\", handleResize);\n return () => {\n window.removeEventListener(\"resize\", handleResize);\n };\n }, [isOpen, updateContentHeight]);\n\n const toggleAccordion = () => {\n setIsOpen(!isOpen);\n };\n\n return (\n \n \n \n {title}\n \n \n \n \n \n \n \n {children}\n \n \n \n );\n};\n\nexport default Accordion;\n","import React from \"react\";\nimport { Text, Box } from \"@chakra-ui/react\";\n\ntype BusinessEntityChipProps = {\n children: string;\n};\n\nexport const BusinessEntityChip = ({\n children,\n}: BusinessEntityChipProps) => {\n return (\n \n \n {children}\n \n \n );\n};\n","import React from \"react\";\nimport { Stack, Heading, StackProps } from \"@chakra-ui/react\";\n\ntype BusinessEntityContentsCardProps = {\n title?: string;\n children: React.ReactNode;\n} & StackProps;\n\nconst BusinessEntityContentsCard = ({\n title,\n children,\n ...props\n}: BusinessEntityContentsCardProps) => {\n return (\n \n {title && (\n \n {title}\n \n )}\n {children}\n \n );\n};\n\nexport default BusinessEntityContentsCard;\n","import { Box, BoxProps } from \"@chakra-ui/react\";\nimport React from \"react\";\n\nconst BusinessEntityDottedLine = (props?: BoxProps) => {\n return (\n \n );\n};\n\nexport default BusinessEntityDottedLine;\n","import { Box, HStack, Stack, Text } from \"@chakra-ui/react\";\nimport React, { ReactNode, useEffect, useRef, useState } from \"react\";\nimport ArrowDropDownIcon from \"../../../shared/components/icons/ArrowDropDownIcon\";\nimport ArrowDropUpIcon from \"../../../shared/components/icons/ArrowDropUpIcon\";\n\nconst ExpandableText = ({\n children,\n showExpandButton = true,\n foldingMaxHeight,\n showExpandButtonLabel,\n}: {\n children: ReactNode;\n showExpandButton?: boolean;\n foldingMaxHeight: number;\n showExpandButtonLabel: string;\n}) => {\n const [expanded, setExpanded] = useState(false);\n const [isOverFoldingMaxHeight, setIsOverFoldingMaxHeight] = useState(false);\n const [textHeight, setTextHeight] = useState(0);\n const textContainerRef = useRef(null);\n\n useEffect(() => {\n if (textContainerRef.current) {\n const height = textContainerRef.current.scrollHeight;\n setTextHeight(height);\n setIsOverFoldingMaxHeight(height > foldingMaxHeight);\n }\n }, [foldingMaxHeight]);\n\n return (\n \n \n \n \n {children}\n \n
\n \n\n {isOverFoldingMaxHeight && showExpandButton && (\n setExpanded(!expanded)}\n cursor=\"pointer\"\n _hover={{ textDecoration: \"underline\" }}\n >\n \n {expanded ? \"閉じる\" : showExpandButtonLabel}\n \n {expanded ? (\n \n ) : (\n \n )}\n \n )}\n \n );\n};\n\nexport default ExpandableText;\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst ArrowForwardIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default ArrowForwardIcon;\n","import { Box, HStack, Stack } from \"@chakra-ui/react\";\nimport React, { useId } from \"react\";\nimport CustomLinkLinkify from \"../../../shared/components/atoms/CustomLinkLinkify\";\nimport { BusinessEntityChip } from \"./BusinessEntityChip\";\nimport BusinessEntityContentsCard from \"./BusinessEntityContentsCard\";\nimport BusinessEntityDottedLine from \"./BusinessEntityDottedLine\";\nimport ExpandableText from \"./ExpandableText\";\nimport { Swiper, SwiperSlide } from \"swiper/react\";\nimport { Navigation, Pagination } from \"swiper/modules\";\n\nimport \"swiper/css\";\nimport \"swiper/css/navigation\";\nimport \"swiper/css/pagination\";\nimport ArrowForwardIcon from \"../../../shared/components/icons/ArrowForwardIcon\";\nimport { Helmet } from \"react-helmet\";\n\nconst BusinessEntityDesignWorkCard = ({\n designWork,\n}: {\n designWork: {\n project_name: string;\n project_detail: string;\n building_type: { name: string; id: number };\n structure_types: { name: string; id: number }[];\n prefecture: { name: string; id: number };\n above_ground_floors: number | null;\n underground_floors: number | null;\n completion_year: number | null;\n total_floor_area: number | null;\n images: { url: string }[];\n };\n}) => {\n const id = useId();\n const hasImages = designWork.images.length > 0;\n\n return (\n \n {hasImages && (\n <>\n \n \n \n \n \n \n \n \n \n {designWork.images.map((image, index) => (\n \n \n \n ))}\n \n \n \n \n \n \n {designWork.images.length > 1 && (\n \n \n \n )}\n >\n )}\n \n \n {designWork.project_name}\n \n \n {[\n designWork.prefecture.name,\n designWork.above_ground_floors\n ? `地上${designWork.above_ground_floors}階建て`\n : null,\n designWork.underground_floors\n ? `地下${designWork.underground_floors}階建て`\n : null,\n designWork.total_floor_area\n ? `延床面積:${designWork.total_floor_area.toLocaleString()}㎡`\n : null,\n designWork.completion_year\n ? `竣工年:${designWork.completion_year}年`\n : null,\n ]\n .filter(Boolean)\n .map((text, index, original) => (\n \n \n {text}\n \n {index < original.length - 1 && (\n \n )}\n \n ))}\n \n \n {[\n {\n sectionTitle: \"建物種別\",\n contents: [designWork.building_type.name],\n },\n {\n sectionTitle: \"構造種別\",\n contents: designWork.structure_types.map(\n (structureType) => structureType.name,\n ),\n },\n ].map(({ sectionTitle, contents }) => (\n \n \n {sectionTitle}:\n \n \n {contents.map((text) => (\n {text}\n ))}\n \n \n ))}\n \n \n \n \n {designWork.project_detail}\n \n \n \n \n );\n};\n\nexport default BusinessEntityDesignWorkCard;\n","import React, { ReactNode } from \"react\";\nimport { Heading, HeadingProps } from \"@chakra-ui/react\";\n\ntype BusinessEntitySectionTitleProps = {\n children: ReactNode;\n} & HeadingProps;\n\nconst BusinessEntitySectionTitle = ({\n children,\n ...props\n}: BusinessEntitySectionTitleProps) => {\n return (\n \n {children}\n \n );\n};\n\nexport default BusinessEntitySectionTitle;\n","import React from \"react\";\nimport { Box, Stack } from \"@chakra-ui/react\";\nimport SideMenu from \"../../../shared/components/atoms/SideMenu\";\nimport { Button } from \"../../../shared/components/atoms\";\nimport SmsIcon from \"../../../shared/components/icons/SmsIcon\";\n\n// TODO: 各メニューにページ内リンクを設定する\nconst BusinessEntitySideMenu = ({\n preview = false,\n showWorks,\n}: {\n preview?: boolean;\n showWorks: boolean;\n}) => {\n const focus = (id: string) => {\n const element = document.querySelector(id);\n if (element) {\n element.scrollIntoView({ behavior: \"smooth\" });\n }\n };\n return (\n \n \n focus(\"#summary\"),\n },\n {\n label: \"業務の希望条件\",\n onClick: () => focus(\"#conditions\"),\n },\n showWorks\n ? {\n label: \"設計事例・実績\",\n onClick: () => focus(\"#works\"),\n }\n : null,\n {\n label: \"事業者情報\",\n onClick: () => focus(\"#business-entity-info\"),\n },\n ].filter((v) => v !== null)}\n />\n \n {/* TODO: リンク追加 */}\n }\n {...(preview ? {} : { as: \"a\", href: \"\" })}\n >\n お問い合わせする\n \n \n );\n};\n\nexport default BusinessEntitySideMenu;\n","import React from \"react\";\nimport { HStack, Text } from \"@chakra-ui/react\";\nimport DownloadIcon from \"../../../shared/components/icons/DownloadIcon\";\nimport FileUploadIcon from \"../../../shared/components/icons/FileUploadIcon\";\n\nexport type BusinessEntityPreferenceStatus = \"contractor\" | \"client\" | \"both\";\n\ntype BusinessEntityPreferenceStatusTagProps = {\n type: BusinessEntityPreferenceStatus;\n variant: \"contractor\" | \"client\";\n};\n\nexport type BusinessEntityPreferenceStatusTagsProps = {\n preferenceStatus: BusinessEntityPreferenceStatus;\n};\n\nconst BusinessEntityPreferenceStatusTag = ({\n variant,\n}: BusinessEntityPreferenceStatusTagProps) => {\n const tagConfig = {\n contractor: {\n color: \"#00468C\",\n bgColor: \"#D7E7F7\",\n text: \"受注したい\",\n icon: ,\n },\n client: {\n color: \"#246C03\",\n bgColor: \"#E0E8DC\",\n text: \"委託したい\",\n icon: ,\n },\n };\n\n const config = tagConfig[variant];\n\n return (\n \n {config.icon}\n \n {config.text}\n \n \n );\n};\n\nconst BusinessEntityPreferenceStatusTags = ({\n preferenceStatus,\n}: BusinessEntityPreferenceStatusTagsProps) => {\n return (\n \n {(preferenceStatus === \"contractor\" || preferenceStatus === \"both\") && (\n \n )}\n {(preferenceStatus === \"client\" || preferenceStatus === \"both\") && (\n \n )}\n \n );\n};\n\nexport default BusinessEntityPreferenceStatusTags;\n","import {\n Box,\n Divider,\n Flex,\n Heading,\n HStack,\n Image,\n Link,\n Stack,\n Text,\n} from \"@chakra-ui/react\";\nimport React from \"react\";\nimport { SharedCurrentUser } from \"../../../shared/lib/types\";\nimport ExpandableText from \"./ExpandableText\";\nimport { BusinessEntity } from \"../lib/types\";\n\ntype BusinessEntitySummaryProps = Pick<\n BusinessEntity,\n | \"name\"\n | \"prefecture\"\n | \"industry\"\n | \"number_of_employees\"\n | \"website_url\"\n | \"introduction\"\n | \"logo_image_url\"\n> & { currentUser: SharedCurrentUser };\n\nconst BusinessEntitySummary = ({\n name,\n prefecture,\n industry,\n number_of_employees,\n website_url,\n introduction,\n logo_image_url,\n currentUser,\n}: BusinessEntitySummaryProps) => {\n return (\n \n \n }\n src={logo_image_url}\n />\n \n {name}\n \n \n {prefecture.name}\n \n {industry.name}\n \n 従業員数:{number_of_employees}名\n \n {website_url !== \"\" && (\n \n {website_url}\n \n )}\n \n \n \n \n \n {introduction}\n \n \n );\n};\n\nexport default BusinessEntitySummary;\n","import {\n Box,\n Container,\n Flex,\n Grid,\n GridItem,\n HStack,\n Show,\n Stack,\n} from \"@chakra-ui/react\";\nimport React, { ReactNode } from \"react\";\nimport CustomLinkLinkify from \"../../../shared/components/atoms/CustomLinkLinkify\";\nimport FacebookIcon from \"../../../shared/components/icons/FacebookIcon\";\nimport InstagramIcon from \"../../../shared/components/icons/InstagramIcon\";\nimport NoteIcon from \"../../../shared/components/icons/NoteIcon\";\nimport PinterestIcon from \"../../../shared/components/icons/PinterestIcon\";\nimport ThreadsIcon from \"../../../shared/components/icons/ThreadsIcon\";\nimport TikTokIcon from \"../../../shared/components/icons/TikTokIcon\";\nimport XIcon from \"../../../shared/components/icons/XIcon\";\nimport YoutubeIcon from \"../../../shared/components/icons/YoutubeIcon\";\nimport { SharedApprovedCurrentUser } from \"../../../shared/lib/types\";\nimport { BusinessEntity } from \"../../business_entities/lib/types\";\nimport Accordion from \"../../business_entities/components/Accordion\";\nimport { BusinessEntityChip } from \"../../business_entities/components/BusinessEntityChip\";\nimport BusinessEntityContentsCard from \"../../business_entities/components/BusinessEntityContentsCard\";\nimport BusinessEntityDesignWorkCard from \"../../business_entities/components/BusinessEntityDesignWorkCard\";\nimport BusinessEntityDottedLine from \"../../business_entities/components/BusinessEntityDottedLine\";\nimport BusinessEntitySectionTitle from \"../../business_entities/components/BusinessEntitySectionTitle\";\nimport BusinessEntitySideMenu from \"../../business_entities/components/BusinessEntitySideMenu\";\nimport BusinessEntityPreferenceStatusTags from \"../../business_entities/components/BusinessEntityStatusTags\";\nimport BusinessEntitySummary from \"../../business_entities/components/BusinessEntitySummary\";\n\nconst BusinessEntityShowContent = ({\n businessEntity,\n currentUser,\n preview = false,\n}: {\n businessEntity: Omit;\n currentUser: SharedApprovedCurrentUser | null;\n preview?: boolean;\n}) => {\n return (\n \n \n \n \n \n \n \n 希望する協力形態:\n \n \n \n \n \n \n \n \n \n 業務の希望条件\n \n \n {(businessEntity.use_case === \"client\" ||\n businessEntity.use_case === \"both\") &&\n businessEntity.client_info && (\n \n \n 発注したい業種\n \n {businessEntity.client_info.desired_industries.map(\n ({ name, id }) => (\n \n {name}\n \n ),\n )}\n \n ,\n \n 発注したい分野\n \n {businessEntity.client_info.desired_business_fields.map(\n ({ name, id }) => (\n \n {name}\n \n ),\n )}\n \n ,\n \n 発注したい業務内容\n \n \n {\n businessEntity.client_info\n .desired_business_service_note\n }\n \n \n ,\n ]}\n />\n \n )}\n {(businessEntity.use_case === \"contractor\" ||\n businessEntity.use_case === \"both\") &&\n businessEntity.contractor_info && (\n \n \n 受注したい業種\n \n {businessEntity.contractor_info.desired_industries.map(\n ({ name, id }) => (\n \n {name}\n \n ),\n )}\n \n ,\n \n 対応できる分野\n \n {businessEntity.contractor_info.supported_business_fields.map(\n ({ name, id }) => (\n \n {name}\n \n ),\n )}\n \n ,\n \n 対応できる業務内容\n \n \n {\n businessEntity.contractor_info\n .supported_business_service_note\n }\n \n \n ,\n \n \n 対応できる建物種別・構造種別\n \n (\n \n {structure_types.map(({ id, name }) => (\n \n {name}\n \n ))}\n \n ),\n )}\n />\n ,\n \n 対応可能エリア\n \n {businessEntity.contractor_info.supported_prefectures.map(\n ({ region, prefectures }) => (\n \n \n {region}\n \n \n {prefectures\n .map(({ name }) => name)\n .join(\" / \")}\n \n \n ),\n )}\n \n ,\n ]}\n />\n \n )}\n \n \n \n {(businessEntity.use_case === \"contractor\" ||\n businessEntity.use_case === \"both\") &&\n businessEntity.contractor_info &&\n businessEntity.contractor_info.architectural_design_works\n .length > 0 && (\n \n \n 設計事例・実績\n \n \n {businessEntity.contractor_info.architectural_design_works.map(\n (designWork, index) => (\n \n ),\n )}\n \n \n )}\n \n {(businessEntity.main_client !== \"\" ||\n businessEntity.used_architectural_software_categories\n .length > 0) && (\n \n {businessEntity.main_client !== \"\" && (\n \n {businessEntity.main_client}\n \n )}\n {businessEntity.used_architectural_software_categories\n .length > 0 && (\n \n (\n \n {softwares.map(({ name, id }) => (\n \n {name}\n \n ))}\n \n ),\n )}\n />\n \n )}\n \n )}\n \n \n \n 事業者情報\n \n \n \n {businessEntity.name}\n ,\n \n \n 〒{businessEntity.postal_code}\n
\n {businessEntity.prefecture.name}\n {businessEntity.address1}\n {businessEntity.address2}\n {businessEntity.address3}\n \n ,\n \n {businessEntity.industry.name}\n ,\n \n {businessEntity.number_of_employees}名\n ,\n \n \n {\n businessEntity.establishment_year_month.split(\n \"/\",\n )[0]\n }\n 年\n {\n businessEntity.establishment_year_month.split(\n \"/\",\n )[1]\n }\n 月\n \n ,\n \n \n {businessEntity.capital_ten_thousand_yen === 0\n ? \"-\"\n : `${businessEntity.capital_ten_thousand_yen}万円`}\n \n ,\n \n {businessEntity.president_name}\n ,\n businessEntity.website_url !== \"\" ? (\n \n \n \n {businessEntity.website_url}\n \n \n \n ) : null,\n (\n [\n \"facebook_url\",\n \"instagram_url\",\n \"note_url\",\n \"pinterest_url\",\n \"threads_url\",\n \"tiktok_url\",\n \"x_url\",\n \"youtube_url\",\n ] as const\n ).some((key) => businessEntity[key] !== \"\") ? (\n \n {businessEntity.facebook_url !== \"\" && (\n \n \n \n )}\n {businessEntity.instagram_url !== \"\" && (\n \n \n \n )}\n {businessEntity.note_url !== \"\" && (\n \n \n \n )}\n {businessEntity.pinterest_url !== \"\" && (\n \n \n \n )}\n {businessEntity.threads_url !== \"\" && (\n \n \n \n )}\n {businessEntity.tiktok_url !== \"\" && (\n \n \n \n )}\n {businessEntity.x_url !== \"\" && (\n \n \n \n )}\n {businessEntity.youtube_url !== \"\" && (\n \n \n \n )}\n \n ) : null,\n ].filter(Boolean)}\n />\n \n \n \n \n \n \n \n 0\n )\n }\n />\n \n \n \n \n \n \n \n );\n};\n\nconst CardRows = ({ rows }: { rows: ReactNode[] }) => (\n \n {rows.map((row, index) => (\n <>\n {row}\n {index < rows.length - 1 && }\n >\n ))}\n \n);\n\nconst CardRowTitle = ({ children }: { children: ReactNode }) => (\n \n {children}\n \n);\n\nconst CardRowGroup = ({\n name,\n children,\n}: {\n name: string;\n children: ReactNode;\n}) => (\n \n \n {name}\n \n \n {children}\n \n \n);\n\nconst CardRowGroups = ({ groups }: { groups: ReactNode[] }) => (\n \n {groups.map((group, index) => (\n {group}\n ))}\n \n);\n\nexport default BusinessEntityShowContent;\n","import { Box, Flex, HStack } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport BackgroundBuildingImage from \"../../../shared/components/atoms/BackgroundBuildingImage\";\nimport Background from \"../../../shared/components/atoms/Background\";\nimport ArrowLeftAltIcon from \"../../../shared/components/icons/ArrowRightAltIcon\";\nimport BusinessEntityShowContent from \"./BusinessEntityShowContent\";\nimport { useNavigate } from \"react-router-dom\";\nimport { Options, SharedApprovedCurrentUser } from \"../../../shared/lib/types\";\nimport { Step1FormData } from \"../../owned_business_entities/lib/useStep1Form\";\nimport { Step2FormData } from \"../../owned_business_entities/lib/useStep2Form\";\nimport {\n GroupedPrefectures,\n SharedArchitecturalSoftwares,\n} from \"../../owned_business_entities/lib/types\";\n\nconst BusinessEntityContentPreview = ({\n currentUser,\n businessEntity,\n prefectures,\n industries,\n businessFields,\n groupedPrefectures,\n buildingTypes,\n structureTypes,\n logoImage,\n designedArchitecturalWorksImages,\n architecturalSoftwares,\n}: {\n currentUser: SharedApprovedCurrentUser;\n businessEntity: Step1FormData & Step2FormData;\n prefectures: Options;\n industries: Options;\n businessFields: Options;\n groupedPrefectures: GroupedPrefectures;\n buildingTypes: Options;\n structureTypes: Options;\n logoImage: string;\n designedArchitecturalWorksImages: (\n | { url: string; signed_id: string }\n | File\n )[][];\n architecturalSoftwares: SharedArchitecturalSoftwares;\n}) => {\n const navigate = useNavigate();\n const client_info =\n businessEntity.client_info_attributes &&\n \"desired_industry_ids\" in businessEntity.client_info_attributes\n ? {\n ...businessEntity.client_info_attributes,\n desired_industries:\n businessEntity.client_info_attributes.desired_industry_ids.map(\n (id) => optionObject({ id, options: industries }),\n ),\n desired_business_fields:\n businessEntity.client_info_attributes.desired_business_field_ids.map(\n (id) => optionObject({ id, options: businessFields }),\n ),\n }\n : null;\n\n const contractor_info =\n businessEntity.contractor_info_attributes &&\n \"desired_industry_ids\" in businessEntity.contractor_info_attributes\n ? {\n ...businessEntity.contractor_info_attributes,\n desired_industries:\n businessEntity.contractor_info_attributes.desired_industry_ids.map(\n (id) => optionObject({ id, options: industries }),\n ),\n supported_business_fields:\n businessEntity.contractor_info_attributes.supported_business_field_ids.map(\n (id) => optionObject({ id, options: businessFields }),\n ),\n supported_building_types:\n businessEntity.contractor_info_attributes.contractor_info_supported_building_types_attributes.map(\n (building_type) => ({\n ...optionObject({\n id: building_type.building_type_id,\n options: buildingTypes,\n }),\n structure_types: building_type.structure_type_ids.map((id) =>\n optionObject({ id, options: structureTypes }),\n ),\n }),\n ),\n supported_prefectures: groupedPrefectures\n .map((regionGroup) => {\n const regionPrefectures = regionGroup.prefectures.filter(\n (prefecture) =>\n businessEntity.contractor_info_attributes &&\n \"desired_industry_ids\" in\n businessEntity.contractor_info_attributes &&\n businessEntity.contractor_info_attributes?.supported_prefecture_ids?.includes(\n prefecture.id.toString(),\n ),\n );\n\n return regionPrefectures.length > 0\n ? {\n region: regionGroup.region,\n prefectures: regionPrefectures,\n }\n : null;\n })\n .filter((v) => v !== null),\n architectural_design_works:\n businessEntity.contractor_info_attributes.architectural_design_works_attributes\n .filter((v) => v.published)\n .map((work, index) => ({\n ...work,\n prefecture: optionObject({\n id: work.prefecture_id,\n options: prefectures,\n }),\n building_type: optionObject({\n id: work.building_type_id,\n options: buildingTypes,\n }),\n structure_types: work.structure_type_ids.map((id) =>\n optionObject({ id, options: structureTypes }),\n ),\n above_ground_floors: work.above_ground_floors\n ? parseInt(work.above_ground_floors)\n : null,\n underground_floors: work.underground_floors\n ? parseInt(work.underground_floors)\n : null,\n total_floor_area: work.total_floor_area\n ? parseInt(work.total_floor_area)\n : null,\n completion_year: work.completion_year\n ? parseInt(work.completion_year)\n : null,\n images: designedArchitecturalWorksImages![index]?.map(\n (image) => {\n if (image instanceof File) {\n return { url: URL.createObjectURL(image), signed_id: \"\" };\n } else {\n return image;\n }\n },\n ),\n })),\n }\n : null;\n\n return (\n \n \n \n navigate(-1)}\n textAlign=\"center\"\n >\n \n \n \n \n \n 入力画面に戻る\n \n \n \n \n プレビュー表示中\n \n \n \n {\n const categorySoftwares = items.filter((item) =>\n businessEntity.used_architectural_software_ids.includes(\n item.id.toString(),\n ),\n );\n return categorySoftwares.length > 0\n ? {\n category,\n softwares: categorySoftwares.map((software) => ({\n id: software.id,\n name: software.name,\n })),\n }\n : null;\n })\n .filter((v) => v !== null),\n capital_ten_thousand_yen: parseInt(\n businessEntity.capital_ten_thousand_yen,\n ),\n prefecture: optionObject({\n id: businessEntity.prefecture_id,\n options: prefectures,\n }),\n industry: optionObject({\n id: businessEntity.industry_id,\n options: industries,\n }),\n establishment_year_month: `${businessEntity.establishment_year}/${businessEntity.establishment_month}`,\n client_info,\n contractor_info,\n }}\n preview\n />\n \n \n \n );\n};\n\nconst optionObject = ({\n id,\n options,\n}: {\n id: string | undefined;\n options: Options;\n}) => {\n return {\n id: parseInt(id ?? \"0\"),\n name:\n options.find((option) => option.id === parseInt(id ?? \"0\"))?.name ?? \"\",\n };\n};\n\nexport default BusinessEntityContentPreview;\n","export const useCaseOptions = [\n { value: \"contractor\", label: \"受注したい\" },\n { value: \"client\", label: \"発注したい\" },\n { value: \"both\", label: \"両方利用したい\" },\n];\n","import { Box, Flex, Heading, HStack, Stack, Text } from \"@chakra-ui/react\";\nimport React, { Fragment } from \"react\";\nimport SectionTitle from \"../../../shared/components/atoms/SectionTitle\";\nimport { Button } from \"../../../shared/components/atoms\";\nimport { FormLabel } from \"../../../shared/components/atoms/form\";\nimport { Options } from \"../../../shared/lib/types\";\nimport { Step1FormData } from \"../lib/useStep1Form\";\nimport { Step2FormData } from \"../lib/useStep2Form\";\nimport { useCaseOptions } from \"../lib/useCasesOptions\";\nimport { BusinessType, SharedArchitecturalSoftwares } from \"../lib/types\";\nimport { Helmet } from \"react-helmet\";\n\nconst Confirmation = ({\n values,\n logoImagePreview,\n businessTypes,\n prefectures,\n industries,\n isSubmitting,\n businessFields,\n architecturalSoftwares,\n buildingTypes,\n structureTypes,\n designedArchitecturalWorksImageUrls,\n onSubmit,\n onClickFix,\n onClickPreview,\n}: {\n values: Step1FormData & Step2FormData;\n logoImagePreview: string;\n businessTypes: BusinessType[];\n prefectures: Options;\n industries: Options;\n isSubmitting: boolean;\n businessFields: Options;\n architecturalSoftwares: SharedArchitecturalSoftwares;\n buildingTypes: Options;\n structureTypes: Options;\n designedArchitecturalWorksImageUrls: string[][];\n onSubmit: () => void;\n onClickFix: () => void;\n onClickPreview: () => void;\n}) => {\n const businessTypeLabel = businessTypes.find(\n (businessType) => businessType.value === values.business_type,\n )!.label;\n\n const isSelectedSns =\n values.facebook_url ||\n values.instagram_url ||\n values.note_url ||\n values.pinterest_url ||\n values.threads_url ||\n values.tiktok_url ||\n values.x_url ||\n values.youtube_url;\n\n return (\n <>\n \n \n 事業者情報 確認画面|建築士のためのコミュニティA-Loop【エーループ】\n \n \n \n 事業者情報 確認画面\n \n \n 事業者情報\n \n \n 事業者区分\n {businessTypeLabel}\n \n \n 事業者名\n {values.name}\n \n {values.name_kana && (\n \n 事業者名かな\n {values.name_kana}\n \n )}\n \n 郵便番号\n {values.postal_code}\n \n \n 都道府県\n \n {\n prefectures.find(\n (prefecture) =>\n prefecture.id.toString() ===\n values.prefecture_id.toString(),\n )!.name\n }\n \n \n \n 市区町村\n {values.address1}\n \n \n 番地\n {values.address2}\n \n \n 建物名\n {values.address3}\n \n \n 電話番号\n {values.phone_number}\n \n \n 受発注の通知を受け取るメールアドレス\n {values.email}\n \n \n 主業種\n \n {\n industries.find(\n (industry) =>\n industry.id.toString() === values.industry_id.toString(),\n )!.name\n }\n \n \n \n 従業員数\n {values.number_of_employees}名\n \n \n 設立月\n \n {values.establishment_year}\n 年\n {values.establishment_month}\n 月\n \n \n \n 資本金\n {values.capital_ten_thousand_yen}万円\n \n \n 事業者ロゴ\n {logoImagePreview ? (\n \n ) : (\n 未設定\n )}\n \n \n 代表者氏名\n {values.president_name}\n \n {values.website_url && (\n \n Webサイト\n {values.website_url}\n \n )}\n \n \n {isSelectedSns && (\n \n SNS\n \n {values.facebook_url && (\n \n Facebook\n {values.facebook_url}\n \n )}\n {values.instagram_url && (\n \n Instagram\n {values.instagram_url}\n \n )}\n {values.note_url && (\n \n note\n {values.note_url}\n \n )}\n {values.pinterest_url && (\n \n Pinterest\n {values.pinterest_url}\n \n )}\n {values.threads_url && (\n \n Threads\n {values.threads_url}\n \n )}\n {values.tiktok_url && (\n \n TikTok\n {values.tiktok_url}\n \n )}\n {values.x_url && (\n \n X(旧Twitter)\n {values.x_url}\n \n )}\n {values.youtube_url && (\n \n YouTube\n {values.youtube_url}\n \n )}\n \n \n )}\n \n 受発注の利用用途\n \n \n {useCaseOptions.find((it) => it.value === values.use_case)!.label}\n \n \n \n \n 事業者プロフィール\n \n \n 事業者紹介\n {values.introduction}\n \n \n {values.used_architectural_software_ids.length > 0 && (\n <>\n 利用している建築系ソフトウェア\n \n {architecturalSoftwares.map(\n ({ category, items }) =>\n values.used_architectural_software_ids.some((id) =>\n items.find((it) => it.id.toString() === id),\n ) && (\n \n \n \n {category}\n \n \n {values.used_architectural_software_ids.map(\n (id) => {\n const item = items.find(\n (it) => it.id.toString() === id,\n );\n return (\n \n {item ? {item.name} : <>>}\n \n );\n },\n )}\n \n \n \n ),\n )}\n \n >\n )}\n \n {values.main_client && (\n \n 主な取引先\n {values.main_client}\n \n )}\n \n {[\"contractor\", \"both\"].includes(values.use_case) &&\n // 以下2つの条件はfalseにならない想定\n // yup.lazyによりtypescriptが怒るので、条件分岐を入れている\n values.contractor_info_attributes &&\n \"desired_industry_ids\" in values.contractor_info_attributes && (\n \n 受注したい業務\n \n \n 受注したい業種\n \n {values.contractor_info_attributes.desired_industry_ids.map(\n (id) => (\n \n {industryById(industries, id)?.name}\n \n ),\n )}\n \n \n \n 対応できる分野\n \n {values.contractor_info_attributes.supported_business_field_ids.map(\n (id) => (\n \n {businessFieldById(businessFields, id)?.name}\n \n ),\n )}\n \n \n \n 対応できる業務内容\n \n {\n values.contractor_info_attributes\n .supported_business_service_note\n }\n \n \n \n 対応できる建物種別\n \n {values.contractor_info_attributes.contractor_info_supported_building_types_attributes.map(\n (it) => (\n \n \n {\n buildingTypeById(\n buildingTypes,\n it.building_type_id,\n )?.name\n }\n \n \n {it.structure_type_ids.map((id) => (\n \n {structureTypeById(structureTypes, id)?.name}\n \n ))}\n \n \n ),\n )}\n \n \n \n 対応可能エリア\n \n {values.contractor_info_attributes.supported_prefecture_ids\n .map((id) => prefectureById(prefectures, id)?.name ?? \"\")\n .join(\", \")}\n \n \n \n 設計実績\n \n {values.contractor_info_attributes.architectural_design_works_attributes.map(\n (it, index) => (\n \n \n プロジェクト名\n {it.project_name}\n \n \n 建物種別\n \n {\n buildingTypeById(\n buildingTypes,\n it.building_type_id!,\n )?.name\n }\n \n \n \n 構造種別\n \n {it.structure_type_ids\n .map(\n (id) =>\n structureTypeById(structureTypes, id)?.name,\n )\n .join(\", \")}\n \n \n \n プロジェクト詳細\n {it.project_detail}\n \n {it.prefecture_id && (\n \n 所在地\n \n {\n prefectureById(prefectures, it.prefecture_id)\n ?.name\n }\n \n \n )}\n {it.above_ground_floors && (\n \n 地上\n {it.above_ground_floors}階\n \n )}\n {it.underground_floors && (\n \n 地下\n {it.underground_floors}階\n \n )}\n {it.total_floor_area && (\n \n 延床面積\n {it.total_floor_area}㎡\n \n )}\n {it.completion_year && (\n \n 竣工年\n {it.completion_year}年\n \n )}\n {designedArchitecturalWorksImageUrls[index].length >\n 0 && (\n \n 建物写真\n {designedArchitecturalWorksImageUrls[index]}\n \n {designedArchitecturalWorksImageUrls[index].map(\n (file, index) => (\n \n ),\n )}\n \n \n )}\n \n ),\n )}\n \n \n \n \n )}\n {[\"client\", \"both\"].includes(values.use_case) &&\n // 以下2つの条件はfalseにならない想定\n // yup.lazyによりtypescriptが怒るので、条件分岐を入れている\n values.client_info_attributes &&\n \"desired_industry_ids\" in values.client_info_attributes && (\n \n 発注したい業務\n \n \n 発注したい業務\n \n {values.client_info_attributes.desired_industry_ids.map(\n (id) => (\n \n {industryById(industries, id)?.name}\n \n ),\n )}\n \n \n \n 発注したい分野\n \n {values.client_info_attributes.desired_business_field_ids.map(\n (id) => (\n \n {businessFieldById(businessFields, id)?.name}\n \n ),\n )}\n \n \n \n 発注したい業務内容\n \n {\n values.client_info_attributes\n .desired_business_service_note\n }\n \n \n \n \n )}\n \n\n \n \n \n \n \n \n >\n );\n};\n\nconst prefectureById = (prefectures: Options, id: string) =>\n prefectures.find((it) => it.id.toString() === id);\n\nconst structureTypeById = (structureTypes: Options, id: string) =>\n structureTypes.find((it) => it.id.toString() === id);\n\nconst businessFieldById = (businessFields: Options, id: string) =>\n businessFields.find((it) => it.id.toString() === id);\n\nconst buildingTypeById = (buildingTypes: Options, id: string) =>\n buildingTypes.find((it) => it.id.toString() === id);\n\nconst industryById = (industries: Options, id: string) =>\n industries.find((it) => it.id.toString() === id);\n\nexport default Confirmation;\n","import { UseFormReturn } from \"react-hook-form\";\nimport YubinBango from \"yubinbango.js\";\nimport { Options } from \"../../../shared/lib/types\";\n\nconst fillAddressByPostalCode = async ({\n prefectures,\n postalCode,\n form,\n}: {\n prefectures: Options;\n postalCode: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n form: UseFormReturn;\n}) => {\n if (postalCode.length !== 7) return;\n\n try {\n const address = await YubinBango.getAddress(postalCode);\n const prefectureId = prefectures.find(\n (prefecture) => prefecture.name === address.prefecture,\n )!.id;\n\n form.setValue(\"prefecture_id\", prefectureId.toString());\n form.setValue(\"address1\", `${address.locality}${address.street}`);\n if (address.extended) form.setValue(\"address2\", address.extended);\n\n form.clearErrors(\"postal_code\");\n } catch (e) {\n if (e instanceof Error && e.message.includes(`Can't find matched data.`)) {\n form.setError(\"postal_code\", { message: \"存在しない郵便番号です\" });\n } else {\n throw e;\n }\n }\n};\n\nexport default fillAddressByPostalCode;\n","const getEstablishmentYears = () => {\n return Array.from(\n { length: new Date().getFullYear() - 499 },\n (_, i) => i + 500,\n ).reverse();\n};\n\nexport default getEstablishmentYears;\n","/* eslint-disable no-irregular-whitespace */\nimport {\n Box,\n HStack,\n InputProps,\n RadioGroup,\n Stack,\n Text,\n} from \"@chakra-ui/react\";\nimport React from \"react\";\nimport {\n ControllerFieldState,\n ControllerRenderProps,\n FieldPath,\n} from \"react-hook-form\";\nimport UploadFiles, {\n FileType,\n} from \"../../../../shared/components/UploadFiles\";\nimport Dropzone from \"../../../shared/components/atoms/Dropzone\";\nimport {\n Input,\n InputError,\n Radio,\n Textarea,\n} from \"../../../shared/components/atoms/form\";\nimport Select from \"../../../shared/components/atoms/form/Select\";\nimport { Options } from \"../../../shared/lib/types\";\nimport getEstablishmentYears from \"../lib/getEstablishmentYears\";\nimport { GroupedPrefectures } from \"../lib/types\";\nimport { useCaseOptions } from \"../lib/useCasesOptions\";\nimport { Step1FormData } from \"../lib/useStep1Form\";\nimport { Step2FormData } from \"../lib/useStep2Form\";\n\ntype Step1Field> =\n ControllerRenderProps<\n Step1FormData | (Step1FormData & Step2FormData),\n TFieldName\n >;\n\ntype Step2Field> =\n ControllerRenderProps<\n Step2FormData | (Step1FormData & Step2FormData),\n TFieldName\n >;\n\nexport const NameField = ({\n field,\n fieldState: { error },\n onInput,\n isNoLabel = false,\n}: {\n field: Step1Field<\"name\">;\n fieldState: ControllerFieldState;\n onInput: () => void;\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n\nexport const NameKanaField = ({\n field,\n fieldState: { error },\n isNoLabel = false,\n}: {\n field: Step1Field<\"name_kana\">;\n fieldState: ControllerFieldState;\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n\nexport const PostalCodeField = ({\n field,\n fieldState: { error },\n onChange,\n isNoLabel = false,\n}: {\n field: Step1Field<\"postal_code\">;\n fieldState: ControllerFieldState;\n onChange: (e: React.ChangeEvent) => void;\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n\nexport const PrefectureField = ({\n field,\n fieldState: { error },\n prefectures,\n isNoLabel = false,\n}: {\n field: Step1Field<\"prefecture_id\">;\n fieldState: ControllerFieldState;\n prefectures: { id: string | number; name: string }[];\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n\nexport const Address1Field = ({\n field,\n fieldState: { error },\n isNoLabel = false,\n}: {\n field: Step1Field<\"address1\">;\n fieldState: ControllerFieldState;\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n\nexport const Address2Field = ({\n field,\n fieldState: { error },\n isNoLabel = false,\n}: {\n field: Step1Field<\"address2\">;\n fieldState: ControllerFieldState;\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n\nexport const Address3Field = ({\n field,\n fieldState: { error },\n isNoLabel = false,\n}: {\n field: Step1Field<\"address3\">;\n fieldState: ControllerFieldState;\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n\nexport const PhoneNumberField = ({\n field,\n fieldState: { error },\n isNoLabel = false,\n}: {\n field: Step1Field<\"phone_number\">;\n fieldState: ControllerFieldState;\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n\nexport const EmailField = ({\n field,\n fieldState: { error },\n isNoLabel = false,\n}: {\n field: Step1Field<\"email\">;\n fieldState: ControllerFieldState;\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n\nexport const IndustryField = ({\n field,\n fieldState: { error },\n industries,\n isNoLabel = false,\n}: {\n field: Step1Field<\"industry_id\">;\n fieldState: ControllerFieldState;\n industries: { id: string | number; name: string }[];\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n\nexport const EmployeesField = ({\n field,\n fieldState: { error },\n isNoLabel = false,\n inputProps,\n}: {\n field: Step1Field<\"number_of_employees\">;\n fieldState: ControllerFieldState;\n isNoLabel?: boolean;\n inputProps?: InputProps;\n}) => {\n return (\n \n \n \n \n \n 名\n \n \n 従業員がいない場合は0を入力してください。\n \n \n );\n};\n\nexport const EstablishmentYearField = ({\n field,\n fieldState: { error },\n}: {\n field: Step1Field<\"establishment_year\">;\n fieldState: ControllerFieldState;\n}) => {\n return (\n \n );\n};\n\nexport const EstablishmentMonthField = ({\n field,\n fieldState: { error },\n}: {\n field: Step1Field<\"establishment_month\">;\n fieldState: ControllerFieldState;\n}) => {\n return (\n \n );\n};\n\nexport const CapitalField = ({\n field,\n fieldState: { error },\n inputProps,\n}: {\n field: Step1Field<\"capital_ten_thousand_yen\">;\n fieldState: ControllerFieldState;\n inputProps?: InputProps;\n}) => {\n return (\n \n \n \n \n \n 万円\n \n \n 資本金を持たない場合は、「0」を入力してください。\n \n \n );\n};\n\nexport const PresidentNameField = ({\n field,\n fieldState: { error },\n isNoLabel = false,\n}: {\n field: Step1Field<\"president_name\">;\n fieldState: ControllerFieldState;\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n\nexport const WebsiteField = ({\n field,\n fieldState: { error },\n isNoLabel = false,\n}: {\n field: Step1Field<\"website_url\">;\n fieldState: ControllerFieldState;\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n\nexport const FacebookUrlField = ({\n field,\n fieldState: { error },\n isNoLabel = false,\n}: {\n field: Step1Field<\"facebook_url\">;\n fieldState: ControllerFieldState;\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n\nexport const InstagramUrlField = ({\n field,\n fieldState: { error },\n isNoLabel = false,\n}: {\n field: Step1Field<\"instagram_url\">;\n fieldState: ControllerFieldState;\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n\nexport const NoteUrlField = ({\n field,\n fieldState: { error },\n isNoLabel = false,\n}: {\n field: Step1Field<\"note_url\">;\n fieldState: ControllerFieldState;\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n\nexport const PinterestUrlField = ({\n field,\n fieldState: { error },\n isNoLabel = false,\n}: {\n field: Step1Field<\"pinterest_url\">;\n fieldState: ControllerFieldState;\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n\nexport const ThreadsUrlField = ({\n field,\n fieldState: { error },\n isNoLabel = false,\n}: {\n field: Step1Field<\"threads_url\">;\n fieldState: ControllerFieldState;\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n\nexport const TiktokUrlField = ({\n field,\n fieldState: { error },\n isNoLabel = false,\n}: {\n field: Step1Field<\"tiktok_url\">;\n fieldState: ControllerFieldState;\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n\nexport const XUrlField = ({\n field,\n fieldState: { error },\n isNoLabel = false,\n}: {\n field: Step1Field<\"x_url\">;\n fieldState: ControllerFieldState;\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n\nexport const YoutubeUrlField = ({\n field,\n fieldState: { error },\n isNoLabel = false,\n}: {\n field: Step1Field<\"youtube_url\">;\n fieldState: ControllerFieldState;\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n\nexport const UseCaseField = ({\n field,\n fieldState: { error },\n}: {\n field: Step2Field<\"use_case\">;\n fieldState: ControllerFieldState;\n}) => {\n return (\n \n \n \n {useCaseOptions.map(({ label, value }) => (\n \n {label}\n \n ))}\n \n \n {error && {error?.message}}\n \n );\n};\n\nexport const IntroductionField = ({\n field,\n fieldState: { error },\n isNoLabel = false,\n}: {\n field: Step2Field<\"introduction\">;\n fieldState: ControllerFieldState;\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n\nexport const MainClientField = ({\n field,\n fieldState: { error },\n isNoLabel = false,\n}: {\n field: Step2Field<\"main_client\">;\n fieldState: ControllerFieldState;\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n\nexport const ContractorInfoSupportedBusinessServiceNoteField = ({\n field,\n fieldState: { error },\n isNoLabel = false,\n}: {\n field: Step2Field<\"contractor_info_attributes.supported_business_service_note\">;\n fieldState: ControllerFieldState;\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n\nexport const ContractorInfoArchitecturalDesignWorksProjectNameField = ({\n field,\n fieldState: { error },\n}: {\n field: Step2Field<`contractor_info_attributes.architectural_design_works_attributes.${number}.project_name`>;\n fieldState: ControllerFieldState;\n}) => {\n return (\n \n 正式なプロジェクト名ではなく、イメージが伝わるような仮の名称でも可能です。\n
\n 30文字まで\n >\n }\n />\n );\n};\n\nexport const ContractorInfoArchitecturalDesignWorksBuildingTypeField = ({\n field,\n fieldState: { error },\n buildingTypes,\n}: {\n field: Step2Field<`contractor_info_attributes.architectural_design_works_attributes.${number}.building_type_id`>;\n fieldState: ControllerFieldState;\n buildingTypes: Options;\n}) => {\n return (\n \n );\n};\n\nexport const ContractorInfoArchitecturalDesignWorksProjectDetailField = ({\n field,\n fieldState: { error },\n}: {\n field: Step2Field<`contractor_info_attributes.architectural_design_works_attributes.${number}.project_detail`>;\n fieldState: ControllerFieldState;\n}) => {\n return (\n \n );\n};\n\nexport const ContractorInfoArchitecturalDesignWorksPrefectureField = ({\n field,\n fieldState: { error },\n groupedPrefectures,\n}: {\n field: Step2Field<`contractor_info_attributes.architectural_design_works_attributes.${number}.prefecture_id`>;\n fieldState: ControllerFieldState;\n groupedPrefectures: GroupedPrefectures;\n}) => {\n return (\n \n );\n};\n\nexport const ContractorInfoArchitecturalDesignWorksAboveGroundFloorsField = ({\n field,\n fieldState: { error },\n}: {\n field: Step2Field<`contractor_info_attributes.architectural_design_works_attributes.${number}.above_ground_floors`>;\n fieldState: ControllerFieldState;\n}) => {\n return (\n (\n \n 階建\n \n )}\n />\n );\n};\n\nexport const ContractorInfoArchitecturalDesignWorksUndergroundFloorsField = ({\n field,\n fieldState: { error },\n}: {\n field: Step2Field<`contractor_info_attributes.architectural_design_works_attributes.${number}.underground_floors`>;\n fieldState: ControllerFieldState;\n}) => {\n return (\n (\n \n 階建\n \n )}\n />\n );\n};\n\nexport const ContractorInfoArchitecturalDesignWorksTotalFloorAreaField = ({\n field,\n fieldState: { error },\n}: {\n field: Step2Field<`contractor_info_attributes.architectural_design_works_attributes.${number}.total_floor_area`>;\n fieldState: ControllerFieldState;\n}) => {\n return (\n (\n \n ㎡\n \n )}\n />\n );\n};\n\nexport const ContractorInfoArchitecturalDesignWorksCompletionYearField = ({\n field,\n fieldState: { error },\n}: {\n field: Step2Field<`contractor_info_attributes.architectural_design_works_attributes.${number}.completion_year`>;\n fieldState: ControllerFieldState;\n}) => {\n return (\n (\n \n 年\n \n )}\n />\n );\n};\n\nexport const ContractorInfoArchitecturalDesignWorksImagesField = ({\n files,\n addFiles,\n removeFile,\n isDisabled = false,\n}: {\n files: (FileType & { is_image?: boolean })[];\n addFiles: (files: File[]) => void;\n removeFile: (file: FileType) => void;\n isDisabled?: boolean;\n}) => {\n return (\n \n {!isDisabled && (\n addFiles(files)}\n aspectRatio={3 / 2}\n message={\n \n 外観や内容などの建物写真をアップロードしてください。\n
\n PNGもしくはJPGファイルで、5MB未満のサイズでアップロードしてください。\n
\n 最大で3つまでアップロード可能です。\n \n }\n />\n )}\n \n \n \n \n );\n};\n\nexport const ClientInfoDesiredBusinessServiceNoteField = ({\n field,\n fieldState: { error },\n isNoLabel = false,\n}: {\n field: Step2Field<\"client_info_attributes.desired_business_service_note\">;\n fieldState: ControllerFieldState;\n isNoLabel?: boolean;\n}) => {\n return (\n \n );\n};\n","import {\n Box,\n Divider,\n Flex,\n Heading,\n RadioGroup,\n Stack,\n Text,\n VStack,\n} from \"@chakra-ui/react\";\nimport React, { Ref } from \"react\";\nimport { Helmet } from \"react-helmet\";\nimport { Controller } from \"react-hook-form\";\nimport { Button } from \"../../../shared/components/atoms\";\nimport {\n FormLabel,\n InputError,\n Radio,\n} from \"../../../shared/components/atoms/form\";\nimport {\n ImageFileDropzone,\n useImageFileDropzoneState,\n} from \"../../../shared/components/atoms/ImageFileDropzone\";\nimport SectionTitle from \"../../../shared/components/atoms/SectionTitle\";\nimport SiteSeal from \"../../../shared/components/atoms/SiteSeal\";\nimport { Options } from \"../../../shared/lib/types\";\nimport useAutokana from \"../../../shared/lib/useAutokana\";\nimport { Step1FormData, useStep1Form } from \"../lib/useStep1Form\";\nimport fillAddressByPostalCode from \"../lib/fillAddressByPostalCode\";\nimport {\n NameField,\n NameKanaField,\n PostalCodeField,\n PrefectureField,\n Address1Field,\n Address2Field,\n Address3Field,\n PhoneNumberField,\n EmailField,\n IndustryField,\n EmployeesField,\n EstablishmentYearField,\n EstablishmentMonthField,\n CapitalField,\n PresidentNameField,\n WebsiteField,\n FacebookUrlField,\n InstagramUrlField,\n NoteUrlField,\n PinterestUrlField,\n ThreadsUrlField,\n TiktokUrlField,\n XUrlField,\n YoutubeUrlField,\n} from \"./FormFields\";\n\nconst Step1 = ({\n methods,\n logoImageFileFormRef,\n logoImageFileState,\n prefectures,\n industries,\n onSubmit,\n}: {\n methods: ReturnType;\n logoImageFileFormRef: Ref;\n logoImageFileState: ReturnType;\n prefectures: Options;\n industries: Options;\n onSubmit: (data: Step1FormData) => void;\n}) => {\n const {\n handleSubmit,\n control,\n setValue,\n formState: { isSubmitting },\n } = methods;\n const nameAutoKana = useAutokana(\"#name\", \"#name_kana\");\n const businessTypes = [\n { value: \"company\", label: \"法人\" },\n { value: \"self_employment\", label: \"個人事業主\" },\n ];\n\n return (\n \n \n \n 事業者情報 登録|建築士のためのコミュニティA-Loop【エーループ】\n \n \n \n 事業者情報 登録\n \n \n \n 現在、受発注機能(β版)の利用申請について、事前お申し込みを受け付けております。\n
\n 掲載開始および受発注機能の利用開始は、\n \n 6月10日(火)11時\n \n を予定しておりますので、あらかじめご了承ください。\n \n \n logoImageFileState.validate())}\n mt={10}\n >\n 事業者情報\n \n \n 事業者区分\n {\n const { ref, ...rest } = field;\n return (\n <>\n \n \n {businessTypes.map((businessType) => (\n \n {businessType.label}\n \n ))}\n \n \n {fieldState.error?.message !== undefined && (\n {fieldState.error.message}\n )}\n >\n );\n }}\n />\n \n \n (\n {\n setValue(\"name_kana\", nameAutoKana?.getFurigana());\n }}\n />\n )}\n />\n \n \n (\n \n )}\n />\n \n \n (\n {\n field.onChange(e);\n await fillAddressByPostalCode({\n prefectures,\n postalCode: e.target.value,\n form: methods,\n });\n }}\n />\n )}\n />\n \n \n (\n \n )}\n />\n \n \n (\n \n )}\n />\n \n \n (\n \n )}\n />\n \n \n (\n \n )}\n />\n \n \n (\n \n )}\n />\n \n \n \n (\n \n )}\n />\n \n \n \n \n (\n \n )}\n />\n \n \n 会員登録時の業種と異なる場合は、受発注用の業種を入力してください。\n \n \n \n \n 従業員数\n \n (\n \n )}\n />\n \n \n 設立年月\n \n \n \n \n (\n \n )}\n />\n \n 年\n \n \n \n (\n \n )}\n />\n \n 月\n \n \n \n \n \n \n \n 資本金\n \n (\n \n )}\n />\n \n \n \n 事業者ロゴ\n \n \n \n (\n \n )}\n />\n \n \n (\n \n )}\n />\n \n \n SNS\n \n \n (\n \n )}\n />\n \n \n (\n \n )}\n />\n \n \n (\n \n )}\n />\n \n \n (\n \n )}\n />\n \n \n (\n \n )}\n />\n \n \n (\n \n )}\n />\n \n \n (\n \n )}\n />\n \n \n (\n \n )}\n />\n \n \n \n \n \n \n \n \n \n \n \n );\n};\n\nexport default Step1;\n","import { Box, CheckboxGroup, Stack } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport { Controller, UseFormReturn } from \"react-hook-form\";\nimport { useUploadFiles } from \"../../../../shared/components/UploadFiles\";\nimport { FormLabel, InputError } from \"../../../shared/components/atoms/form\";\nimport Checkbox from \"../../../shared/components/atoms/form/Checkbox\";\nimport { Options } from \"../../../shared/lib/types\";\nimport { GroupedPrefectures } from \"../lib/types\";\nimport {\n ContractorInfoArchitecturalDesignWorksAboveGroundFloorsField,\n ContractorInfoArchitecturalDesignWorksBuildingTypeField,\n ContractorInfoArchitecturalDesignWorksCompletionYearField,\n ContractorInfoArchitecturalDesignWorksImagesField,\n ContractorInfoArchitecturalDesignWorksPrefectureField,\n ContractorInfoArchitecturalDesignWorksProjectDetailField,\n ContractorInfoArchitecturalDesignWorksProjectNameField,\n ContractorInfoArchitecturalDesignWorksTotalFloorAreaField,\n ContractorInfoArchitecturalDesignWorksUndergroundFloorsField,\n} from \"./FormFields\";\n\nconst DesigendArchitecturalFormForNew = ({\n methods: { control },\n namePrefix,\n buildingTypes,\n structureTypes,\n groupedPrefectures,\n onChange,\n defaultImages = [],\n}: {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n methods: UseFormReturn;\n namePrefix: `contractor_info_attributes.architectural_design_works_attributes.${number}`;\n buildingTypes: Options;\n structureTypes: Options;\n groupedPrefectures: GroupedPrefectures;\n onChange: (files: File[]) => void;\n defaultImages?: { url: string; signed_id: string }[];\n}) => {\n // TODO: 新規登録時にはFile[]しかないが、asは使いたくない\n const [files, addFiles, removeFile] = useUploadFiles(\n defaultImages ?? [],\n [],\n 3,\n (files) => onChange(files as File[]),\n );\n\n return (\n \n (\n \n )}\n />\n (\n \n )}\n />\n \n \n 構造種別\n \n \n (\n \n \n \n {structureTypes.map((structureType) => (\n \n {structureType.name}\n \n ))}\n \n \n {error?.message && {error.message}}\n \n )}\n />\n \n \n (\n \n )}\n />\n (\n \n )}\n />\n (\n \n )}\n />\n (\n \n )}\n />\n (\n \n )}\n />\n (\n \n )}\n />\n \n \n 建物写真\n \n \n \n \n );\n};\n\nexport default DesigendArchitecturalFormForNew;\n","type Value = {\n building_type_id: string;\n structure_type_ids: string[];\n}[];\n\nexport const buildingTypeIds = (value: Value) => {\n return value.map((value) => value.building_type_id);\n};\n\nexport const addBuildingType = (value: Value, id: string) => {\n return [\n ...value,\n {\n building_type_id: id,\n structure_type_ids: [],\n },\n ];\n};\n\nexport const removeBuildingType = (value: Value, id: string) => {\n return value.filter((value) => value.building_type_id !== id);\n};\n\nexport const isBuildingTypeSelected = (value: Value, id: string) => {\n return value.some((value) => value.building_type_id === id);\n};\n\nexport const addStructureType = (\n value: Value,\n buildingTypeId: string,\n structureTypeId: string,\n) => {\n return value.map((value) => {\n if (value.building_type_id === buildingTypeId) {\n return {\n ...value,\n structure_type_ids: [...value.structure_type_ids, structureTypeId],\n };\n }\n return value;\n });\n};\n\nexport const removeStructureType = (\n value: Value,\n buildingTypeId: string,\n structureTypeId: string,\n) => {\n return value.map((value) => {\n if (value.building_type_id === buildingTypeId) {\n return {\n ...value,\n structure_type_ids: value.structure_type_ids.filter(\n (id) => id !== structureTypeId,\n ),\n };\n }\n return value;\n });\n};\n","import { Box, CheckboxGroup, Stack } from \"@chakra-ui/react\";\nimport React from \"react\";\nimport { Controller, UseFormReturn } from \"react-hook-form\";\nimport { FormLabel, InputError } from \"../../../shared/components/atoms/form\";\nimport Checkbox from \"../../../shared/components/atoms/form/Checkbox\";\nimport { Options } from \"../../../shared/lib/types\";\nimport {\n addBuildingType,\n addStructureType,\n buildingTypeIds,\n isBuildingTypeSelected,\n removeBuildingType,\n removeStructureType,\n} from \"../lib/buildingTypeStructureTypeHelper\";\n\nconst BuildingTypeStructureTypeFieldForNew = ({\n methods,\n namePrefix,\n labelPrefix,\n buildingTypes,\n structureTypes,\n}: {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n methods: UseFormReturn;\n labelPrefix: string;\n namePrefix: \"supported\";\n buildingTypes: Options;\n structureTypes: Options;\n}) => {\n const { control } = methods;\n\n return (\n \n \n {labelPrefix}建物種別\n \n \n {\n return (\n \n \n \n {buildingTypes.map((buildingType) => (\n \n {\n if (e.target.checked) {\n onChange(\n addBuildingType(\n field.value,\n buildingType.id.toString(),\n ),\n );\n } else {\n onChange(\n removeBuildingType(\n field.value,\n buildingType.id.toString(),\n ),\n );\n }\n }}\n >\n {buildingType.name}\n \n {isBuildingTypeSelected(\n field.value,\n buildingType.id.toString(),\n ) && (\n \n \n \n \n \n \n {labelPrefix}構造種別 - {buildingType.name}\n \n \n \n \n {structureTypes.map((structureType) => (\n \n {\n if (e.target.checked) {\n onChange(\n addStructureType(\n field.value,\n buildingType.id.toString(),\n structureType.id.toString(),\n ),\n );\n } else {\n onChange(\n removeStructureType(\n field.value,\n buildingType.id.toString(),\n structureType.id.toString(),\n ),\n );\n }\n }}\n >\n {structureType.name}\n \n \n ))}\n \n \n \n \n \n )}\n \n ))}\n \n \n {error && {error.message}}\n \n );\n }}\n />\n \n \n );\n};\n\nconst structureTypeIdsByBuildingTypeId = ({\n id,\n value,\n}: {\n id: string;\n value: Array<{ building_type_id: string; structure_type_ids: string[] }>;\n}) => {\n return value.find((v) => v.building_type_id === id)!.structure_type_ids;\n};\n\nexport default BuildingTypeStructureTypeFieldForNew;\n","import { GroupedPrefectures } from \"./types\";\n\nexport const onCheckAllPrefecture = ({\n checked,\n onChange,\n value,\n groupedPrefectures,\n}: {\n checked: boolean;\n onChange: (value: string[]) => void;\n value: string[];\n groupedPrefectures: GroupedPrefectures;\n}) => {\n if (checked) {\n onChange(\n groupedPrefectures.flatMap(({ prefectures }) =>\n prefectures.map(({ id }) => id.toString()),\n ),\n );\n } else if (!checked && value.length === 47) {\n onChange([]);\n }\n};\n","import {\n Box,\n CheckboxGroup,\n Divider,\n Flex,\n Heading,\n HStack,\n Link,\n Stack,\n Text,\n VStack,\n} from \"@chakra-ui/react\";\nimport React from \"react\";\nimport { Helmet } from \"react-helmet\";\nimport { Controller, useFieldArray } from \"react-hook-form\";\nimport { businessMatchingTermsOfServicePath, privacyPolicyPath } from \"../../../../../routes\";\nimport { Button } from \"../../../shared/components/atoms\";\nimport { FormLabel, InputError } from \"../../../shared/components/atoms/form\";\nimport Checkbox from \"../../../shared/components/atoms/form/Checkbox\";\nimport SectionTitle from \"../../../shared/components/atoms/SectionTitle\";\nimport SiteSeal from \"../../../shared/components/atoms/SiteSeal\";\nimport AddIcon from \"../../../shared/components/icons/AddIcon\";\nimport { Options } from \"../../../shared/lib/types\";\nimport { GroupedPrefectures, SharedArchitecturalSoftwares } from \"../lib/types\";\nimport { Step2FormData, useStep2Form } from \"../lib/useStep2Form\";\nimport DesigendArchitecturalFormForNew from \"./DesignedArchitecturalFormForNew\";\nimport BuildingTypeStructureTypeFieldForNew from \"./BuildingTypeStructureTypeFieldForNew\";\nimport {\n ClientInfoDesiredBusinessServiceNoteField,\n ContractorInfoSupportedBusinessServiceNoteField,\n IntroductionField,\n MainClientField,\n UseCaseField,\n} from \"./FormFields\";\nimport { onCheckAllPrefecture } from \"../lib/onCheckAllPrefecture\";\n\nconst Step2 = ({\n methods,\n architecturalSoftwares,\n industries,\n businessFields,\n groupedPrefectures,\n buildingTypes,\n structureTypes,\n setDesigendArchitecturalWorkImages,\n onSubmit,\n onClickBack,\n}: {\n methods: ReturnType;\n architecturalSoftwares: SharedArchitecturalSoftwares;\n industries: Options;\n businessFields: Options;\n groupedPrefectures: GroupedPrefectures;\n buildingTypes: Options;\n structureTypes: Options;\n setDesigendArchitecturalWorkImages: React.Dispatch<\n React.SetStateAction\n >;\n onSubmit: (data: Step2FormData) => void;\n onClickBack: () => void;\n}) => {\n const {\n control,\n formState: { errors, isSubmitting },\n watch,\n } = methods;\n const { fields, append, remove } = useFieldArray({\n control,\n name: \"contractor_info_attributes.architectural_design_works_attributes\",\n });\n const useCase = watch(\"use_case\");\n\n return (\n \n \n \n 事業者情報詳細 登録|建築士のためのコミュニティA-Loop【エーループ】\n \n \n \n 事業者情報 詳細登録\n (業務内容、設計実績など)\n \n \n \n 受発注の利用用途\n \n \n 利用用途\n \n \n (\n \n )}\n />\n \n \n \n \n 事業者プロフィール\n \n (\n \n )}\n />\n \n \n 利用している建築系ソフトウェア\n \n \n {architecturalSoftwares.map(({ category, items }) => (\n \n \n {category}\n \n (\n \n \n \n {items.map((item) => (\n \n {item.name}\n \n ))}\n \n \n \n )}\n />\n \n ))}\n {errors.used_architectural_software_ids && (\n \n {errors.used_architectural_software_ids.message}\n \n )}\n \n \n (\n \n )}\n />\n \n \n \n 受注したい業務\n \n \n \n 受注したい業種\n \n \n (\n \n \n \n {industries.map((industry) => (\n \n {industry.name}\n \n ))}\n \n \n {error && {error?.message}}\n \n )}\n />\n \n \n \n \n 対応できる分野\n \n \n (\n \n \n \n {businessFields.map((businessField) => (\n \n {businessField.name}\n \n ))}\n \n \n {error && {error?.message}}\n \n )}\n />\n \n \n (\n \n )}\n />\n \n \n \n 対応可能エリア\n \n \n (\n \n \n \n {\n onCheckAllPrefecture({\n checked: e.target.checked,\n onChange: field.onChange,\n value: field.value,\n groupedPrefectures,\n });\n }}\n >\n 全国\n \n {groupedPrefectures.map(({ region, prefectures }) => (\n \n \n {region}\n \n \n {prefectures.map((prefecture) => (\n \n {prefecture.name}\n \n ))}\n \n \n ))}\n \n \n {error && {error?.message}}\n \n )}\n />\n \n \n \n \n 設計実績\n \n \n {fields.map((field, index) => (\n \n \n \n 実績 {index + 1}\n \n \n {\n setDesigendArchitecturalWorkImages((prev) => {\n const result = [...prev];\n result[index] = files;\n return result;\n });\n }}\n />\n \n {index > 0 && (\n \n )}\n \n \n ))}\n \n {fields.length < 3 && (\n }\n onClick={() => {\n append({\n project_name: \"\",\n building_type_id: \"\",\n structure_type_ids: [],\n project_detail: \"\",\n prefecture_id: \"\",\n above_ground_floors: \"\",\n underground_floors: \"\",\n total_floor_area: \"\",\n completion_year: \"\",\n published: true,\n });\n setDesigendArchitecturalWorkImages((prev) => [...prev, []]);\n }}\n >\n 実績を追加\n \n )}\n \n \n \n \n 発注したい業務\n \n \n \n 発注したい業種\n \n \n (\n \n \n \n {industries.map((industry) => (\n \n {industry.name}\n \n ))}\n \n \n {error && {error?.message}}\n \n )}\n />\n \n \n\n \n \n 発注したい分野\n \n \n (\n \n \n \n {businessFields.map((businessField) => (\n \n {businessField.name}\n \n ))}\n \n \n {error && {error?.message}}\n \n )}\n />\n \n \n\n (\n \n )}\n />\n \n \n \n \n \n (\n <>\n \n \n 「利用規約」および「個人情報保護方針」に同意する\n \n \n {fieldState.error !== undefined && (\n {fieldState.error.message}\n )}\n >\n )}\n />\n \n 「\n \n 利用規約\n \n 」「\n \n 個人情報保護方針\n \n 」をご確認いただき、チェックをお願いします。\n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n};\n\nexport default Step2;\n","import { useStep1Form } from \"./useStep1Form\";\n\nexport const handleValidateEmailResponse = async ({\n response,\n methods,\n}: {\n response: Response;\n methods: Pick<\n ReturnType,\n \"setError\" | \"clearErrors\"\n >;\n}) => {\n const json = await response.json();\n if (json.email) {\n json.email.forEach((message: string) => {\n methods.setError(\"email\", { message }, { shouldFocus: true });\n });\n } else {\n methods.clearErrors(\"email\");\n }\n return json.email == null;\n};\n","import * as yup from \"yup\";\n\nconst max_architectural_design_works = 3;\n\nexport const step1Schema = yup.object({\n business_type: yup.string().trim().required().label(\"事業区分\"),\n name: yup.string().trim().required().label(\"事業者名\"),\n name_kana: yup.string().trim().hiragana().label(\"事業者名かな\"),\n postal_code: yup\n .string()\n .trim()\n .required()\n .matches(/^\\d{7}$/, {\n message: \"ハイフン無しの7桁の数字を入力してください\",\n excludeEmptyString: true,\n })\n .label(\"郵便番号\"),\n prefecture_id: yup.string().trim().required().label(\"都道府県\"),\n address1: yup.string().trim().required().label(\"市区町村\"),\n address2: yup.string().trim().required().label(\"番地\"),\n address3: yup.string().trim().ensure().label(\"建物名\"),\n phone_number: yup.string().trim().required().phoneNumber().label(\"電話番号\"),\n email: yup.string().trim().email().required().label(\"メールアドレス\"),\n industry_id: yup.string().trim().required().label(\"主業種\"),\n number_of_employees: yup\n .string()\n .trim()\n .required()\n .matches(/^(0|[1-9][0-9]*)$/, \"半角数字で入力してください\")\n .test(\n \"lessThanOrEqualToOneHundredMillion\",\n \"100,000,000以下の値を入力してください\",\n (value) => {\n if (value === undefined) return false;\n const num = parseInt(value);\n return !isNaN(num) && num <= 100000000;\n },\n )\n .label(\"従業員数\"),\n establishment_year: yup\n .number()\n .transform((value, originalValue) =>\n originalValue === \"\" ? undefined : value,\n )\n .required()\n .min(500)\n .max(new Date().getFullYear())\n .label(\"設立年\"),\n establishment_month: yup\n .number()\n .transform((value, originalValue) =>\n originalValue === \"\" ? undefined : value,\n )\n .required()\n .min(1)\n .max(12)\n .label(\"設立月\"),\n capital_ten_thousand_yen: yup\n .string()\n .trim()\n .matches(/^(0|[1-9][0-9]*)$/, \"半角数字で入力してください\")\n .required()\n .test(\n \"lessThanOrEqualToBillion\",\n \"1,000,000,000以下の値を入力してください\",\n (value) => {\n if (value === undefined) return false;\n const num = parseInt(value);\n return !isNaN(num) && num <= 1000000000;\n },\n )\n .label(\"資本金\"),\n president_name: yup.string().trim().required().label(\"代表者氏名\"),\n website_url: yup.string().ensure().trim().url().label(\"Webサイト\"),\n facebook_url: yup.string().trim().url().ensure().label(\"Facebook\"),\n instagram_url: yup.string().trim().url().ensure().label(\"Instagram\"),\n note_url: yup.string().trim().url().ensure().label(\"note\"),\n pinterest_url: yup.string().trim().url().ensure().label(\"Pinterest\"),\n threads_url: yup.string().trim().url().ensure().label(\"Threads\"),\n tiktok_url: yup.string().trim().url().ensure().label(\"TikTok\"),\n x_url: yup.string().trim().url().ensure().label(\"X(旧Twitter)\"),\n youtube_url: yup.string().trim().url().ensure().label(\"YouTube\"),\n});\n\nexport const step2Schema = yup.object({\n use_case: yup.string().required().label(\"利用用途\"),\n introduction: yup.string().required().max(1000).label(\"事業者紹介\"),\n used_architectural_software_ids: yup\n .array()\n .of(yup.string().required())\n .required()\n .label(\"利用している建築系ソフトウェア\"),\n main_client: yup.string().ensure().max(500).label(\"主な取引先\"),\n contractor_info_attributes: yup.lazy((_value, { parent }) => {\n if (parent.use_case === \"contractor\" || parent.use_case === \"both\") {\n return yup.object({\n desired_industry_ids: yup\n .array()\n .of(yup.string().required())\n .required()\n .min(1)\n .label(\"受注したい業種\"),\n supported_business_field_ids: yup\n .array()\n .of(yup.string().required())\n .required()\n .min(1)\n .label(\"対応できる分野\"),\n supported_business_service_note: yup\n .string()\n .max(1000)\n .required()\n .label(\"対応できる業務内容\"),\n contractor_info_supported_building_types_attributes: yup\n .array()\n .of(\n yup.object({\n building_type_id: yup.string().required().label(\"対応できる建物種別\"),\n structure_type_ids: yup\n .array()\n .of(yup.string().required())\n .required()\n .min(1)\n .label(\"対応できる構造種別\"),\n }),\n )\n .required()\n .min(1)\n .label(\"対応できる建物種別\"),\n supported_prefecture_ids: yup\n .array()\n .of(yup.string().required())\n .required()\n .min(1)\n .label(\"対応可能エリア\"),\n architectural_design_works_attributes: yup\n .array()\n .test(\n \"publishedLessThanThree\",\n `${max_architectural_design_works}つまでしか公開できません`,\n (value, { createError }) => {\n const published = value?.filter((v) => v.published) ?? [];\n\n if (published.length > max_architectural_design_works) {\n return createError({\n message: `公開できるのは${max_architectural_design_works}つまでです`,\n });\n } else {\n return true;\n }\n },\n )\n .of(\n yup.object({\n project_name: yup\n .string()\n .max(30)\n .required()\n .label(\"プロジェクト名\"),\n building_type_id: yup.string().required().label(\"建物種別\"),\n structure_type_ids: yup\n .array()\n .of(yup.string().required())\n .required()\n .min(1)\n .label(\"構造種別\"),\n project_detail: yup\n .string()\n .max(1000)\n .required()\n .label(\"業務内容\"),\n prefecture_id: yup.string().label(\"所在地\"),\n above_ground_floors: yup\n .string()\n .matches(/[1-9][0-9]*/, {\n excludeEmptyString: true,\n message: \"1以上の整数を入力してください\",\n })\n .ensure()\n .label(\"地上\"),\n underground_floors: yup\n .string()\n .matches(/[1-9][0-9]*/, {\n excludeEmptyString: true,\n message: \"1以上の整数を入力してください\",\n })\n .ensure()\n .label(\"地下\"),\n total_floor_area: yup\n .string()\n .matches(/[1-9][0-9]*/, {\n excludeEmptyString: true,\n message: \"1以上の整数を入力してください\",\n })\n .ensure()\n .label(\"延床面積\"),\n completion_year: yup\n .string()\n .matches(/[1-9][0-9]*/, {\n excludeEmptyString: true,\n message: \"1以上の整数を入力してください\",\n })\n .ensure()\n .label(\"竣工年\"),\n published: yup.boolean().required(),\n }),\n )\n .required()\n .min(1),\n });\n } else {\n return yup.mixed().nullable();\n }\n }),\n client_info_attributes: yup.lazy((_value, { parent }) => {\n if (parent.use_case === \"client\" || parent.use_case === \"both\") {\n return yup.object({\n desired_industry_ids: yup\n .array()\n .of(yup.string().required())\n .required()\n .min(1)\n .label(\"発注したい業務\"),\n desired_business_field_ids: yup\n .array()\n .of(yup.string().required())\n .required()\n .min(1)\n .label(\"発注したい分野\"),\n desired_business_service_note: yup\n .string()\n .max(1000)\n .required()\n .label(\"発注したい業務補足\"),\n });\n } else {\n return yup.mixed().nullable();\n }\n }),\n});\n","export default {\n agreement: false,\n use_case: \"\",\n introduction: \"\",\n used_architectural_software_ids: [],\n main_client: \"\",\n contractor_info_attributes: {\n desired_industry_ids: [],\n supported_business_field_ids: [],\n supported_business_service_note: \"\",\n contractor_info_supported_building_types_attributes: [],\n supported_prefecture_ids: [],\n architectural_design_works_attributes: [\n {\n project_name: \"\",\n building_type_id: \"\",\n structure_type_ids: [],\n project_detail: \"\",\n prefecture_id: \"\",\n above_ground_floors: \"\",\n underground_floors: \"\",\n total_floor_area: \"\",\n completion_year: \"\",\n published: true,\n },\n ],\n },\n client_info_attributes: {\n desired_industry_ids: [],\n desired_business_field_ids: [],\n desired_business_service_note: \"\",\n },\n};\n","import { yupResolver } from \"@hookform/resolvers/yup\";\nimport { useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport { step2Schema } from \"./schemas\";\nimport step2DefaultValue from \"./step2DefaultValue\";\n\nexport type Step2FormData = yup.InferType;\n\nexport const useStep2Form = ({ defaultValues = step2DefaultValue } = {}) => {\n return useForm({\n defaultValues: defaultValues,\n resolver: yupResolver(\n step2Schema.concat(\n yup.object({\n agreement: yup\n .bool()\n .is(\n [true],\n \"「利用規約」および「個人情報保護方針」に同意してください\",\n ),\n }),\n ),\n ),\n });\n};\n","import { Box, Container } from \"@chakra-ui/react\";\nimport React, { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { BrowserRouter, useLocation, useNavigate } from \"react-router-dom\";\nimport {\n businessMatchingOwnedBusinessEntitiesPath,\n businessMatchingRootPath,\n validateBusinessMatchingOwnedBusinessEntitiesPath,\n} from \"../../../../routes\";\nimport { Flash } from \"../../../shared/lib/types\";\nimport { uploadFiles } from \"../../../shared/lib/uploadFile\";\nimport { Header } from \"../../shared/components/atoms\";\nimport Background from \"../../shared/components/atoms/Background\";\nimport BackgroundBuildingImage from \"../../shared/components/atoms/BackgroundBuildingImage\";\nimport GoBackLink from \"../../shared/components/atoms/GoBackLink\";\nimport { useImageFileDropzoneState } from \"../../shared/components/atoms/ImageFileDropzone\";\nimport ShadowCard from \"../../shared/components/atoms/ShadowCard\";\nimport Application from \"../../shared/components/layouts/Application\";\nimport { Options, SharedApprovedCurrentUser } from \"../../shared/lib/types\";\nimport useRequest from \"../../shared/lib/useRequest\";\nimport BusinessEntityContentPreview from \"../shared/components/BusinessEntityContentPreview\";\nimport Confirmation from \"./components/Confirmation\";\nimport Step1 from \"./components/Step1\";\nimport Step2 from \"./components/Step2\";\nimport { handleValidateEmailResponse } from \"./lib/handleValidateEmailResponse\";\nimport { GroupedPrefectures, SharedArchitecturalSoftwares } from \"./lib/types\";\nimport { Step1FormData, useStep1Form } from \"./lib/useStep1Form\";\nimport { Step2FormData, useStep2Form } from \"./lib/useStep2Form\";\n\nconst businessTypes = [\n { value: \"company\", label: \"法人\" },\n { value: \"self_employment\", label: \"個人事業主\" },\n];\n\nconst OwnedBusinessEntitiesNew = ({\n flash,\n currentUser,\n currentUserIndustryId,\n prefectures,\n industries,\n businessFields,\n architecturalSoftwares,\n groupedPrefectures,\n buildingTypes,\n structureTypes,\n}: {\n flash: Flash;\n currentUser: SharedApprovedCurrentUser;\n currentUserIndustryId: number | null;\n prefectures: Options;\n industries: Options;\n businessFields: Options;\n architecturalSoftwares: SharedArchitecturalSoftwares;\n groupedPrefectures: GroupedPrefectures;\n buildingTypes: Options;\n structureTypes: Options;\n}) => {\n const step1Methods = useStep1Form({\n defaultValues: {\n business_type: \"\",\n name: \"\",\n name_kana: \"\",\n postal_code: \"\",\n prefecture_id: \"\",\n address1: \"\",\n address2: \"\",\n address3: \"\",\n phone_number: \"\",\n email: currentUser.email,\n industry_id: currentUserIndustryId\n ? currentUserIndustryId.toString()\n : \"\",\n number_of_employees: \"\",\n establishment_year: undefined,\n establishment_month: undefined,\n capital_ten_thousand_yen: \"\",\n president_name: \"\",\n website_url: \"\",\n facebook_url: \"\",\n instagram_url: \"\",\n note_url: \"\",\n pinterest_url: \"\",\n threads_url: \"\",\n tiktok_url: \"\",\n x_url: \"\",\n youtube_url: \"\",\n },\n });\n const step2Methods = useStep2Form();\n const {\n handleSubmit: handleFinalFormSubmit,\n formState: { isSubmitting: isFinalFormSubmitting },\n } = useForm();\n const logoImageFileFormRef = useRef(null);\n const logoImageFile = useImageFileDropzoneState({});\n const request = useRequest();\n const [formData, setFormData] = useState<{\n step1: Step1FormData | undefined;\n step2: Step2FormData | undefined;\n }>({ step1: undefined, step2: undefined });\n const [\n designedArchitecturalWorksImages,\n setDesignedArchitecturalWorksImages,\n ] = useState([[]]);\n\n const onBeforeUnload = useCallback((e: BeforeUnloadEvent) => {\n e.preventDefault();\n e.returnValue = \"\";\n }, []);\n\n const navigate = useNavigate();\n const location = useLocation();\n const step = location.hash;\n\n useEffect(() => {\n window.addEventListener(\"beforeunload\", onBeforeUnload);\n return () => window.removeEventListener(\"beforeunload\", onBeforeUnload);\n }, [onBeforeUnload]);\n\n const onStep2 = async (data: Step1FormData) => {\n if (!logoImageFile.validate()) return;\n if (!(await validateEmail(data))) {\n return;\n }\n setFormData((prev) => ({ ...prev, step1: data }));\n\n await navigate(\"#step2\");\n window.scrollTo(0, 0);\n };\n\n const onConfirm = async (data: Step2FormData) => {\n await navigate(\"#confirm\");\n setFormData((prev) => ({ ...prev, step2: data }));\n window.scrollTo(0, 0);\n };\n\n const validateEmail = async (data: Step1FormData) => {\n const res = await request(\n validateBusinessMatchingOwnedBusinessEntitiesPath(),\n \"POST\",\n { business_entity_info_application: { email: data.email } },\n );\n return await handleValidateEmailResponse({\n response: res,\n methods: {\n setError: step1Methods.setError,\n clearErrors: step1Methods.clearErrors,\n },\n });\n };\n const onSubmit = async () => {\n if (formData.step1 == null || formData.step2 == null) return;\n\n if (!(await validateEmail(formData.step1))) {\n return;\n }\n\n const { establishment_year, establishment_month, ...restData } =\n formData.step1;\n\n let results = null;\n if (logoImageFile.file != null) {\n results = await uploadFiles({ files: [logoImageFile.file!] });\n }\n const designedArchitecturalWorksImagesUploadResults = await Promise.all(\n designedArchitecturalWorksImages.map((files) => {\n return uploadFiles({ files });\n }),\n );\n\n const client_info_attributes = [\"client\", \"both\"].includes(\n formData.step2.use_case,\n )\n ? formData.step2.client_info_attributes\n : null;\n\n const contractor_info_attributes = [\"contractor\", \"both\"].includes(\n formData.step2.use_case,\n )\n ? {\n ...formData.step2.contractor_info_attributes,\n architectural_design_works_attributes:\n designedArchitecturalWorksImagesUploadResults.map(\n (result, index) => {\n if (\n formData.step2?.contractor_info_attributes &&\n \"architectural_design_works_attributes\" in\n formData.step2.contractor_info_attributes\n ) {\n return {\n ...formData.step2.contractor_info_attributes\n .architectural_design_works_attributes[index],\n images: result.map((r) => r.blob.signed_id),\n };\n }\n },\n ),\n }\n : null;\n\n const requestData = {\n first_business_entity_info_application: {\n ...restData,\n establishment_year_month: `${establishment_year}/${establishment_month}`,\n logo_image: results ? results[0].blob.signed_id : null,\n ...{\n ...formData.step2,\n contractor_info_attributes,\n client_info_attributes,\n },\n },\n };\n\n const res = await request(\n businessMatchingOwnedBusinessEntitiesPath(),\n \"POST\",\n requestData,\n );\n if (res.ok) {\n window.removeEventListener(\"beforeunload\", onBeforeUnload);\n const json = await res.json();\n window.location.href = json.redirect_url;\n }\n };\n\n return (\n \n \n \n \n \n \n \n {step === \"\" ? (\n \n 事業者一覧に戻る\n \n ) : step === \"#step2\" ? (\n navigate(-1)}>\n 事業者情報の入力に戻る\n \n ) : (\n <>>\n )}\n \n \n \n \n \n \n navigate(-1)}\n />\n \n {step === \"#confirm\" && (\n \n files.map((file) => URL.createObjectURL(file)),\n )}\n isSubmitting={isFinalFormSubmitting}\n onSubmit={handleFinalFormSubmit(onSubmit)}\n onClickFix={() => navigate(-1)}\n onClickPreview={() => {\n void navigate(\"#preview\");\n window.scrollTo(0, 0);\n }}\n />\n )}\n \n \n \n \n \n \n {step === \"#preview\" && (\n \n )}\n \n );\n};\n\nconst BrowserRouterWrapper = (props: {\n flash: Flash;\n currentUser: SharedApprovedCurrentUser;\n currentUserIndustryId: number | null;\n prefectures: Options;\n industries: Options;\n businessFields: Options;\n architecturalSoftwares: SharedArchitecturalSoftwares;\n groupedPrefectures: GroupedPrefectures;\n buildingTypes: Options;\n structureTypes: Options;\n}) => {\n return (\n \n \n \n );\n};\n\nexport default BrowserRouterWrapper;\n","import { yupResolver } from \"@hookform/resolvers/yup\";\nimport { useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport { step1Schema } from \"./schemas\";\n\nexport type Step1FormData = yup.InferType;\n\ntype BusinessEntityFormDefaultValues = Omit<\n yup.InferType,\n \"establishment_year\" | \"establishment_month\"\n> & {\n establishment_year: number | undefined;\n establishment_month: number | undefined;\n};\n\nexport const useStep1Form = ({\n defaultValues,\n}: {\n defaultValues: BusinessEntityFormDefaultValues;\n}) => {\n const form = useForm({\n defaultValues: defaultValues,\n resolver: yupResolver(step1Schema),\n });\n\n return form;\n};\n","import React from \"react\";\nimport { Icon } from \"@chakra-ui/react\";\nimport { IconProps } from \"@chakra-ui/react\";\n\nconst MarkEmailUnreadIcon = (props: IconProps) => (\n \n \n \n);\n\nexport default MarkEmailUnreadIcon;\n","import React, { ReactNode } from \"react\";\nimport Select, {\n OptionProps,\n GroupBase,\n DropdownIndicatorProps,\n Props,\n MultiValueGenericProps,\n MenuListProps,\n} from \"react-select\";\nimport { components } from \"react-select\";\nimport ExpandMoreIcon from \"../../../shared/components/icons/ExpandMoreIcon\";\nimport { StylesConfig } from \"react-select\";\nimport Checkbox from \"../../../shared/components/atoms/form/Checkbox\";\nimport { Box, HStack } from \"@chakra-ui/react\";\n\nexport type OptionType = {\n value: string;\n label: string;\n sublabel?: string;\n};\n\nexport type MultiSelectProps = Props<\n OptionType,\n true,\n GroupBase\n> & { isInvalid?: boolean };\n\nconst CheckboxOption = (props: OptionProps) => {\n const { innerProps, isSelected, label } = props;\n\n return (\n \n \n e.stopPropagation() }}\n >\n {label}\n \n \n \n );\n};\n\nconst CustomDropdownIndicator = (\n props: DropdownIndicatorProps,\n) => {\n return (\n \n \n \n );\n};\n\nconst MultiValueLabel = (props: MultiValueGenericProps) => {\n return (\n \n \n {props.data.sublabel && (\n \n {props.data.sublabel}\n \n )}\n \n );\n};\n\nconst createMenuList = (menuListContent: ReactNode) => {\n return function MenuList(props: MenuListProps) {\n return (\n \n {menuListContent}\n {props.children}\n \n );\n };\n};\n\nconst customStyles: StylesConfig = {\n control: (provided, { selectProps }) => ({\n ...provided,\n borderColor: selectProps[\"aria-invalid\"] ? \"#CD4429\" : \"#99A9B0\",\n minHeight: \"2.5rem\",\n borderRadius: \"2px\",\n }),\n valueContainer: (provided, state) => ({\n ...provided,\n padding: state.hasValue ? \"0.5rem 0.75rem 0.5rem 0.75rem\" : \"0 0 0 0.75rem\",\n }),\n multiValue: (provided) => ({\n ...provided,\n margin: \"0.25rem 0.75rem 0.25rem 0\",\n backgroundColor: \"#F5F5F5\",\n borderRadius: \"100px\",\n }),\n multiValueLabel: (provided) => ({\n ...provided,\n padding: \"0.25rem 0.75rem\",\n paddingLeft: \"none\",\n fontSize: \"0.875rem\",\n }),\n indicatorsContainer: (provided) => ({\n ...provided,\n position: \"absolute\",\n right: \"0.25rem\",\n top: \"0.125rem\",\n }),\n indicatorSeparator: (provided) => ({\n ...provided,\n display: \"none\",\n }),\n clearIndicator: (provided) => ({\n ...provided,\n display: \"none\",\n }),\n placeholder: (provided) => {\n return {\n ...provided,\n color: \"#212529\",\n };\n },\n multiValueRemove: (provided, { selectProps }) => ({\n ...provided,\n paddingLeft: \"0.5rem\",\n paddingRight: \"0.5rem\",\n display: selectProps.isDisabled ? \"none\" : \"flex\",\n }),\n menuList: (provided) => ({\n ...provided,\n padding: \"1rem 1.25rem\",\n maxHeight: \"320px\"\n }),\n groupHeading: (provided) => ({\n ...provided,\n fontSize: \"0.75rem\",\n color: \"#212529\",\n borderBottom: \"1px solid #C4CCCA\",\n paddingBottom: \"0.25rem\",\n paddingLeft: 0,\n marginBottom: \"0.5rem\",\n }),\n group: (provided) => ({\n ...provided,\n marginBottom: \"1rem\",\n paddingBottom: 0,\n }),\n option: (provided) => ({\n ...provided,\n padding: \"0.25rem 0\",\n marginBottom: \"0.25rem\",\n }),\n};\n\nconst MultiSelect = ({\n menuListContent,\n isInvalid,\n ...props\n}: { menuListContent?: ReactNode } & MultiSelectProps) => {\n return (\n