| @@ -17,10 +17,30 @@ export type AppReceiptIssuingOrder = { | |||||
| status_order_mail_datetime?: string; | status_order_mail_datetime?: string; | ||||
| status_mail_post_date?: string; | status_mail_post_date?: string; | ||||
| accept_privacy_policy?: boolean; | |||||
| accept_correct_entry?: boolean; | |||||
| accept_site_policy?: boolean; | |||||
| updated_at: string; | 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 = { | export type ConfirmRequest = { | ||||
| access_token: string; | access_token: string; | ||||
| receipt_name: string; | receipt_name: string; | ||||
| @@ -16,6 +16,7 @@ export const ApiId = { | |||||
| // APP向け------------------------------------ | // APP向け------------------------------------ | ||||
| APP_TOKEN_CHECK: id++, | APP_TOKEN_CHECK: id++, | ||||
| RECEIPT_ISSUING_ORDER_ACCEPT_POLICIES: id++, | |||||
| RECEIPT_ISSUING_ORDER_CONFIRM: id++, | RECEIPT_ISSUING_ORDER_CONFIRM: id++, | ||||
| DOWNLOAD_RECEIPT: id++, | DOWNLOAD_RECEIPT: id++, | ||||
| RECEIPT_ISSUING_ORDER_MAIL_ORDER: id++, | RECEIPT_ISSUING_ORDER_MAIL_ORDER: id++, | ||||
| @@ -9,6 +9,8 @@ const urls = { | |||||
| [A.CHANGE_CONTRACT]: "change-contract", | [A.CHANGE_CONTRACT]: "change-contract", | ||||
| [A.APP_TOKEN_CHECK]: "app-token-check", | [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.RECEIPT_ISSUING_ORDER_CONFIRM]: "receipt-issuing-order/confirm", | ||||
| [A.DOWNLOAD_RECEIPT]: "receipt/download", | [A.DOWNLOAD_RECEIPT]: "receipt/download", | ||||
| [A.RECEIPT_ISSUING_ORDER_MAIL_ORDER]: "receipt-issuing-order/mail-order", | [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_INDEX: id++, | ||||
| APP_RECEIPT_ISSUING_ORDER_MAIL_ORDER: id++, | APP_RECEIPT_ISSUING_ORDER_MAIL_ORDER: id++, | ||||
| APP_RECEIPT_ISSUING_ORDER_EMAIL_ORDER: id++, | APP_RECEIPT_ISSUING_ORDER_EMAIL_ORDER: id++, | ||||
| APP_PRIVACY_POLICY: id++, | |||||
| DASHBOARD_OVERVIEW: id++, | DASHBOARD_OVERVIEW: id++, | ||||
| @@ -1,29 +1,19 @@ | |||||
| import { | import { | ||||
| Box, | Box, | ||||
| Button, | |||||
| Paper, | Paper, | ||||
| Stack, | Stack, | ||||
| Table, | Table, | ||||
| TableBody, | TableBody, | ||||
| TableCell, | TableCell, | ||||
| TableHead, | |||||
| TableRow, | TableRow, | ||||
| TextField, | |||||
| Typography, | Typography, | ||||
| styled, | |||||
| } from "@mui/material"; | } from "@mui/material"; | ||||
| import { HasChildren } from "@types"; | 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 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 { sprintf } from "sprintf-js"; | ||||
| import { formatDateStr, formatDateTimeStr } from "utils/datetime"; | import { formatDateStr, formatDateTimeStr } from "utils/datetime"; | ||||
| import useAcceptPolicies from "./hooks/useAcceptPolicies"; | |||||
| import useReceiptIssuingOrderConfirm from "./hooks/useReceiptIssuingOrderConfirm"; | import useReceiptIssuingOrderConfirm from "./hooks/useReceiptIssuingOrderConfirm"; | ||||
| import useReceiptIssuingOrderSelectHowToGet from "./hooks/useReceiptIssuingOrderSelectHowToGet"; | import useReceiptIssuingOrderSelectHowToGet from "./hooks/useReceiptIssuingOrderSelectHowToGet"; | ||||
| @@ -64,8 +54,18 @@ const Section = ({ title, subtitle, children }: SectionProps) => { | |||||
| export default function ReceiptIssuingOrder() { | export default function ReceiptIssuingOrder() { | ||||
| const { tokenResult, receiptIssuingOrder: order } = useApp(); | 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 confirm = useReceiptIssuingOrderConfirm(); | ||||
| const selectHowToGet = useReceiptIssuingOrderSelectHowToGet(); | const selectHowToGet = useReceiptIssuingOrderSelectHowToGet(); | ||||
| const acceptPolicies = useAcceptPolicies(); | |||||
| const tokenExpiresAtStr = useMemo(() => { | const tokenExpiresAtStr = useMemo(() => { | ||||
| if (order) { | if (order) { | ||||
| @@ -89,6 +89,9 @@ export default function ReceiptIssuingOrder() { | |||||
| return <Box>不正なアクセス</Box>; | return <Box>不正なアクセス</Box>; | ||||
| } | } | ||||
| if (acceptedPolicies === false) { | |||||
| } | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <Box sx={{ p: 3, pt: 5, mx: "auto", maxWidth: 500 }} textAlign="center"> | <Box sx={{ p: 3, pt: 5, mx: "auto", maxWidth: 500 }} textAlign="center"> | ||||
| @@ -131,7 +134,13 @@ export default function ReceiptIssuingOrder() { | |||||
| </Table> | </Table> | ||||
| </Section> | </Section> | ||||
| {order?.status_order_mail_datetime && ( | |||||
| {acceptedPolicies === false && ( | |||||
| <Section title="各種ポリシーへの同意確認"> | |||||
| {acceptPolicies.element} | |||||
| </Section> | |||||
| )} | |||||
| {!!acceptedPolicies && order?.status_order_mail_datetime && ( | |||||
| <Section title="郵送依頼状況"> | <Section title="郵送依頼状況"> | ||||
| <Table sx={{ borderBottom: "none" }}> | <Table sx={{ borderBottom: "none" }}> | ||||
| <TableBody> | <TableBody> | ||||
| @@ -142,11 +151,11 @@ export default function ReceiptIssuingOrder() { | |||||
| </Section> | </Section> | ||||
| )} | )} | ||||
| {confirm.shouldShow && ( | |||||
| {!!acceptedPolicies && confirm.shouldShow && ( | |||||
| <Section title="宛名入力">{confirm.element}</Section> | <Section title="宛名入力">{confirm.element}</Section> | ||||
| )} | )} | ||||
| {selectHowToGet.shouldShow && ( | |||||
| {!!acceptedPolicies && selectHowToGet.shouldShow && ( | |||||
| <Section title="領収証取得方法選択"> | <Section title="領収証取得方法選択"> | ||||
| {selectHowToGet.element} | {selectHowToGet.element} | ||||
| </Section> | </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 { PageID } from "codes/page"; | ||||
| import { UserRole } from "codes/user"; | |||||
| import LoadingScreen from "components/LoadingScreen"; | import LoadingScreen from "components/LoadingScreen"; | ||||
| import { AppContextProvider } from "contexts/AppContext"; | import { AppContextProvider } from "contexts/AppContext"; | ||||
| import useAuth from "hooks/useAuth"; | 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 => ({ | const AuthRoutes = (): RouteObject => ({ | ||||
| element: <SimpleLayout />, | element: <SimpleLayout />, | ||||
| children: [ | children: [ | ||||
| @@ -133,6 +142,7 @@ const DashboardRoutes = (): RouteObject => { | |||||
| export function Routes() { | export function Routes() { | ||||
| const { initialized } = useAuth(); | const { initialized } = useAuth(); | ||||
| return useRoutes([ | return useRoutes([ | ||||
| CommonRoutes(), | |||||
| AuthRoutes(), | AuthRoutes(), | ||||
| AppRoutes(), | AppRoutes(), | ||||
| DashboardRoutes(), | 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 Page403 = Loadable(lazy(() => import("pages/common/Page403"))); | ||||
| const Page404 = Loadable(lazy(() => import("pages/common/Page404"))); | const Page404 = Loadable(lazy(() => import("pages/common/Page404"))); | ||||
| @@ -45,6 +45,7 @@ const PATHS = { | |||||
| "/app/receipt-issuing-order/mail", | "/app/receipt-issuing-order/mail", | ||||
| [makePathKey(PageID.APP_RECEIPT_ISSUING_ORDER_EMAIL_ORDER)]: | [makePathKey(PageID.APP_RECEIPT_ISSUING_ORDER_EMAIL_ORDER)]: | ||||
| "/app/receipt-issuing-order/email", | "/app/receipt-issuing-order/email", | ||||
| [makePathKey(PageID.APP_PRIVACY_POLICY)]: "/app/privacy-policy", | |||||
| // 契約関連 | // 契約関連 | ||||
| [makePathKey(PageID.DASHBOARD_CONTRACT_LIST)]: | [makePathKey(PageID.DASHBOARD_CONTRACT_LIST)]: | ||||
| @@ -0,0 +1,3 @@ | |||||
| export const scrollToTop = () => { | |||||
| window.scroll({ top: 0, behavior: "smooth" }); | |||||
| }; | |||||