| @@ -17,10 +17,30 @@ export type AppReceiptIssuingOrder = { | |||
| status_order_mail_datetime?: string; | |||
| status_mail_post_date?: string; | |||
| accept_privacy_policy?: boolean; | |||
| accept_correct_entry?: boolean; | |||
| accept_site_policy?: boolean; | |||
| updated_at: string; | |||
| }; | |||
| // ポリシーへの同意 ------------------------------- | |||
| export type AcceptPolicies = { | |||
| access_token: string; | |||
| accept_privacy_policy: boolean; | |||
| accept_correct_entry: boolean; | |||
| accept_site_policy: boolean; | |||
| } & TimestampRequest; | |||
| export const acceptPolicies = async (data: AcceptPolicies) => { | |||
| const res = await request({ | |||
| url: getUrl(ApiId.RECEIPT_ISSUING_ORDER_ACCEPT_POLICIES), | |||
| method: HttpMethod.POST, | |||
| data: makeParam(data), | |||
| }); | |||
| return res; | |||
| }; | |||
| // 領収証確定 | |||
| // 領収証確定 ------------------------------- | |||
| export type ConfirmRequest = { | |||
| access_token: string; | |||
| receipt_name: string; | |||
| @@ -16,6 +16,7 @@ export const ApiId = { | |||
| // APP向け------------------------------------ | |||
| APP_TOKEN_CHECK: id++, | |||
| RECEIPT_ISSUING_ORDER_ACCEPT_POLICIES: id++, | |||
| RECEIPT_ISSUING_ORDER_CONFIRM: id++, | |||
| DOWNLOAD_RECEIPT: id++, | |||
| RECEIPT_ISSUING_ORDER_MAIL_ORDER: id++, | |||
| @@ -9,6 +9,8 @@ const urls = { | |||
| [A.CHANGE_CONTRACT]: "change-contract", | |||
| [A.APP_TOKEN_CHECK]: "app-token-check", | |||
| [A.RECEIPT_ISSUING_ORDER_ACCEPT_POLICIES]: | |||
| "receipt-issuing-order/accept/policies", | |||
| [A.RECEIPT_ISSUING_ORDER_CONFIRM]: "receipt-issuing-order/confirm", | |||
| [A.DOWNLOAD_RECEIPT]: "receipt/download", | |||
| [A.RECEIPT_ISSUING_ORDER_MAIL_ORDER]: "receipt-issuing-order/mail-order", | |||
| @@ -9,6 +9,7 @@ export const PageID = { | |||
| APP_RECEIPT_ISSUING_ORDER_INDEX: id++, | |||
| APP_RECEIPT_ISSUING_ORDER_MAIL_ORDER: id++, | |||
| APP_RECEIPT_ISSUING_ORDER_EMAIL_ORDER: id++, | |||
| APP_PRIVACY_POLICY: id++, | |||
| DASHBOARD_OVERVIEW: id++, | |||
| @@ -1,29 +1,19 @@ | |||
| import { | |||
| Box, | |||
| Button, | |||
| Paper, | |||
| Stack, | |||
| Table, | |||
| TableBody, | |||
| TableCell, | |||
| TableHead, | |||
| TableRow, | |||
| TextField, | |||
| Typography, | |||
| styled, | |||
| } from "@mui/material"; | |||
| import { HasChildren } from "@types"; | |||
| import { ApiId } from "api"; | |||
| import { confirm } from "api/app/receipt-issuing-order"; | |||
| import { getFullUrl } from "api/url"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| import useApp from "hooks/useApp"; | |||
| import { useDialog } from "hooks/useDialog"; | |||
| import useSnackbarCustom from "hooks/useSnackbarCustom"; | |||
| import React, { ReactNode, useEffect, useMemo, useRef, useState } from "react"; | |||
| import { useParams } from "react-router-dom"; | |||
| import { useMemo } from "react"; | |||
| import { sprintf } from "sprintf-js"; | |||
| import { formatDateStr, formatDateTimeStr } from "utils/datetime"; | |||
| import useAcceptPolicies from "./hooks/useAcceptPolicies"; | |||
| import useReceiptIssuingOrderConfirm from "./hooks/useReceiptIssuingOrderConfirm"; | |||
| import useReceiptIssuingOrderSelectHowToGet from "./hooks/useReceiptIssuingOrderSelectHowToGet"; | |||
| @@ -64,8 +54,18 @@ const Section = ({ title, subtitle, children }: SectionProps) => { | |||
| export default function ReceiptIssuingOrder() { | |||
| const { tokenResult, receiptIssuingOrder: order } = useApp(); | |||
| const acceptedPolicies = useMemo(() => { | |||
| if (!order) return false; | |||
| return ( | |||
| !!order.accept_privacy_policy && | |||
| !!order.accept_correct_entry && | |||
| !!order.accept_site_policy | |||
| ); | |||
| }, [order]); | |||
| const confirm = useReceiptIssuingOrderConfirm(); | |||
| const selectHowToGet = useReceiptIssuingOrderSelectHowToGet(); | |||
| const acceptPolicies = useAcceptPolicies(); | |||
| const tokenExpiresAtStr = useMemo(() => { | |||
| if (order) { | |||
| @@ -89,6 +89,9 @@ export default function ReceiptIssuingOrder() { | |||
| return <Box>不正なアクセス</Box>; | |||
| } | |||
| if (acceptedPolicies === false) { | |||
| } | |||
| return ( | |||
| <> | |||
| <Box sx={{ p: 3, pt: 5, mx: "auto", maxWidth: 500 }} textAlign="center"> | |||
| @@ -131,7 +134,13 @@ export default function ReceiptIssuingOrder() { | |||
| </Table> | |||
| </Section> | |||
| {order?.status_order_mail_datetime && ( | |||
| {acceptedPolicies === false && ( | |||
| <Section title="各種ポリシーへの同意確認"> | |||
| {acceptPolicies.element} | |||
| </Section> | |||
| )} | |||
| {!!acceptedPolicies && order?.status_order_mail_datetime && ( | |||
| <Section title="郵送依頼状況"> | |||
| <Table sx={{ borderBottom: "none" }}> | |||
| <TableBody> | |||
| @@ -142,11 +151,11 @@ export default function ReceiptIssuingOrder() { | |||
| </Section> | |||
| )} | |||
| {confirm.shouldShow && ( | |||
| {!!acceptedPolicies && confirm.shouldShow && ( | |||
| <Section title="宛名入力">{confirm.element}</Section> | |||
| )} | |||
| {selectHowToGet.shouldShow && ( | |||
| {!!acceptedPolicies && selectHowToGet.shouldShow && ( | |||
| <Section title="領収証取得方法選択"> | |||
| {selectHowToGet.element} | |||
| </Section> | |||
| @@ -0,0 +1,158 @@ | |||
| import { yupResolver } from "@hookform/resolvers/yup"; | |||
| import { Box, Button, Stack, Typography } from "@mui/material"; | |||
| import { acceptPolicies, confirm } from "api/app/receipt-issuing-order"; | |||
| import { PageID } from "codes/page"; | |||
| import { FormProvider, RHFCheckbox, RHFTextField } from "components/hook-form"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| import useApp from "hooks/useApp"; | |||
| import useBackDrop from "hooks/useBackDrop"; | |||
| import { useDialog } from "hooks/useDialog"; | |||
| import useSnackbarCustom from "hooks/useSnackbarCustom"; | |||
| import { useEffect, useMemo } from "react"; | |||
| import { useForm } from "react-hook-form"; | |||
| import { getPath } from "routes/path"; | |||
| import { sprintf } from "sprintf-js"; | |||
| import { scrollToTop } from "utils/page"; | |||
| import * as Yup from "yup"; | |||
| type FormProps = { | |||
| accept_privacy_policy: boolean; | |||
| accept_correct_entry: boolean; | |||
| accept_site_policy: boolean; | |||
| }; | |||
| const schema = Yup.object().shape({ | |||
| accept_privacy_policy: Yup.boolean().test( | |||
| "accept", | |||
| "同意が必要です", | |||
| function (value) { | |||
| return !!value; | |||
| } | |||
| ), | |||
| accept_correct_entry: Yup.boolean().test( | |||
| "accept", | |||
| "同意が必要です", | |||
| function (value) { | |||
| return !!value; | |||
| } | |||
| ), | |||
| accept_site_policy: Yup.boolean().test( | |||
| "accept", | |||
| "同意が必要です", | |||
| function (value) { | |||
| return !!value; | |||
| } | |||
| ), | |||
| }); | |||
| export default function useAcceptPolicies() { | |||
| const { receiptIssuingOrder: order, fetch, token } = useApp(); | |||
| const { success, error } = useSnackbarCustom(); | |||
| const form = useForm<FormProps>({ | |||
| defaultValues: { | |||
| accept_privacy_policy: false, | |||
| accept_correct_entry: false, | |||
| accept_site_policy: false, | |||
| }, | |||
| resolver: yupResolver(schema), | |||
| }); | |||
| const acceptPrivacyPolicy = form.watch("accept_privacy_policy"); | |||
| const acceptCorrectEntry = form.watch("accept_correct_entry"); | |||
| const acceptSitePolicy = form.watch("accept_site_policy"); | |||
| const canSubmit = useMemo(() => { | |||
| return acceptPrivacyPolicy && acceptCorrectEntry && acceptSitePolicy; | |||
| }, [acceptPrivacyPolicy, acceptCorrectEntry, acceptSitePolicy]); | |||
| const shouldShow = useMemo(() => { | |||
| return !order?.confirmed; | |||
| }, [order]); | |||
| const { callAPI } = useAPICall({ | |||
| apiMethod: acceptPolicies, | |||
| backDrop: true, | |||
| onSuccess: () => { | |||
| fetch(); | |||
| scrollToTop(); | |||
| }, | |||
| onFailed: () => { | |||
| error("失敗しました"); | |||
| }, | |||
| }); | |||
| const privacyPolicyUrl: string = useMemo(() => { | |||
| return getPath(PageID.APP_PRIVACY_POLICY); | |||
| }, []); | |||
| const handleSubmit = () => { | |||
| if (!order) return; | |||
| callAPI({ | |||
| ...form.getValues(), | |||
| access_token: token, | |||
| timestamp: order.updated_at, | |||
| }); | |||
| }; | |||
| const element = ( | |||
| <FormProvider | |||
| methods={form} | |||
| onSubmit={form.handleSubmit(handleSubmit, (e) => { | |||
| console.log({ e }); | |||
| })} | |||
| > | |||
| <Stack spacing={1}> | |||
| <Box sx={{ px: 2 }}> | |||
| <Stack | |||
| direction="row" | |||
| spacing={0} | |||
| alignItems="center" | |||
| justifyContent="center" | |||
| > | |||
| <RHFCheckbox label={null} name="accept_privacy_policy" /> | |||
| <Typography variant="body1"> | |||
| <a href={privacyPolicyUrl} target="_blank"> | |||
| 個人情報保護方針 | |||
| </a> | |||
| への同意 | |||
| </Typography> | |||
| </Stack> | |||
| </Box> | |||
| <Box sx={{ px: 2 }}> | |||
| <RHFCheckbox label="注意事項の確認" name="accept_site_policy" /> | |||
| <Typography variant="body1"><注意事項></Typography> | |||
| <Typography variant="body2"> | |||
| ・申込内容に誤りがある場合は、恐れ入りますが改めて発行依頼申込を行ってください。 | |||
| </Typography> | |||
| <Typography variant="body2"> | |||
| ・WEB上で領収証PDFをダウンロード可能です。 | |||
| </Typography> | |||
| <Typography variant="body2"> | |||
| ・郵送を希望する場合、到着までに時間を要します。お急ぎの場合はダウンロードやEmail送付をご利用ください。 | |||
| </Typography> | |||
| </Box> | |||
| <Box sx={{ px: 2 }}> | |||
| <RHFCheckbox | |||
| label={ | |||
| <Typography variant="body1" color="error"> | |||
| 申請内容に虚偽・誤りが無いことへの同意 | |||
| </Typography> | |||
| } | |||
| name="accept_correct_entry" | |||
| /> | |||
| </Box> | |||
| <Box> | |||
| <Button type="submit" variant="contained" disabled={!canSubmit}> | |||
| 登録 | |||
| </Button> | |||
| </Box> | |||
| </Stack> | |||
| </FormProvider> | |||
| ); | |||
| return { | |||
| element, | |||
| shouldShow, | |||
| }; | |||
| } | |||
| @@ -0,0 +1,22 @@ | |||
| import { Box, Stack, Typography } from "@mui/material"; | |||
| export default function PrivacyPolicy() { | |||
| return ( | |||
| <Box sx={{ p: 3, pt: 5, mx: "auto" }} textAlign="center"> | |||
| <Stack spacing={3}> | |||
| <Box> | |||
| <Typography variant="h5">個人情報保護方針</Typography> | |||
| </Box> | |||
| <Box> | |||
| <Box sx={{ m: 1, p: 1, backgroundColor: "#D1E0E4" }}> | |||
| <Typography variant="h6">1.個人譲歩の取得/利用/提供</Typography> | |||
| </Box> | |||
| <Typography variant="body2"> | |||
| お客さまの個人情報の取得は、適法かつ公正な手段によってこれを行い、個人情報の利用目的等の必要事項について取得時に明示するか、当社ウェブサイト等にて告知します。適正な手続きのもと、利用目的の範囲で個人情報の取り扱いをいたします。 | |||
| また、個人情報を第三者に提供・開示等する場合は、法令の定める手続きに則って行います。 | |||
| </Typography> | |||
| </Box> | |||
| </Stack> | |||
| </Box> | |||
| ); | |||
| } | |||
| @@ -1,5 +1,4 @@ | |||
| import { PageID } from "codes/page"; | |||
| import { UserRole } from "codes/user"; | |||
| import LoadingScreen from "components/LoadingScreen"; | |||
| import { AppContextProvider } from "contexts/AppContext"; | |||
| import useAuth from "hooks/useAuth"; | |||
| @@ -17,6 +16,16 @@ const Loadable = (Component: ElementType) => (props: any) => { | |||
| ); | |||
| }; | |||
| const CommonRoutes = (): RouteObject => ({ | |||
| element: <SimpleLayout />, | |||
| children: [ | |||
| { | |||
| path: getRoute(PageID.APP_PRIVACY_POLICY), | |||
| element: <PrivacyPolicy />, | |||
| }, | |||
| ], | |||
| }); | |||
| const AuthRoutes = (): RouteObject => ({ | |||
| element: <SimpleLayout />, | |||
| children: [ | |||
| @@ -133,6 +142,7 @@ const DashboardRoutes = (): RouteObject => { | |||
| export function Routes() { | |||
| const { initialized } = useAuth(); | |||
| return useRoutes([ | |||
| CommonRoutes(), | |||
| AuthRoutes(), | |||
| AppRoutes(), | |||
| DashboardRoutes(), | |||
| @@ -214,5 +224,9 @@ const ChangePassword = Loadable( | |||
| ); | |||
| // その他 --------------------------------- | |||
| const PrivacyPolicy = Loadable( | |||
| lazy(() => import("pages/common/PrivacyPolicy")) | |||
| ); | |||
| const Page403 = Loadable(lazy(() => import("pages/common/Page403"))); | |||
| const Page404 = Loadable(lazy(() => import("pages/common/Page404"))); | |||
| @@ -45,6 +45,7 @@ const PATHS = { | |||
| "/app/receipt-issuing-order/mail", | |||
| [makePathKey(PageID.APP_RECEIPT_ISSUING_ORDER_EMAIL_ORDER)]: | |||
| "/app/receipt-issuing-order/email", | |||
| [makePathKey(PageID.APP_PRIVACY_POLICY)]: "/app/privacy-policy", | |||
| // 契約関連 | |||
| [makePathKey(PageID.DASHBOARD_CONTRACT_LIST)]: | |||
| @@ -0,0 +1,3 @@ | |||
| export const scrollToTop = () => { | |||
| window.scroll({ top: 0, behavior: "smooth" }); | |||
| }; | |||