| @@ -1,4 +1,4 @@ | |||||
| import { APICommonResponse, ApiId, HttpMethod, request } from "."; | |||||
| import { APICommonResponse, ApiId, HttpMethod, makeParam, request } from "."; | |||||
| import { getUrl } from "./url"; | import { getUrl } from "./url"; | ||||
| export type Me = { | export type Me = { | ||||
| @@ -41,3 +41,34 @@ export const logout = async () => { | |||||
| }); | }); | ||||
| return res; | return res; | ||||
| }; | }; | ||||
| // -------パスワード設定開始--------------- | |||||
| export type StartPasswordSettingRequest = { | |||||
| email: string; | |||||
| }; | |||||
| export const startPasswordSetting = async ( | |||||
| param: StartPasswordSettingRequest | |||||
| ) => { | |||||
| const res = await request({ | |||||
| url: getUrl(ApiId.PASSWORD_SETTING_START), | |||||
| method: HttpMethod.POST, | |||||
| data: makeParam(param), | |||||
| }); | |||||
| return res; | |||||
| }; | |||||
| // -------パスワード設定認証--------------- | |||||
| export type VerifyPasswordSettingRequest = { | |||||
| token: string; | |||||
| password: string; | |||||
| }; | |||||
| export const verifyPasswordSetting = async ( | |||||
| param: VerifyPasswordSettingRequest | |||||
| ) => { | |||||
| const res = await request({ | |||||
| url: getUrl(ApiId.PASSWORD_SETTING_VERIFY), | |||||
| method: HttpMethod.POST, | |||||
| data: makeParam(param), | |||||
| }); | |||||
| return res; | |||||
| }; | |||||
| @@ -14,6 +14,9 @@ export const ApiId = { | |||||
| LOGIN: id++, | LOGIN: id++, | ||||
| LOGOUT: id++, | LOGOUT: id++, | ||||
| PASSWORD_SETTING_START: id++, | |||||
| PASSWORD_SETTING_VERIFY: id++, | |||||
| SEASON_TICKET_CONTRACTS: id++, | SEASON_TICKET_CONTRACTS: id++, | ||||
| PAYMENT_PLANS: id++, | PAYMENT_PLANS: id++, | ||||
| @@ -6,6 +6,8 @@ const urls = { | |||||
| [A.ME]: "me", | [A.ME]: "me", | ||||
| [A.LOGIN]: "login", | [A.LOGIN]: "login", | ||||
| [A.LOGOUT]: "logout", | [A.LOGOUT]: "logout", | ||||
| [A.PASSWORD_SETTING_START]: "password/setting/start", | |||||
| [A.PASSWORD_SETTING_VERIFY]: "password/setting/verify", | |||||
| [A.SEASON_TICKET_CONTRACTS]: "season-ticket-contracts", | [A.SEASON_TICKET_CONTRACTS]: "season-ticket-contracts", | ||||
| [A.PAYMENT_PLANS]: "season-ticket-contract/payment-plans", | [A.PAYMENT_PLANS]: "season-ticket-contract/payment-plans", | ||||
| [A.STICKER_RE_ORDER]: "season-ticket-contract/sticker-re-order", | [A.STICKER_RE_ORDER]: "season-ticket-contract/sticker-re-order", | ||||
| @@ -73,7 +73,14 @@ export default function Login() { | |||||
| <LoadingButton loading={sending} type="submit" variant="contained"> | <LoadingButton loading={sending} type="submit" variant="contained"> | ||||
| ログイン | ログイン | ||||
| </LoadingButton> | </LoadingButton> | ||||
| <Button variant="outlined">パスワードを忘れた</Button> | |||||
| <Button | |||||
| variant="outlined" | |||||
| onClick={() => { | |||||
| navigateWhenChanged(getPath(PageID.USER_SETTING_PASSWORD_START)); | |||||
| }} | |||||
| > | |||||
| パスワードを忘れた/設定したい | |||||
| </Button> | |||||
| </Stack> | </Stack> | ||||
| </Box> | </Box> | ||||
| </FormProvider> | </FormProvider> | ||||
| @@ -0,0 +1,120 @@ | |||||
| import { yupResolver } from "@hookform/resolvers/yup"; | |||||
| import { Box, Button, Stack, Typography } from "@mui/material"; | |||||
| import { HasChildren } from "@types"; | |||||
| import { startPasswordSetting } from "api/auth"; | |||||
| import RequireChip from "components/chip/RequireChip"; | |||||
| import { FormProvider, RHFTextField } from "components/hook-form"; | |||||
| import StackRow from "components/stack/StackRow"; | |||||
| import useAPICall from "hooks/useAPICall"; | |||||
| import useNavigateCustom from "hooks/useNavigateCustom"; | |||||
| import { PageID } from "pages"; | |||||
| import { useState } from "react"; | |||||
| import { useForm } from "react-hook-form"; | |||||
| import { getPath } from "routes/path"; | |||||
| import { object as YupObject, string as YupString } from "yup"; | |||||
| type AreaBoxProps = { | |||||
| label: string; | |||||
| require?: boolean; | |||||
| } & HasChildren; | |||||
| function AreaBox({ label, children, require }: AreaBoxProps) { | |||||
| return ( | |||||
| <Box> | |||||
| <StackRow> | |||||
| <Typography variant="subtitle1">〇{label}</Typography> | |||||
| <RequireChip require={require ?? false} /> | |||||
| </StackRow> | |||||
| {children} | |||||
| </Box> | |||||
| ); | |||||
| } | |||||
| type FormProps = { | |||||
| email: string; | |||||
| retype_email: string; | |||||
| }; | |||||
| export default function PasswordSettingStart() { | |||||
| const { navigateWhenChanged } = useNavigateCustom(); | |||||
| const [done, setDone] = useState<boolean | null>(null); | |||||
| const form = useForm<FormProps>({ | |||||
| defaultValues: { | |||||
| email: "", | |||||
| retype_email: "", | |||||
| }, | |||||
| resolver: yupResolver( | |||||
| YupObject().shape({ | |||||
| email: YupString().required("必須項目です"), | |||||
| retype_email: YupString() | |||||
| .required("必須項目です") | |||||
| .test("retype", "一致しません", function (value) { | |||||
| return value === this.parent.email; | |||||
| }), | |||||
| }) | |||||
| ), | |||||
| }); | |||||
| const { callAPI: callStartPasswordSetting } = useAPICall({ | |||||
| apiMethod: startPasswordSetting, | |||||
| backDrop: true, | |||||
| form, | |||||
| onSuccess: () => { | |||||
| setDone(true); | |||||
| }, | |||||
| onFailed: () => {}, | |||||
| }); | |||||
| const handleSubmit = (data: FormProps) => { | |||||
| callStartPasswordSetting({ ...data }); | |||||
| }; | |||||
| const toLogin = () => { | |||||
| navigateWhenChanged(getPath(PageID.LOGIN)); | |||||
| }; | |||||
| if (done === true) { | |||||
| return ( | |||||
| <Box sx={{ p: 3, pt: 5, mx: "auto", maxWidth: 500 }} textAlign="center"> | |||||
| <Stack spacing={2}> | |||||
| <Box> | |||||
| <Typography> | |||||
| メールアドレスに設定手続きのご案内を送信しました。 | |||||
| </Typography> | |||||
| <Typography>ご確認ください。</Typography> | |||||
| </Box> | |||||
| <Box> | |||||
| <Button onClick={toLogin}>ログインへ</Button> | |||||
| </Box> | |||||
| </Stack> | |||||
| </Box> | |||||
| ); | |||||
| } | |||||
| return ( | |||||
| <Box sx={{ p: 3, pt: 5, mx: "auto", maxWidth: 500 }} textAlign="center"> | |||||
| <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}> | |||||
| <Stack spacing={2}> | |||||
| <Box> | |||||
| <Button onClick={toLogin}>戻る</Button> | |||||
| </Box> | |||||
| <Box> | |||||
| <Typography> | |||||
| 登録済みのメールアドレスへ変更用のURLを送信します。 | |||||
| </Typography> | |||||
| </Box> | |||||
| <AreaBox label="Email" require={true}> | |||||
| <RHFTextField name="email" /> | |||||
| </AreaBox> | |||||
| <AreaBox label="Email (確認用)" require={true}> | |||||
| <RHFTextField name="retype_email" /> | |||||
| </AreaBox> | |||||
| <Box> | |||||
| <Button type="submit">送信</Button> | |||||
| </Box> | |||||
| </Stack> | |||||
| </FormProvider> | |||||
| </Box> | |||||
| ); | |||||
| } | |||||
| @@ -0,0 +1,117 @@ | |||||
| import { yupResolver } from "@hookform/resolvers/yup"; | |||||
| import { Box, Button, Stack, Typography } from "@mui/material"; | |||||
| import { HasChildren } from "@types"; | |||||
| import { verifyPasswordSetting } from "api/auth"; | |||||
| import RequireChip from "components/chip/RequireChip"; | |||||
| import { FormProvider, RHFTextField } from "components/hook-form"; | |||||
| import StackRow from "components/stack/StackRow"; | |||||
| import useAPICall from "hooks/useAPICall"; | |||||
| import useNavigateCustom from "hooks/useNavigateCustom"; | |||||
| import { PageID } from "pages"; | |||||
| import { useState } from "react"; | |||||
| import { useForm } from "react-hook-form"; | |||||
| import { useParams } from "react-router-dom"; | |||||
| import { getPath } from "routes/path"; | |||||
| import { object as YupObject, string as YupString } from "yup"; | |||||
| type AreaBoxProps = { | |||||
| label: string; | |||||
| require?: boolean; | |||||
| } & HasChildren; | |||||
| function AreaBox({ label, children, require }: AreaBoxProps) { | |||||
| return ( | |||||
| <Box> | |||||
| <StackRow> | |||||
| <Typography variant="subtitle1">〇{label}</Typography> | |||||
| <RequireChip require={require ?? false} /> | |||||
| </StackRow> | |||||
| {children} | |||||
| </Box> | |||||
| ); | |||||
| } | |||||
| type FormProps = { | |||||
| password: string; | |||||
| retype_password: string; | |||||
| }; | |||||
| export default function PasswordSettingVerify() { | |||||
| const { navigateWhenChanged } = useNavigateCustom(); | |||||
| const { token: paramToken } = useParams(); | |||||
| const [done, setDone] = useState<boolean | null>(null); | |||||
| const form = useForm<FormProps>({ | |||||
| defaultValues: { | |||||
| password: "", | |||||
| retype_password: "", | |||||
| }, | |||||
| resolver: yupResolver( | |||||
| YupObject().shape({ | |||||
| password: YupString().required("必須項目です"), | |||||
| retype_password: YupString() | |||||
| .required("必須項目です") | |||||
| .test("retype", "一致しません", function (value) { | |||||
| return value === this.parent.password; | |||||
| }), | |||||
| }) | |||||
| ), | |||||
| }); | |||||
| const { callAPI: callVerifyPasswordSetting } = useAPICall({ | |||||
| apiMethod: verifyPasswordSetting, | |||||
| backDrop: true, | |||||
| form, | |||||
| onSuccess: () => { | |||||
| setDone(true); | |||||
| }, | |||||
| onFailed: () => {}, | |||||
| }); | |||||
| const handleSubmit = (data: FormProps) => { | |||||
| if (!paramToken) return; | |||||
| callVerifyPasswordSetting({ ...data, token: paramToken }); | |||||
| }; | |||||
| const toLogin = () => { | |||||
| navigateWhenChanged(getPath(PageID.LOGIN)); | |||||
| }; | |||||
| if (done === true) { | |||||
| return ( | |||||
| <Box sx={{ p: 3, pt: 5, mx: "auto", maxWidth: 500 }} textAlign="center"> | |||||
| <Stack spacing={2}> | |||||
| <Box> | |||||
| <Typography>パスワードを設定しました</Typography> | |||||
| </Box> | |||||
| <Box> | |||||
| <Button onClick={toLogin}>ログインへ</Button> | |||||
| </Box> | |||||
| </Stack> | |||||
| </Box> | |||||
| ); | |||||
| } | |||||
| return ( | |||||
| <Box sx={{ p: 3, pt: 5, mx: "auto", maxWidth: 500 }} textAlign="center"> | |||||
| <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}> | |||||
| <Stack spacing={2}> | |||||
| <Box> | |||||
| <Typography> | |||||
| 新たに設定するログインパスワードを入力してください | |||||
| </Typography> | |||||
| </Box> | |||||
| <AreaBox label="新しいログインパスワード" require={true}> | |||||
| <RHFTextField name="password" type="password" /> | |||||
| </AreaBox> | |||||
| <AreaBox label="新しいログインパスワード (再入力)" require={true}> | |||||
| <RHFTextField name="retype_password" type="password" /> | |||||
| </AreaBox> | |||||
| <Box> | |||||
| <Button type="submit">送信</Button> | |||||
| </Box> | |||||
| </Stack> | |||||
| </FormProvider> | |||||
| </Box> | |||||
| ); | |||||
| } | |||||
| @@ -5,6 +5,8 @@ export const PageID = { | |||||
| LOGIN: id++, | LOGIN: id++, | ||||
| LOGOUT: id++, | LOGOUT: id++, | ||||
| USER_CHANGE_EMAIL_VERIFY: id++, | USER_CHANGE_EMAIL_VERIFY: id++, | ||||
| USER_SETTING_PASSWORD_START: id++, | |||||
| USER_SETTING_PASSWORD_VERIFY: id++, | |||||
| DASHBOARD_OVERVIEW: id++, | DASHBOARD_OVERVIEW: id++, | ||||
| @@ -35,7 +35,14 @@ const AuthRoutes = (): RouteObject => ({ | |||||
| path: getRoute(PageID.USER_CHANGE_EMAIL_VERIFY), | path: getRoute(PageID.USER_CHANGE_EMAIL_VERIFY), | ||||
| element: <ChangeEmailVerify />, | element: <ChangeEmailVerify />, | ||||
| }, | }, | ||||
| {}, | |||||
| { | |||||
| path: getRoute(PageID.USER_SETTING_PASSWORD_START), | |||||
| element: <PasswordSettingStart />, | |||||
| }, | |||||
| { | |||||
| path: getRoute(PageID.USER_SETTING_PASSWORD_VERIFY), | |||||
| element: <PasswordSettingVerify />, | |||||
| }, | |||||
| ], | ], | ||||
| }); | }); | ||||
| @@ -63,6 +70,12 @@ const Logout = Loadable(lazy(() => import("pages/auth/logout"))); | |||||
| const ChangeEmailVerify = Loadable( | const ChangeEmailVerify = Loadable( | ||||
| lazy(() => import("pages/auth/change-email-verify")) | lazy(() => import("pages/auth/change-email-verify")) | ||||
| ); | ); | ||||
| const PasswordSettingStart = Loadable( | |||||
| lazy(() => import("pages/auth/password-setting-start")) | |||||
| ); | |||||
| const PasswordSettingVerify = Loadable( | |||||
| lazy(() => import("pages/auth/password-setting-verify")) | |||||
| ); | |||||
| // その他 --------------------------------- | // その他 --------------------------------- | ||||
| @@ -66,6 +66,9 @@ const PATHS = { | |||||
| [makePathKey(PageID.LOGIN)]: "/login", | [makePathKey(PageID.LOGIN)]: "/login", | ||||
| [makePathKey(PageID.LOGOUT)]: "/logout", | [makePathKey(PageID.LOGOUT)]: "/logout", | ||||
| [makePathKey(PageID.USER_CHANGE_EMAIL_VERIFY)]: "/change/email/verify/:token", | [makePathKey(PageID.USER_CHANGE_EMAIL_VERIFY)]: "/change/email/verify/:token", | ||||
| [makePathKey(PageID.USER_SETTING_PASSWORD_START)]: "/setting/password/start", | |||||
| [makePathKey(PageID.USER_SETTING_PASSWORD_VERIFY)]: | |||||
| "/setting/password/verify/:token", | |||||
| // ダッシュボード---------------- | // ダッシュボード---------------- | ||||
| ...PATHS_DASHBOARD, | ...PATHS_DASHBOARD, | ||||