From 556af87bd562103301079126de1508fabe805a27 Mon Sep 17 00:00:00 2001 From: Adrian Andersen Date: Fri, 5 Jul 2024 14:29:14 +0200 Subject: [PATCH] feat(login): handle auth for bl-web --- cypress/e2e/login.spec.js | 5 +- cypress/e2e/register.spec.js | 5 +- next.config.js | 40 +++++ src/api/api.ts | 2 + src/api/user.ts | 14 ++ .../email/confirm/[confirmationId]/page.tsx | 41 +++++ src/app/auth/failure/page.tsx | 43 ++++++ src/app/auth/forgot/page.tsx | 2 +- src/app/auth/logout/page.ts | 11 -- src/app/auth/logout/page.tsx | 46 ++++++ src/app/auth/permission/denied/page.tsx | 44 ++++++ src/app/auth/register/page.tsx | 5 +- src/app/auth/reset/[userId]/page.tsx | 43 ++++++ src/app/auth/token/page.tsx | 52 +++++-- src/app/matches/page.tsx | 54 ++----- src/app/settings/page.tsx | 36 ++--- src/components/AuthVerifier.tsx | 41 +++++ src/components/CountdownToRedirect.tsx | 1 + src/components/EmailConfirmer.tsx | 64 ++++++++ src/components/matches/Matches.tsx | 39 +++++ src/components/user/PasswordReset.tsx | 145 ++++++++++++++++++ src/components/user/Settings.tsx | 65 ++++++++ src/components/user/UserDetailEditor.tsx | 125 ++++++++++++--- src/utils/bl-config.ts | 6 +- 24 files changed, 809 insertions(+), 120 deletions(-) create mode 100644 src/app/auth/email/confirm/[confirmationId]/page.tsx create mode 100644 src/app/auth/failure/page.tsx delete mode 100644 src/app/auth/logout/page.ts create mode 100644 src/app/auth/logout/page.tsx create mode 100644 src/app/auth/permission/denied/page.tsx create mode 100644 src/app/auth/reset/[userId]/page.tsx create mode 100644 src/components/AuthVerifier.tsx create mode 100644 src/components/EmailConfirmer.tsx create mode 100644 src/components/matches/Matches.tsx create mode 100644 src/components/user/PasswordReset.tsx create mode 100644 src/components/user/Settings.tsx diff --git a/cypress/e2e/login.spec.js b/cypress/e2e/login.spec.js index 4ec07b5a..f7b8d533 100644 --- a/cypress/e2e/login.spec.js +++ b/cypress/e2e/login.spec.js @@ -6,9 +6,8 @@ describe("Login", () => { }); it("displays all important elements", () => { - // TODO: when FB and Google is enabled, change these two - cy.getBySel("facebook-button").should("not.exist"); - cy.getBySel("google-button").should("not.exist"); + cy.getBySel("facebook-button").should("be.visible"); + cy.getBySel("google-button").should("be.visible"); cy.getBySel("error-message").should("not.exist"); cy.getBySel("email-field").should("be.visible"); cy.getBySel("password-field").should("be.visible"); diff --git a/cypress/e2e/register.spec.js b/cypress/e2e/register.spec.js index 771c6ac2..6f30440c 100644 --- a/cypress/e2e/register.spec.js +++ b/cypress/e2e/register.spec.js @@ -6,9 +6,8 @@ describe("Register", () => { }); it("displays correct initial elements", () => { - // TODO: fix me when fb is enabled - cy.getBySel("facebook-button").should("not.exist"); - cy.getBySel("google-button").should("not.exist"); + cy.getBySel("facebook-button").should("be.visible"); + cy.getBySel("google-button").should("be.visible"); cy.getBySel("email-field").should("be.visible"); cy.getBySel("password-field").should("be.visible"); cy.getBySel("submit-button").should("be.visible"); diff --git a/next.config.js b/next.config.js index 642dbbee..2840f961 100644 --- a/next.config.js +++ b/next.config.js @@ -4,4 +4,44 @@ module.exports = { eslint: { dirs: ["src", "cypress"], }, + // TODO: temporary redirects required while in tandem with bl-web / bl-admin + async redirects() { + return [ + { + source: "/overleveringer", + destination: "/matches", + permanent: false, + }, + { + source: "/auth/login/forgot", + destination: "/auth/forgot", + permanent: false, + }, + { + source: "/auth/menu", + destination: "/auth/login", + permanent: false, + }, + { + source: "/auth/success", + destination: "/", + permanent: false, + }, + { + source: "/auth/social/failure", + destination: "/auth/failure", + permanent: false, + }, + { + source: "/auth/register/detail", + destination: "/settings", + permanent: false, + }, + { + source: "/u/edit", + destination: "/settings", + permanent: false, + }, + ]; + }, }; diff --git a/src/api/api.ts b/src/api/api.ts index 0f92a120..32e8a864 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -4,6 +4,8 @@ import { apiPath, getHeaders } from "@/api/apiRequest"; import { fetchNewTokens } from "@/api/token"; import { BlError } from "@/utils/types"; +// TODO before merge: rewrite to properly handle errors (especially accessToken not valid) and abandon Axios + // eslint-disable-next-line @typescript-eslint/no-explicit-any export const get = async ( url: string, diff --git a/src/api/user.ts b/src/api/user.ts index 7c548fd2..8919b3ba 100644 --- a/src/api/user.ts +++ b/src/api/user.ts @@ -36,3 +36,17 @@ export const updateUserDetails = async ( userDetails, ); }; + +export const resetPassword = async ( + userId: string, + resetToken: string, + newPassword: string, +) => { + return await patch( + `${BL_CONFIG.collection.pendingPasswordReset}/${userId}/${BL_CONFIG.pendingPasswordReset.confirm.operation}`, + { + resetToken, + newPassword, + }, + ); +}; diff --git a/src/app/auth/email/confirm/[confirmationId]/page.tsx b/src/app/auth/email/confirm/[confirmationId]/page.tsx new file mode 100644 index 00000000..fac75544 --- /dev/null +++ b/src/app/auth/email/confirm/[confirmationId]/page.tsx @@ -0,0 +1,41 @@ +import { Card, Container } from "@mui/material"; +import { Box } from "@mui/system"; +import { Metadata } from "next"; +import Image from "next/image"; + +import EmailConfirmer from "@/components/EmailConfirmer"; + +export const metadata: Metadata = { + title: "Bekreft e-post | Boklisten.no", + description: + "Bekreft din e-post-adresse, slik at du får viktig informasjon fra oss.", +}; + +export default function TokenPage({ + params, +}: { + params: { confirmationId: string }; +}) { + return ( + + + + logo + + + + + ); +} diff --git a/src/app/auth/failure/page.tsx b/src/app/auth/failure/page.tsx new file mode 100644 index 00000000..09a6f748 --- /dev/null +++ b/src/app/auth/failure/page.tsx @@ -0,0 +1,43 @@ +import { Alert, AlertTitle, Card, Container } from "@mui/material"; +import { Box } from "@mui/system"; +import { Metadata } from "next"; +import Image from "next/image"; + +import DynamicLink from "@/components/DynamicLink"; + +export const metadata: Metadata = { + title: "Klarte ikke logge inn | Boklisten.no", + description: + "Vi klarte ikke å logge deg inn. Vennligst prøv på nytt eller ta kontakt hvis problemet vedvarer.", +}; + +export default function AuthFailurePage() { + return ( + + + + logo + + Vi klarte ikke å logge deg inn + Vennligst prøv på nytt eller ta kontakt hvis problemet vedvarer. + + + Tilbake til innloggingssiden + + + + + ); +} diff --git a/src/app/auth/forgot/page.tsx b/src/app/auth/forgot/page.tsx index 4bb530ba..b3ee722c 100644 --- a/src/app/auth/forgot/page.tsx +++ b/src/app/auth/forgot/page.tsx @@ -89,7 +89,7 @@ const ForgotPage = () => { )} {success && ( - + Hvis det finnes en bruker med denne e-postaddressen har vi sendt en e-post med instruksjoner for hvordan du kan endre passordet ditt. diff --git a/src/app/auth/logout/page.ts b/src/app/auth/logout/page.ts deleted file mode 100644 index b32cf878..00000000 --- a/src/app/auth/logout/page.ts +++ /dev/null @@ -1,11 +0,0 @@ -"use client"; -import { redirect } from "next/navigation"; - -import { logout } from "@/api/auth"; -import { attachTokensToHref } from "@/components/AuthLinker"; -import BL_CONFIG from "@/utils/bl-config"; - -export default function LogoutPage() { - logout(); - redirect(attachTokensToHref(BL_CONFIG.blWeb.basePath + "logout")); -} diff --git a/src/app/auth/logout/page.tsx b/src/app/auth/logout/page.tsx new file mode 100644 index 00000000..2a544564 --- /dev/null +++ b/src/app/auth/logout/page.tsx @@ -0,0 +1,46 @@ +"use client"; +import { Card, Container, Typography } from "@mui/material"; +import { Box } from "@mui/system"; +import Image from "next/image"; +import { useEffect } from "react"; + +import { logout } from "@/api/auth"; +import CountdownToRedirect from "@/components/CountdownToRedirect"; +import BL_CONFIG from "@/utils/bl-config"; + +// TODO before merge: handle login/logout redirects to and from bl-admin as well +// TODO before merge: handle permission lockouts for bl-admin + +export default function LogoutPage() { + useEffect(() => { + logout(); + }, []); + return ( + + + + logo + + Du er nå logget ut + + + + + + ); +} diff --git a/src/app/auth/permission/denied/page.tsx b/src/app/auth/permission/denied/page.tsx new file mode 100644 index 00000000..e75317cd --- /dev/null +++ b/src/app/auth/permission/denied/page.tsx @@ -0,0 +1,44 @@ +import { Alert, AlertTitle, Card, Container } from "@mui/material"; +import { Box } from "@mui/system"; +import { Metadata } from "next"; +import Image from "next/image"; + +import DynamicLink from "@/components/DynamicLink"; + +export const metadata: Metadata = { + title: "Du har ikke tilgang til denne siden | Boklisten.no", + description: + "Vi klarte ikke å logge deg inn. Vennligst prøv på nytt eller ta kontakt hvis problemet vedvarer.", +}; + +export default function PermissionDeniedPage() { + return ( + + + + logo + + Du har tilgang til å se dette innholdet + Du forsøke å logge inn med en annen bruker eller ta kontakt med + administrator for spørsmål. + + + Tilbake til innloggingssiden + + + + + ); +} diff --git a/src/app/auth/register/page.tsx b/src/app/auth/register/page.tsx index 9dda0dd9..2b1da814 100644 --- a/src/app/auth/register/page.tsx +++ b/src/app/auth/register/page.tsx @@ -1,5 +1,6 @@ import { Card } from "@mui/material"; import { Metadata } from "next"; +import { Suspense } from "react"; import UserDetailEditor from "@/components/user/UserDetailEditor"; @@ -12,7 +13,9 @@ const RegisterPage = () => { return ( <> - + + + ); diff --git a/src/app/auth/reset/[userId]/page.tsx b/src/app/auth/reset/[userId]/page.tsx new file mode 100644 index 00000000..f65c0df8 --- /dev/null +++ b/src/app/auth/reset/[userId]/page.tsx @@ -0,0 +1,43 @@ +import { Card, Container, Typography } from "@mui/material"; +import { Box } from "@mui/system"; +import { Metadata } from "next"; +import Image from "next/image"; + +import PasswordReset from "@/components/user/PasswordReset"; + +export const metadata: Metadata = { + title: "Lag nytt passord | Boklisten.no", + description: "Lag et nytt passord, slik at du får tilgang på kontoen din.", +}; + +export default function PasswordResetPage({ + params, +}: { + params: { userId: string }; +}) { + return ( + + + + logo + + Lag nytt passord + + + + + + ); +} diff --git a/src/app/auth/token/page.tsx b/src/app/auth/token/page.tsx index 333570d4..e7d8117b 100644 --- a/src/app/auth/token/page.tsx +++ b/src/app/auth/token/page.tsx @@ -1,15 +1,43 @@ -"use client"; +import { Card, CircularProgress, Container, Typography } from "@mui/material"; +import { Box } from "@mui/system"; +import { Metadata } from "next"; +import Image from "next/image"; +import { Suspense } from "react"; -import { useRouter } from "next/navigation"; -import { useEffect } from "react"; +import AuthVerifier from "@/components/AuthVerifier"; -export default function TokenRedirectPage() { - const router = useRouter(); - useEffect(() => { - // Redirect after tokens have been captured - setTimeout(() => { - router.push("/"); - }, 100); - }, [router]); - return null; +export const metadata: Metadata = { + title: "Logger inn... | Boklisten.no", + description: "Du blir nå logget inn. Vennligst vent.", +}; + +export default function TokenPage() { + return ( + + + + logo + + Du blir nå logget inn... + + + + + + + + + ); } diff --git a/src/app/matches/page.tsx b/src/app/matches/page.tsx index 6b676094..7443a4fc 100644 --- a/src/app/matches/page.tsx +++ b/src/app/matches/page.tsx @@ -1,42 +1,22 @@ -"use client"; -import { Alert, Typography } from "@mui/material"; -import Button from "@mui/material/Button"; -import React, { useEffect, useState } from "react"; +import { Typography } from "@mui/material"; +import { Box } from "@mui/system"; +import { Metadata } from "next"; +import React, { Suspense } from "react"; -import { isLoggedIn } from "@/api/auth"; -import DynamicLink from "@/components/DynamicLink"; -import { MatchesList } from "@/components/matches/matchesList/MatchesList"; +import Matches from "@/components/matches/Matches"; -const MatchesPage = () => { - const [hydrated, setHydrated] = useState(false); - useEffect(() => { - setHydrated(true); - }, []); +export const metadata: Metadata = { + title: "Mine overleveringer | Boklisten.no", + description: "Overleveringer av bøker", +}; +export default function MatchesPage() { return ( - <> - Mine overleveringer | Boklisten.no - -
- Mine overleveringer - {hydrated && - (isLoggedIn() ? ( - - ) : ( - <> - - Du må logge inn for å se overleveringene dine - - - - - - ))} -
- + + Mine overleveringer + + + + ); -}; - -export default MatchesPage; +} diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx index 94329dd2..b3d294c9 100644 --- a/src/app/settings/page.tsx +++ b/src/app/settings/page.tsx @@ -1,32 +1,18 @@ -"use client"; -import { Card } from "@mui/material"; -import { useEffect, useState } from "react"; +import { Metadata } from "next"; +import { Suspense } from "react"; -import { get } from "@/api/api"; -import { getAccessTokenBody } from "@/api/token"; -import UserDetailEditor from "@/components/user/UserDetailEditor"; +import Settings from "@/components/user/Settings"; -const SettingsPage = () => { - const [userDetails, setUserDetails] = useState(); +export const metadata: Metadata = { + title: "Innstillinger | Boklisten.no", + description: "Endre din informasjon", +}; - useEffect(() => { - const { details } = getAccessTokenBody(); - const userDetailUrl = `userdetails/${details}`; - const fetchDetails = async () => { - const data = await get(userDetailUrl); - setUserDetails(data.data.data[0]); - }; - fetchDetails(); - }, []); +const SettingsPage = () => { return ( - <> - Innstillinger | Boklisten.no - - - {!userDetails && } - {userDetails && } - - + + + ); }; diff --git a/src/components/AuthVerifier.tsx b/src/components/AuthVerifier.tsx new file mode 100644 index 00000000..0179ab54 --- /dev/null +++ b/src/components/AuthVerifier.tsx @@ -0,0 +1,41 @@ +"use client"; + +import { useRouter, useSearchParams } from "next/navigation"; +import { useEffect } from "react"; + +import { get } from "@/api/api"; +import { isLoggedIn } from "@/api/auth"; +import { getAccessTokenBody } from "@/api/token"; +import BL_CONFIG from "@/utils/bl-config"; + +export default function AuthVerifier() { + const searchParams = useSearchParams(); + const router = useRouter(); + useEffect(() => { + // Wait for AuthLinker + if (searchParams.size > 0) { + return; + } + if (!isLoggedIn()) { + router.push("/auth/failure"); + } + const { details } = getAccessTokenBody(); + const checkUserDetailsValid = async () => { + try { + const response = await get( + `${BL_CONFIG.collection.userDetail}/${details}/valid`, + ); + const isValid = response.data.data[0]?.valid; + if (isValid) { + router.push("/"); + } else { + router.push("/settings"); + } + } catch { + router.push("/auth/failure"); + } + }; + checkUserDetailsValid(); + }, [router, searchParams.size]); + return null; +} diff --git a/src/components/CountdownToRedirect.tsx b/src/components/CountdownToRedirect.tsx index f451d99a..0ef606e8 100644 --- a/src/components/CountdownToRedirect.tsx +++ b/src/components/CountdownToRedirect.tsx @@ -1,3 +1,4 @@ +"use client"; import { LinearProgress, Box, Typography } from "@mui/material"; import { useRouter } from "next/navigation"; import { useEffect, useRef, useState } from "react"; diff --git a/src/components/EmailConfirmer.tsx b/src/components/EmailConfirmer.tsx new file mode 100644 index 00000000..31cadd03 --- /dev/null +++ b/src/components/EmailConfirmer.tsx @@ -0,0 +1,64 @@ +"use client"; +import { Alert, CircularProgress, Typography } from "@mui/material"; +import { useEffect, useState } from "react"; + +import { patch } from "@/api/api"; +import CountdownToRedirect from "@/components/CountdownToRedirect"; +import DynamicLink from "@/components/DynamicLink"; +import BL_CONFIG from "@/utils/bl-config"; +import { verifyBlError } from "@/utils/types"; + +function validateEmail(confirmationId: string) { + return patch( + `${BL_CONFIG.collection.emailValidation}/${confirmationId}/${BL_CONFIG.emailValidation.confirm.operation}`, + {}, + ); +} + +export default function EmailConfirmer({ + confirmationId, +}: { + confirmationId: string; +}) { + const [status, setStatus] = useState<"WAIT" | "SUCCESS" | "ERROR">("WAIT"); + + useEffect(() => { + async function tryValidateEmail() { + const response = await validateEmail(confirmationId); + if (verifyBlError(response)) { + setStatus("ERROR"); + return; + } + setStatus("SUCCESS"); + } + tryValidateEmail(); + }, [confirmationId]); + + return ( + <> + {status === "WAIT" && ( + <> + + Verifiserer e-post... + + + + )} + {status === "ERROR" && ( + <> + + Kunne ikke bekrefte e-post. Lenken kan være utløpt. Du kan prøve å + sende en ny lenke fra innstillinger. + + Gå til innstillinger + + )} + {status === "SUCCESS" && ( + <> + E-post-adressen ble bekreftet! + + + )} + + ); +} diff --git a/src/components/matches/Matches.tsx b/src/components/matches/Matches.tsx new file mode 100644 index 00000000..7d4f224a --- /dev/null +++ b/src/components/matches/Matches.tsx @@ -0,0 +1,39 @@ +"use client"; +import { Alert } from "@mui/material"; +import Button from "@mui/material/Button"; +import { useSearchParams } from "next/navigation"; +import React, { useEffect, useState } from "react"; + +import { isLoggedIn } from "@/api/auth"; +import DynamicLink from "@/components/DynamicLink"; +import { MatchesList } from "@/components/matches/matchesList/MatchesList"; + +export default function Matches() { + const searchParams = useSearchParams(); + const [hydrated, setHydrated] = useState(false); + useEffect(() => { + // Wait for AuthLinker + if (searchParams.size > 0) { + return; + } + setHydrated(true); + }, [searchParams.size]); + + return ( + hydrated && + (isLoggedIn() ? ( + + ) : ( + <> + + Du må logge inn for å se overleveringene dine + + + + + + )) + ); +} diff --git a/src/components/user/PasswordReset.tsx b/src/components/user/PasswordReset.tsx new file mode 100644 index 00000000..110a327b --- /dev/null +++ b/src/components/user/PasswordReset.tsx @@ -0,0 +1,145 @@ +"use client"; +import { Visibility, VisibilityOff } from "@mui/icons-material"; +import { + Alert, + IconButton, + InputAdornment, + Stack, + Tooltip, +} from "@mui/material"; +import Button from "@mui/material/Button"; +import Grid from "@mui/material/Grid"; +import TextField from "@mui/material/TextField"; +import { Box } from "@mui/system"; +import { useSearchParams } from "next/navigation"; +import { useState } from "react"; +import { SubmitHandler, useForm } from "react-hook-form"; + +import { resetPassword } from "@/api/user"; +import DynamicLink from "@/components/DynamicLink"; +import { verifyBlError } from "@/utils/types"; + +type PasswordResetFields = { + password: string; +}; + +export default function PasswordReset({ userId }: { userId: string }) { + const searchParams = useSearchParams(); + const [showPassword, setShowPassword] = useState(false); + const [apiError, setApiError] = useState(""); + const [success, setSuccess] = useState(false); + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ mode: "onTouched" }); + const onSubmit: SubmitHandler = async (data) => { + setApiError(""); + const result = await resetPassword( + userId, + searchParams.get("resetToken") ?? "", + data.password, + ); + if (verifyBlError(result)) { + setApiError( + "Klarte ikke sette nytt passord. Lenken kan være utløpt. Prøv igjen eller ta kontakt dersom problemet vedvarer.", + ); + return; + } + setSuccess(true); + }; + return ( + + {success ? ( + + + Passordet ble oppdatert! Du kan nå logge inn. + + + + + + ) : ( + <> + {apiError && ( + + {apiError} + + )} + {Object.entries(errors).map(([type, message]) => ( + + {message.message} + + ))} + + + + + setShowPassword(!showPassword)} + onMouseDown={(event: React.MouseEvent) => { + event.preventDefault(); + }} + > + {showPassword ? : } + + + + + + + + + Tilbake til innloggingssiden + + + + + )} + + ); +} diff --git a/src/components/user/Settings.tsx b/src/components/user/Settings.tsx new file mode 100644 index 00000000..ee6c97cc --- /dev/null +++ b/src/components/user/Settings.tsx @@ -0,0 +1,65 @@ +"use client"; +import { UserDetail } from "@boklisten/bl-model"; +import { Card, CircularProgress, Typography } from "@mui/material"; +import { Box } from "@mui/system"; +import Image from "next/image"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useEffect, useState } from "react"; + +import { get } from "@/api/api"; +import { getAccessTokenBody } from "@/api/token"; +import UserDetailEditor from "@/components/user/UserDetailEditor"; +import BL_CONFIG from "@/utils/bl-config"; + +const Settings = () => { + const searchParams = useSearchParams(); + const router = useRouter(); + const [userDetails, setUserDetails] = useState(); + + useEffect(() => { + // Wait for AuthLinker to process query params + if (searchParams.size > 0) { + return; + } + + try { + const { details } = getAccessTokenBody(); + const fetchDetails = async () => { + const data = await get(`${BL_CONFIG.collection.userDetail}/${details}`); + setUserDetails(data.data.data[0]); + }; + fetchDetails(); + } catch { + router.push("/auth/login?redirect=settings"); + } + }, [router, searchParams]); + + return ( + + {!userDetails && ( + + logo + + Innstillinger + + + + )} + {userDetails && } + + ); +}; + +export default Settings; diff --git a/src/components/user/UserDetailEditor.tsx b/src/components/user/UserDetailEditor.tsx index 2a94943a..64d7a351 100644 --- a/src/components/user/UserDetailEditor.tsx +++ b/src/components/user/UserDetailEditor.tsx @@ -1,6 +1,12 @@ "use client"; import { UserDetail } from "@boklisten/bl-model"; -import { Visibility, VisibilityOff } from "@mui/icons-material"; +import { + Check, + Email, + Info, + Visibility, + VisibilityOff, +} from "@mui/icons-material"; import { Alert, Divider, @@ -27,6 +33,7 @@ import isEmail from "validator/lib/isEmail"; import isMobilePhone from "validator/lib/isMobilePhone"; import isPostalCode from "validator/lib/isPostalCode"; +import { add } from "@/api/api"; import { apiPath } from "@/api/apiRequest"; import { fetchData } from "@/api/requests"; import { getAccessTokenBody } from "@/api/token"; @@ -93,9 +100,13 @@ const UserDetailEditor = ({ isSignUp?: boolean; userDetails?: UserDetail; }) => { + const [emailConfirmationRequested, setEmailConfirmationRequested] = + useState(false); const [showPassword, setShowPassword] = useState(false); const [showDetails, setShowDetails] = useState(!isSignUp); - const [postalCity, setPostalCity] = useState(userDetails?.postCity ?? ""); + const [postalCity, setPostalCity] = useState( + userDetails?.postCity ?? null, + ); const [waitingForPostalCity, setWaitingForPostalCity] = useState(false); const router = useRouter(); const searchParams = useSearchParams(); @@ -107,7 +118,7 @@ const UserDetailEditor = ({ phoneNumber: userDetails.phone, address: userDetails.address, postalCode: userDetails.postCode, - birthday: moment(userDetails.dob), + birthday: userDetails.dob ? moment(userDetails.dob) : null, guardianName: userDetails.guardian?.name as string, guardianEmail: userDetails.guardian?.email as string, guardianPhoneNumber: userDetails.guardian?.phone as string, @@ -125,6 +136,12 @@ const UserDetailEditor = ({ } = useForm({ mode: "onTouched", defaultValues }); const onSubmit: SubmitHandler = async (data) => { + if (postalCity === null) { + setError("postalCode", { + message: "Du må oppgi et gyldig postnummer!", + }); + return; + } if (isSignUp) { const result = await registerUser(data.email, data.password); if (verifyBlError(result)) { @@ -183,7 +200,7 @@ const UserDetailEditor = ({ height={50} alt="logo" /> - + {isSignUp ? "Registrer deg" : "Innstillinger"} {isSignUp && ( @@ -208,22 +225,82 @@ const UserDetailEditor = ({ ))} - setShowDetails(true)} - required - disabled={!isSignUp} - fullWidth - id="email" - label="Epost" - autoComplete="email" - error={!!errors.email} - {...register("email", { - required: "Du må fylle inn epost", - validate: (v) => - isEmail(v) ? true : "Du må fylle inn en gyldig epost", - })} - /> + + setShowDetails(true)} + required + disabled={!isSignUp} + fullWidth + id="email" + label="Epost" + autoComplete="email" + error={!!errors.email} + {...register("email", { + required: "Du må fylle inn epost", + validate: (v) => + isEmail(v) ? true : "Du må fylle inn en gyldig epost", + })} + /> + {!isSignUp && ( + + + {userDetails.emailConfirmed ? ( + + ) : ( + + )} + + + )} + + {!isSignUp && !userDetails.emailConfirmed && ( + <> + {emailConfirmationRequested ? ( + }> + Bekreftelseslenke er sendt til din e-post-adresse! Sjekk + søppelpost om den ikke dukker opp i inbox. + + ) : ( + + )} + + )} {isSignUp && ( { if (event.target.value.length === 0) { - setPostalCity(""); + setPostalCity(null); return; } setWaitingForPostalCity(true); const response = await fetchData( apiPath( BL_CONFIG.collection.delivery, - "/postal-code", + "/postal-code-lookup", ), "POST", { postalCode: event.target.value }, @@ -387,7 +464,7 @@ const UserDetailEditor = ({ setWaitingForPostalCity(false); if (!response.data?.[0].postalCity) { - setPostalCity(""); + setPostalCity(null); return; } @@ -404,7 +481,7 @@ const UserDetailEditor = ({ const response = await fetchData( apiPath( BL_CONFIG.collection.delivery, - "/postal-code", + "/postal-code-lookup", ), "POST", { postalCode: v }, diff --git a/src/utils/bl-config.ts b/src/utils/bl-config.ts index bda9d95f..82010251 100644 --- a/src/utils/bl-config.ts +++ b/src/utils/bl-config.ts @@ -45,9 +45,9 @@ const BL_CONFIG = { operation: "agreement", }, }, - passwordReset: { - setNew: { - operation: "new", + pendingPasswordReset: { + confirm: { + operation: "confirm", }, }, emailValidation: {