From 55794034c597f8ae8962aea48f8fc5ad42445c10 Mon Sep 17 00:00:00 2001 From: Simon Emanuelsson Date: Tue, 16 Apr 2024 12:42:44 +0200 Subject: [PATCH] feat(WEB-169): get profile data from API --- .env.local.example | 1 + .../profile/{edit => [...catchAll]}/page.tsx | 0 .../my-pages/@breadcrumbs/profile/default.tsx | 3 - .../@breadcrumbs/profile/edit/default.tsx | 3 - .../(protected)/my-pages/overview/error.tsx | 8 + .../(protected)/my-pages/overview/page.tsx | 2 +- .../my-pages/profile/@profile/edit/page.tsx | 7 +- .../my-pages/profile/@profile/error.tsx | 8 + .../my-pages/profile/@profile/page.tsx | 7 +- auth.ts | 24 +- .../Blocks/Overview/Buttons/CopyButton.tsx | 6 +- .../MyPages/Blocks/Overview/Friend/index.tsx | 4 +- .../Overview/Stats/QualifyingPoints/index.tsx | 37 -- .../Stats/QualifyingPoints/points.module.css | 56 -- .../Overview/Stats/TotalPoints/index.tsx | 2 +- components/MyProfile/Profile/Container.tsx | 4 +- .../MyProfile/Profile/Edit/Form/Content.tsx | 26 +- .../MyProfile/Profile/Edit/Form/index.tsx | 10 +- .../MyProfile/Profile/Edit/Form/schema.ts | 18 +- components/MyProfile/Profile/Edit/index.tsx | 11 +- components/MyProfile/Profile/index.tsx | 27 +- .../Form/Country/countries.ts | 517 +++++++++--------- .../TempDesignSystem/Form/Country/index.tsx | 17 +- .../TempDesignSystem/Form/Phone/index.tsx | 19 +- .../TempDesignSystem/Form/Phone/phone.ts | 1 + env/server.ts | 2 + lib/api/endpoints.ts | 10 + lib/api/index.ts | 60 ++ lib/trpc/Provider.tsx | 8 +- lib/trpc/client.ts | 2 +- package-lock.json | 9 + package.json | 1 + server/errors/trpc.ts | 17 +- server/routers/user/output.ts | 35 +- server/routers/user/query.ts | 149 ++--- server/routers/user/temp.ts | 51 +- server/trpc.ts | 21 +- types/auth.d.ts | 11 +- .../components/myPages/myProfile/container.ts | 6 + types/components/myPages/myProfile/profile.ts | 5 - types/fetch.ts | 7 + types/jwt.d.ts | 8 + types/next/error.ts | 8 + types/user.ts | 11 +- 44 files changed, 632 insertions(+), 607 deletions(-) rename app/[lang]/(live)/(protected)/my-pages/@breadcrumbs/profile/{edit => [...catchAll]}/page.tsx (100%) delete mode 100644 app/[lang]/(live)/(protected)/my-pages/@breadcrumbs/profile/default.tsx delete mode 100644 app/[lang]/(live)/(protected)/my-pages/@breadcrumbs/profile/edit/default.tsx create mode 100644 app/[lang]/(live)/(protected)/my-pages/overview/error.tsx create mode 100644 app/[lang]/(live)/(protected)/my-pages/profile/@profile/error.tsx delete mode 100644 components/MyPages/Blocks/Overview/Stats/QualifyingPoints/index.tsx delete mode 100644 components/MyPages/Blocks/Overview/Stats/QualifyingPoints/points.module.css create mode 100644 lib/api/endpoints.ts create mode 100644 lib/api/index.ts create mode 100644 types/components/myPages/myProfile/container.ts delete mode 100644 types/components/myPages/myProfile/profile.ts create mode 100644 types/fetch.ts create mode 100644 types/jwt.d.ts create mode 100644 types/next/error.ts diff --git a/.env.local.example b/.env.local.example index 7060fdc66..c62dd0f6a 100644 --- a/.env.local.example +++ b/.env.local.example @@ -1,4 +1,5 @@ ADOBE_SCRIPT_SRC="" +API_BASEURL="https://tstapi.scandichotels.com" CMS_ACCESS_TOKEN="" CMS_API_KEY="" CMS_ENVIRONMENT="development" diff --git a/app/[lang]/(live)/(protected)/my-pages/@breadcrumbs/profile/edit/page.tsx b/app/[lang]/(live)/(protected)/my-pages/@breadcrumbs/profile/[...catchAll]/page.tsx similarity index 100% rename from app/[lang]/(live)/(protected)/my-pages/@breadcrumbs/profile/edit/page.tsx rename to app/[lang]/(live)/(protected)/my-pages/@breadcrumbs/profile/[...catchAll]/page.tsx diff --git a/app/[lang]/(live)/(protected)/my-pages/@breadcrumbs/profile/default.tsx b/app/[lang]/(live)/(protected)/my-pages/@breadcrumbs/profile/default.tsx deleted file mode 100644 index 86b9e9a38..000000000 --- a/app/[lang]/(live)/(protected)/my-pages/@breadcrumbs/profile/default.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Default() { - return null -} diff --git a/app/[lang]/(live)/(protected)/my-pages/@breadcrumbs/profile/edit/default.tsx b/app/[lang]/(live)/(protected)/my-pages/@breadcrumbs/profile/edit/default.tsx deleted file mode 100644 index 86b9e9a38..000000000 --- a/app/[lang]/(live)/(protected)/my-pages/@breadcrumbs/profile/edit/default.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Default() { - return null -} diff --git a/app/[lang]/(live)/(protected)/my-pages/overview/error.tsx b/app/[lang]/(live)/(protected)/my-pages/overview/error.tsx new file mode 100644 index 000000000..131424d0b --- /dev/null +++ b/app/[lang]/(live)/(protected)/my-pages/overview/error.tsx @@ -0,0 +1,8 @@ +"use client" + +import type { ErrorPage } from "@/types/next/error" + +export default function MyPageOverviewError({ error }: ErrorPage) { + console.error(error) + return

Error happened, overview

+} diff --git a/app/[lang]/(live)/(protected)/my-pages/overview/page.tsx b/app/[lang]/(live)/(protected)/my-pages/overview/page.tsx index 2a76f5785..a9bcd261b 100644 --- a/app/[lang]/(live)/(protected)/my-pages/overview/page.tsx +++ b/app/[lang]/(live)/(protected)/my-pages/overview/page.tsx @@ -10,7 +10,7 @@ import styles from "./page.module.css" import type { LangParams, PageArgs } from "@/types/params" -export default async function MyPage({ params }: PageArgs) { +export default async function MyPageOverview({ params }: PageArgs) { const user = await serverClient().user.get() return ( diff --git a/app/[lang]/(live)/(protected)/my-pages/profile/@profile/edit/page.tsx b/app/[lang]/(live)/(protected)/my-pages/profile/@profile/edit/page.tsx index d2ba5182e..87dd80061 100644 --- a/app/[lang]/(live)/(protected)/my-pages/profile/@profile/edit/page.tsx +++ b/app/[lang]/(live)/(protected)/my-pages/profile/@profile/edit/page.tsx @@ -1,8 +1,5 @@ -import { serverClient } from "@/lib/trpc/server" - import EditProfile from "@/components/MyProfile/Profile/Edit" -export default async function EditProfileSlot() { - const user = await serverClient().user.get() - return +export default function EditProfileSlot() { + return } diff --git a/app/[lang]/(live)/(protected)/my-pages/profile/@profile/error.tsx b/app/[lang]/(live)/(protected)/my-pages/profile/@profile/error.tsx new file mode 100644 index 000000000..197ee9e67 --- /dev/null +++ b/app/[lang]/(live)/(protected)/my-pages/profile/@profile/error.tsx @@ -0,0 +1,8 @@ +"use client" + +import type { ErrorPage } from "@/types/next/error" + +export default function ProfileError({ error }: ErrorPage) { + console.error(error) + return

Error happened, Profile

+} diff --git a/app/[lang]/(live)/(protected)/my-pages/profile/@profile/page.tsx b/app/[lang]/(live)/(protected)/my-pages/profile/@profile/page.tsx index b1f828678..fd7a32b77 100644 --- a/app/[lang]/(live)/(protected)/my-pages/profile/@profile/page.tsx +++ b/app/[lang]/(live)/(protected)/my-pages/profile/@profile/page.tsx @@ -1,8 +1,5 @@ -import { serverClient } from "@/lib/trpc/server" - import Profile from "@/components/MyProfile/Profile" -export default async function ProfileInfo() { - const user = await serverClient().user.get() - return +export default function ProfileInfo() { + return } diff --git a/auth.ts b/auth.ts index e1dcdd88c..ad1dcdc81 100644 --- a/auth.ts +++ b/auth.ts @@ -18,7 +18,7 @@ const customProvider = { authorization: { url: `${env.CURITY_ISSUER_USER}/oauth/v2/authorize`, params: { - scope: ["openid"], + scope: ["openid", "profile"].join(" "), /** * The `acr_values` param is used to make Curity display the proper login * page for Scandic. Without the parameter Curity presents some choices @@ -53,9 +53,9 @@ export const config = { strategy: "jwt", }, callbacks: { - async signIn(...args) { + async signIn() { console.log("****** SIGN IN *******") - console.log(args) + console.log(arguments) console.log("****** END - SIGN IN *******") return true }, @@ -68,6 +68,7 @@ export const config = { if (session.user) { return { ...session, + token, user: { ...session.user, id: token.sub, @@ -107,22 +108,27 @@ export const config = { console.log("****** END - AUTHORIZED *******") return true }, - async jwt({ session, token, trigger }) { + async jwt({ session, token, trigger, account }) { console.log("****** JWT *******") - console.log({ session, token, trigger }) + console.log({ session, token, trigger, account }) console.log("****** END - JWT *******") + if (account) { + return { + access_token: account.access_token, + } + } return token }, }, events: { - async signIn(...args) { + async signIn() { console.log("#### SIGNIN EVENT ARGS ######") - console.log(args) + console.log(arguments) console.log("#### END - SIGNIN EVENT ARGS ######") }, - async session(...args) { + async session() { console.log("#### SESSION EVENT ARGS ######") - console.log(args) + console.log(arguments) console.log("#### END - SESSION EVENT ARGS ######") }, }, diff --git a/components/MyPages/Blocks/Overview/Buttons/CopyButton.tsx b/components/MyPages/Blocks/Overview/Buttons/CopyButton.tsx index 8af32c8e5..678e27be7 100644 --- a/components/MyPages/Blocks/Overview/Buttons/CopyButton.tsx +++ b/components/MyPages/Blocks/Overview/Buttons/CopyButton.tsx @@ -5,11 +5,9 @@ import Image from "@/components/Image" import type { User } from "@/types/user" -export default function CopyButton({ - membershipId, -}: Pick) { +export default function CopyButton({ membership }: Pick) { function handleCopy() { - console.log(`COPIED! (${membershipId})`) + console.log(`COPIED! (${membership.membershipNumber})`) } return ( diff --git a/components/MyPages/Blocks/Overview/Friend/index.tsx b/components/MyPages/Blocks/Overview/Friend/index.tsx index 6ce53a568..04c3e8773 100644 --- a/components/MyPages/Blocks/Overview/Friend/index.tsx +++ b/components/MyPages/Blocks/Overview/Friend/index.tsx @@ -17,7 +17,9 @@ export default function Friend({ user }: FriendProps) { />

{user.name}

-

{user.membershipId}

+

+ {user.membership.membershipNumber} +

) diff --git a/components/MyPages/Blocks/Overview/Stats/QualifyingPoints/index.tsx b/components/MyPages/Blocks/Overview/Stats/QualifyingPoints/index.tsx deleted file mode 100644 index 00e00eac6..000000000 --- a/components/MyPages/Blocks/Overview/Stats/QualifyingPoints/index.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import Divider from "@/components/TempDesignSystem/Divider" -import Image from "@/components/Image" - -import styles from "./points.module.css" - -import type { QualifyingPointsProps } from "@/types/components/myPages/myPage/qualifyingPoints" - -export default function QualifyingPoints({ user }: QualifyingPointsProps) { - return ( -
-

Progress

- -
-
- Arrow Up Icon -

{user.qualifyingPoints}

-
Qualifying points
-
-
- Arrow Up Icon -

{user.nights}

-
Nights
-
-
-
- ) -} diff --git a/components/MyPages/Blocks/Overview/Stats/QualifyingPoints/points.module.css b/components/MyPages/Blocks/Overview/Stats/QualifyingPoints/points.module.css deleted file mode 100644 index d381756ad..000000000 --- a/components/MyPages/Blocks/Overview/Stats/QualifyingPoints/points.module.css +++ /dev/null @@ -1,56 +0,0 @@ -.qualifyingPoints { - display: none; -} - -.title { - color: var(--some-grey-color, #000); - /* font-family: var(--ff-brandon-text); */ - font-size: 1.5rem; - font-weight: 500; - letter-spacing: 0.6%; - line-height: 1.7rem; - margin: 0 0 0.7rem; -} - -@media screen and (min-width: 950px) { - .qualifyingPoints { - display: block; - } - - .container { - display: grid; - gap: 3.8rem; - grid-template-columns: auto 1fr; - margin-top: 1.5rem; - } - - .title { - color: var(--some-grey-color, #4f4f4f); - font-size: 1.2rem; - } - - .points { - align-items: center; - display: grid; - gap: 1rem; - grid-template-columns: auto 1fr; - } - - .point { - /* font-family: var(--ff-brandon-text); */ - font-size: 2.7rem; - font-weight: 900; - line-height: 2.7rem; - margin: 0; - } - - .pointTitle { - font-family: var(--ff-fira-sans); - font-size: 1.2rem; - font-weight: 400; - letter-spacing: 0.6%; - line-height: 1.4rem; - grid-column: 1/-1; - margin: 0; - } -} diff --git a/components/MyPages/Blocks/Overview/Stats/TotalPoints/index.tsx b/components/MyPages/Blocks/Overview/Stats/TotalPoints/index.tsx index 61f992d7e..49c8159f7 100644 --- a/components/MyPages/Blocks/Overview/Stats/TotalPoints/index.tsx +++ b/components/MyPages/Blocks/Overview/Stats/TotalPoints/index.tsx @@ -10,7 +10,7 @@ export default function TotalPoints({ user }: TotalPointsProps) {
Total Points -

{user.points}

+

{user.membership.currentPoints}

) } diff --git a/components/MyProfile/Profile/Container.tsx b/components/MyProfile/Profile/Container.tsx index a11a2dbb3..f79a2c522 100644 --- a/components/MyProfile/Profile/Container.tsx +++ b/components/MyProfile/Profile/Container.tsx @@ -5,7 +5,7 @@ import Image from "@/components/Image" import styles from "./profile.module.css" -import type { ProfileProps } from "@/types/components/myPages/myProfile/profile" +import type { ContainerProps } from "@/types/components/myPages/myProfile/container" const profileStyles = cva(styles.profile) @@ -14,7 +14,7 @@ export default function Container({ className, user, ...props -}: ProfileProps) { +}: ContainerProps) { return (
diff --git a/components/MyProfile/Profile/Edit/Form/Content.tsx b/components/MyProfile/Profile/Edit/Form/Content.tsx index 62608ae8b..38d0a20a7 100644 --- a/components/MyProfile/Profile/Edit/Form/Content.tsx +++ b/components/MyProfile/Profile/Edit/Form/Content.tsx @@ -1,6 +1,7 @@ "use client" import { useEffect } from "react" import { useFormStatus } from "react-dom" +import { useWatch } from "react-hook-form" import { _ } from "@/lib/translation" import { useProfileStore } from "@/stores/edit-profile" @@ -22,6 +23,7 @@ import type { EditFormContentProps } from "@/types/components/myPages/myProfile/ export default function FormContent({ control }: EditFormContentProps) { const { pending } = useFormStatus() const setIsPending = useProfileStore((store) => store.setIsPending) + const country = useWatch({ name: "address.country" }) useEffect(() => { setIsPending(pending) @@ -30,10 +32,10 @@ export default function FormContent({ control }: EditFormContentProps) { return ( <> - SE - *{_("Country")} + {country} + *{_("Country")} - + @@ -72,9 +74,9 @@ export default function FormContent({ control }: EditFormContentProps) { - *{_("Phone")} + *{_("Phone")} - + @@ -82,12 +84,14 @@ export default function FormContent({ control }: EditFormContentProps) { - *{_("Address")} + + *{_("Address")} + @@ -98,12 +102,12 @@ export default function FormContent({ control }: EditFormContentProps) { - *{_("City/State")} + *{_("City/State")} @@ -114,12 +118,12 @@ export default function FormContent({ control }: EditFormContentProps) { - *{_("Zip code")} + *{_("Zip code")} diff --git a/components/MyProfile/Profile/Edit/Form/index.tsx b/components/MyProfile/Profile/Edit/Form/index.tsx index e882b6aae..d28361e61 100644 --- a/components/MyProfile/Profile/Edit/Form/index.tsx +++ b/components/MyProfile/Profile/Edit/Form/index.tsx @@ -30,15 +30,7 @@ export default function Form({ user }: EditFormProps) { ) const form = useForm({ - defaultValues: { - country: user.country, - city: user.address.city, - dob: user.dob, - email: user.email, - phone: user.phone, - street: user.address.street, - zip: user.address.zipcode, - }, + defaultValues: user, criteriaMode: "all", mode: "onTouched", resolver: zodResolver(editProfileSchema), diff --git a/components/MyProfile/Profile/Edit/Form/schema.ts b/components/MyProfile/Profile/Edit/Form/schema.ts index d561ffa2b..f12ff067c 100644 --- a/components/MyProfile/Profile/Edit/Form/schema.ts +++ b/components/MyProfile/Profile/Edit/Form/schema.ts @@ -4,26 +4,22 @@ import { _ } from "@/lib/translation" import { phoneValidator } from "@/utils/phoneValidator" export const editProfileSchema = z.object({ - city: z - .string({ required_error: _("City is required") }) - .min(1, { message: _("City is required") }), - country: z + "address.country": z .string({ required_error: _("Country is required") }) .min(1, { message: _("Country is required") }), + "address.city": z.string().optional(), + "address.streetAddress": z.string().optional(), + "address.zipCode": z + .string({ required_error: _("Zip code is required") }) + .min(1, { message: _("Zip code is required") }), dob: z .string({ required_error: _("Date of Birth is required") }) .min(1, { message: _("Date of Birth is required") }), email: z.string().email(), - phone: phoneValidator( + phoneNumber: phoneValidator( _("Phone is required"), _("Please enter a valid phone number") ), - street: z - .string({ required_error: _("Address is required") }) - .min(1, { message: _("Address is required") }), - zip: z - .string({ required_error: _("Zip code is required") }) - .min(1, { message: _("Zip code is required") }), }) export type EditProfileSchema = z.infer diff --git a/components/MyProfile/Profile/Edit/index.tsx b/components/MyProfile/Profile/Edit/index.tsx index 12ef48af5..0131284d6 100644 --- a/components/MyProfile/Profile/Edit/index.tsx +++ b/components/MyProfile/Profile/Edit/index.tsx @@ -1,12 +1,13 @@ +import { serverClient } from "@/lib/trpc/server" + import Container from "../Container" import Form from "./Form" -import type { ProfileProps } from "@/types/components/myPages/myProfile/profile" - -export default function EditProfile(props: ProfileProps) { +export default async function EditProfile() { + const user = await serverClient().user.get() return ( - -
+ + ) } diff --git a/components/MyProfile/Profile/index.tsx b/components/MyProfile/Profile/index.tsx index 2df9465cb..0893819fa 100644 --- a/components/MyProfile/Profile/index.tsx +++ b/components/MyProfile/Profile/index.tsx @@ -1,4 +1,5 @@ import { _ } from "@/lib/translation" +import { countries } from "@/components/TempDesignSystem/Form/Country/countries" import { CalendarIcon, @@ -11,22 +12,27 @@ import Field from "../Field" import styles from "./profile.module.css" -import type { ProfileProps } from "@/types/components/myPages/myProfile/profile" +import { serverClient } from "@/lib/trpc/server" -export default function Profile(props: ProfileProps) { +export default async function Profile() { + const user = await serverClient().user.get() + const countryName = countries.find( + (country) => country.code === user.address.country + ) return ( - +
- SE + {user.address.country} {_("Country")} - Sweden + {countryName?.name} {_("Date of Birth")} + {/* TODO: Get this from user when API team adds it to payload */} 27/05/1977 @@ -34,35 +40,36 @@ export default function Profile(props: ProfileProps) { {_("Email")} - f*********@g****.com + {user.email} {_("Phone number")} - +46 ******00 + {user.phoneNumber} {_("Address")} - T*************** + {user.address.streetAddress || "-"} {_("City/State")} - S******* + {/* TODO: Get this from user when API team adds it to payload */} + {user.address.city || "-"} {_("Zip code")} - 1**** + {user.address.zipCode}
diff --git a/components/TempDesignSystem/Form/Country/countries.ts b/components/TempDesignSystem/Form/Country/countries.ts index db9dfa906..6349414b0 100644 --- a/components/TempDesignSystem/Form/Country/countries.ts +++ b/components/TempDesignSystem/Form/Country/countries.ts @@ -1,256 +1,261 @@ -export const countries = [ - { name: "Afghanistan", code: "AF" }, - { name: "Albania", code: "AL" }, - { name: "Algeria", code: "DZ" }, - { name: "American Samoa", code: "AS" }, - { name: "Andorra", code: "AD" }, - { name: "Angola", code: "AO" }, - { name: "Anguilla", code: "AI" }, - { name: "Antarctica", code: "AQ" }, - { name: "Antigua and Barbuda", code: "AG" }, - { name: "Argentina", code: "AR" }, - { name: "Armenia", code: "AM" }, - { name: "Aruba", code: "AW" }, - { name: "Australia", code: "AU" }, - { name: "Austria", code: "AT" }, - { name: "Azerbaijan", code: "AZ" }, - { name: "Bahamas", code: "BS" }, - { name: "Bahrain", code: "BH" }, - { name: "Bangladesh", code: "BD" }, - { name: "Barbados", code: "BB" }, - { name: "Belarus", code: "BY" }, - { name: "Belgium", code: "BE" }, - { name: "Belize", code: "BZ" }, - { name: "Benin", code: "BJ" }, - { name: "Bermuda", code: "BM" }, - { name: "Bhutan", code: "BT" }, - { name: "Bolivia", code: "BO" }, - { name: "Bonaire", code: "BQ" }, - { name: "Bosnia and Herzegovina", code: "BA" }, - { name: "Botswana", code: "BW" }, - { name: "Bouvet Island", code: "BV" }, - { name: "Brazil", code: "BR" }, - { name: "British Indian Ocean Territory", code: "IO" }, - { name: "Brunei Darussalam", code: "BN" }, - { name: "Bulgaria", code: "BG" }, - { name: "Burkina Faso", code: "BF" }, - { name: "Burundi", code: "BI" }, - { name: "Cambodia", code: "KH" }, - { name: "Cameroon", code: "CM" }, - { name: "Canada", code: "CA" }, - { name: "Cape Verde", code: "CV" }, - { name: "Cayman Islands", code: "KY" }, - { name: "Central African Republic", code: "CF" }, - { name: "Chad", code: "TD" }, - { name: "Chile", code: "CL" }, - { name: "China", code: "CN" }, - { name: "Christmas Island", code: "CX" }, - { name: "Cocos (Keeling) Islands", code: "CC" }, - { name: "Colombia", code: "CO" }, - { name: "Comoros", code: "KM" }, - { name: "Congo", code: "CG" }, - { name: "Congo, The Democratic Republic of the", code: "CD" }, - { name: "Cook Islands", code: "CK" }, - { name: "Costa Rica", code: "CR" }, - { name: 'Cote D"Ivoire', code: "CI" }, - { name: "Croatia", code: "HR" }, - { name: "Cuba", code: "CU" }, - { name: "Curacao", code: "CW" }, - { name: "Cyprus", code: "CY" }, - { name: "Czech Republic", code: "CZ" }, - { name: "Denmark", code: "DK" }, - { name: "Djibouti", code: "DJ" }, - { name: "Dominica", code: "DM" }, - { name: "Dominican Republic", code: "DO" }, - { name: "Ecuador", code: "EC" }, - { name: "Egypt", code: "EG" }, - { name: "El Salvador", code: "SV" }, - { name: "Equatorial Guinea", code: "GQ" }, - { name: "Eritrea", code: "ER" }, - { name: "Estonia", code: "EE" }, - { name: "Eswatini", code: "SZ" }, - { name: "Ethiopia", code: "ET" }, - { name: "Falkland Islands (Malvinas)", code: "FK" }, - { name: "Faroe Islands", code: "FO" }, - { name: "Fiji", code: "FJ" }, - { name: "Finland", code: "FI" }, - { name: "France", code: "FR" }, - { name: "French Guiana", code: "GF" }, - { name: "French Polynesia", code: "PF" }, - { name: "French Southern Territories", code: "TF" }, - { name: "Gabon", code: "GA" }, - { name: "Gambia", code: "GM" }, - { name: "Georgia", code: "GE" }, - { name: "Germany", code: "DE" }, - { name: "Ghana", code: "GH" }, - { name: "Gibraltar", code: "GI" }, - { name: "Greece", code: "GR" }, - { name: "Greenland", code: "GL" }, - { name: "Grenada", code: "GD" }, - { name: "Guadeloupe", code: "GP" }, - { name: "Guam", code: "GU" }, - { name: "Guatemala", code: "GT" }, - { name: "Guernsey", code: "GG" }, - { name: "Guinea", code: "GN" }, - { name: "Guinea-Bissau", code: "GW" }, - { name: "Guyana", code: "GY" }, - { name: "Haiti", code: "HT" }, - { name: "Heard Island and Mcdonald Islands", code: "HM" }, - { name: "Holy See (Vatican City State)", code: "VA" }, - { name: "Honduras", code: "HN" }, - { name: "Hong Kong", code: "HK" }, - { name: "Hungary", code: "HU" }, - { name: "Iceland", code: "IS" }, - { name: "India", code: "IN" }, - { name: "Indonesia", code: "ID" }, - { name: "Iran, Islamic Republic Of", code: "IR" }, - { name: "Iraq", code: "IQ" }, - { name: "Ireland", code: "IE" }, - { name: "Isle of Man", code: "IM" }, - { name: "Israel", code: "IL" }, - { name: "Italy", code: "IT" }, - { name: "Ivory Coast", code: "CI" }, - { name: "Jamaica", code: "JM" }, - { name: "Japan", code: "JP" }, - { name: "Jersey", code: "JE" }, - { name: "Jordan", code: "JO" }, - { name: "Kazakhstan", code: "KZ" }, - { name: "Kenya", code: "KE" }, - { name: "Kiribati", code: "KI" }, - { name: 'Korea, Democratic People"S Republic of', code: "KP" }, - { name: "Korea, Republic of", code: "KR" }, - { name: "Kuwait", code: "KW" }, - { name: "Kyrgyzstan", code: "KG" }, - { name: 'Lao People"S Democratic Republic', code: "LA" }, - { name: "Laos", code: "LA" }, - { name: "Latvia", code: "LV" }, - { name: "Lebanon", code: "LB" }, - { name: "Lesotho", code: "LS" }, - { name: "Liberia", code: "LR" }, - { name: "Libyan Arab Jamahiriya", code: "LY" }, - { name: "Liechtenstein", code: "LI" }, - { name: "Lithuania", code: "LT" }, - { name: "Luxembourg", code: "LU" }, - { name: "Macao", code: "MO" }, - { name: "Macedonia, The Former Yugoslav Republic of", code: "MK" }, - { name: "Madagascar", code: "MG" }, - { name: "Malawi", code: "MW" }, - { name: "Malaysia", code: "MY" }, - { name: "Maldives", code: "MV" }, - { name: "Mali", code: "ML" }, - { name: "Malta", code: "MT" }, - { name: "Marshall Islands", code: "MH" }, - { name: "Martinique", code: "MQ" }, - { name: "Mauritania", code: "MR" }, - { name: "Mauritius", code: "MU" }, - { name: "Mayotte", code: "YT" }, - { name: "Mexico", code: "MX" }, - { name: "Micronesia, Federated States of", code: "FM" }, - { name: "Moldova, Republic of", code: "MD" }, - { name: "Monaco", code: "MC" }, - { name: "Mongolia", code: "MN" }, - { name: "Montenegro", code: "ME" }, - { name: "Montserrat", code: "MS" }, - { name: "Morocco", code: "MA" }, - { name: "Mozambique", code: "MZ" }, - { name: "Myanmar", code: "MM" }, - { name: "Namibia", code: "NA" }, - { name: "Nauru", code: "NR" }, - { name: "Nepal", code: "NP" }, - { name: "Netherlands", code: "NL" }, - { name: "Netherlands Antilles", code: "AN" }, - { name: "New Caledonia", code: "NC" }, - { name: "New Zealand", code: "NZ" }, - { name: "Nicaragua", code: "NI" }, - { name: "Niger", code: "NE" }, - { name: "Nigeria", code: "NG" }, - { name: "Niue", code: "NU" }, - { name: "Norfolk Island", code: "NF" }, - { name: "Northern Mariana Islands", code: "MP" }, - { name: "Norway", code: "NO" }, - { name: "Oman", code: "OM" }, - { name: "Pakistan", code: "PK" }, - { name: "Palau", code: "PW" }, - { name: "Palestinian Territory, Occupied", code: "PS" }, - { name: "Panama", code: "PA" }, - { name: "Papua New Guinea", code: "PG" }, - { name: "Paraguay", code: "PY" }, - { name: "Peru", code: "PE" }, - { name: "Philippines", code: "PH" }, - { name: "Pitcairn", code: "PN" }, - { name: "Poland", code: "PL" }, - { name: "Portugal", code: "PT" }, - { name: "Puerto Rico", code: "PR" }, - { name: "Qatar", code: "QA" }, - { name: "RWANDA", code: "RW" }, - { name: "Reunion", code: "RE" }, - { name: "Romania", code: "RO" }, - { name: "Russian Federation", code: "RU" }, - { name: "Saint Barthelemy", code: "BL" }, - { name: "Saint Helena", code: "SH" }, - { name: "Saint Kitts and Nevis", code: "KN" }, - { name: "Saint Lucia", code: "LC" }, - { name: "Saint Martin", code: "MF" }, - { name: "Saint Pierre and Miquelon", code: "PM" }, - { name: "Saint Vincent and the Grenadines", code: "VC" }, - { name: "Samoa", code: "WS" }, - { name: "San Marino", code: "SM" }, - { name: "Sao Tome and Principe", code: "ST" }, - { name: "Saudi Arabia", code: "SA" }, - { name: "Senegal", code: "SN" }, - { name: "Serbia", code: "RS" }, - { name: "Seychelles", code: "SC" }, - { name: "Sierra Leone", code: "SL" }, - { name: "Singapore", code: "SG" }, - { name: "Sint Maarten", code: "SX" }, - { name: "Slovakia", code: "SK" }, - { name: "Slovenia", code: "SI" }, - { name: "Solomon Islands", code: "SB" }, - { name: "Somalia", code: "SO" }, - { name: "South Africa", code: "ZA" }, - { name: "South Georgia and the South Sandwich Islands", code: "GS" }, - { name: "South Sudan", code: "SS" }, - { name: "Spain", code: "ES" }, - { name: "Sri Lanka", code: "LK" }, - { name: "Sudan", code: "SD" }, - { name: "Suriname", code: "SR" }, - { name: "Svalbard and Jan Mayen", code: "SJ" }, - { name: "Swaziland", code: "SZ" }, - { name: "Sweden", code: "SE" }, - { name: "Switzerland", code: "CH" }, - { name: "Syrian Arab Republic", code: "SY" }, - { name: "Taiwan", code: "TW" }, - { name: "Tajikistan", code: "TJ" }, - { name: "Tanzania, United Republic of", code: "TZ" }, - { name: "Thailand", code: "TH" }, - { name: "Timor-Leste", code: "TL" }, - { name: "Togo", code: "TG" }, - { name: "Tokelau", code: "TK" }, - { name: "Tonga", code: "TO" }, - { name: "Trinidad and Tobago", code: "TT" }, - { name: "Tunisia", code: "TN" }, - { name: "Turkey", code: "TR" }, - { name: "Turkmenistan", code: "TM" }, - { name: "Turks and Caicos Islands", code: "TC" }, - { name: "Tuvalu", code: "TV" }, - { name: "Uganda", code: "UG" }, - { name: "Ukraine", code: "UA" }, - { name: "United Arab Emirates", code: "AE" }, - { name: "United Kingdom", code: "GB" }, - { name: "United States", code: "US" }, - { name: "United States Minor Outlying Islands", code: "UM" }, - { name: "Uruguay", code: "UY" }, - { name: "Uzbekistan", code: "UZ" }, - { name: "Vanuatu", code: "VU" }, - { name: "Venezuela", code: "VE" }, - { name: "Viet Nam", code: "VN" }, - { name: "Vietnam", code: "VN" }, - { name: "Virgin Islands, British", code: "VG" }, - { name: "Virgin Islands, U.S.", code: "VI" }, - { name: "Wallis and Futuna", code: "WF" }, - { name: "Western Sahara", code: "EH" }, - { name: "Yemen", code: "YE" }, - { name: "Zambia", code: "ZM" }, - { name: "Zimbabwe", code: "ZW" }, - { name: "Åland Islands", code: "AX" }, -] +export const countriesMap = { + Afghanistan: "AF", + Albania: "AL", + Algeria: "DZ", + "American Samoa": "AS", + Andorra: "AD", + Angola: "AO", + Anguilla: "AI", + Antarctica: "AQ", + "Antigua and Barbuda": "AG", + Argentina: "AR", + Armenia: "AM", + Aruba: "AW", + Australia: "AU", + Austria: "AT", + Azerbaijan: "AZ", + Bahamas: "BS", + Bahrain: "BH", + Bangladesh: "BD", + Barbados: "BB", + Belarus: "BY", + Belgium: "BE", + Belize: "BZ", + Benin: "BJ", + Bermuda: "BM", + Bhutan: "BT", + Bolivia: "BO", + Bonaire: "BQ", + "Bosnia and Herzegovina": "BA", + Botswana: "BW", + "Bouvet Island": "BV", + Brazil: "BR", + "British Indian Ocean Territory": "IO", + "Brunei Darussalam": "BN", + Bulgaria: "BG", + "Burkina Faso": "BF", + Burundi: "BI", + Cambodia: "KH", + Cameroon: "CM", + Canada: "CA", + "Cape Verde": "CV", + "Cayman Islands": "KY", + "Central African Republic": "CF", + Chad: "TD", + Chile: "CL", + China: "CN", + "Christmas Island": "CX", + "Cocos (Keeling) Islands": "CC", + Colombia: "CO", + Comoros: "KM", + Congo: "CG", + "Congo, The Democratic Republic of the": "CD", + "Cook Islands": "CK", + "Costa Rica": "CR", + 'Cote D"Ivoire': "CI", + Croatia: "HR", + Cuba: "CU", + Curacao: "CW", + Cyprus: "CY", + "Czech Republic": "CZ", + Denmark: "DK", + Djibouti: "DJ", + Dominica: "DM", + "Dominican Republic": "DO", + Ecuador: "EC", + Egypt: "EG", + "El Salvador": "SV", + "Equatorial Guinea": "GQ", + Eritrea: "ER", + Estonia: "EE", + Eswatini: "SZ", + Ethiopia: "ET", + "Falkland Islands (Malvinas)": "FK", + "Faroe Islands": "FO", + Fiji: "FJ", + Finland: "FI", + France: "FR", + "French Guiana": "GF", + "French Polynesia": "PF", + "French Southern Territories": "TF", + Gabon: "GA", + Gambia: "GM", + Georgia: "GE", + Germany: "DE", + Ghana: "GH", + Gibraltar: "GI", + Greece: "GR", + Greenland: "GL", + Grenada: "GD", + Guadeloupe: "GP", + Guam: "GU", + Guatemala: "GT", + Guernsey: "GG", + Guinea: "GN", + "Guinea-Bissau": "GW", + Guyana: "GY", + Haiti: "HT", + "Heard Island and Mcdonald Islands": "HM", + "Holy See (Vatican City State)": "VA", + Honduras: "HN", + "Hong Kong": "HK", + Hungary: "HU", + Iceland: "IS", + India: "IN", + Indonesia: "ID", + "Iran, Islamic Republic Of": "IR", + Iraq: "IQ", + Ireland: "IE", + "Isle of Man": "IM", + Israel: "IL", + Italy: "IT", + "Ivory Coast": "CI", + Jamaica: "JM", + Japan: "JP", + Jersey: "JE", + Jordan: "JO", + Kazakhstan: "KZ", + Kenya: "KE", + Kiribati: "KI", + 'Korea, Democratic People"S Republic of': "KP", + "Korea, Republic of": "KR", + Kuwait: "KW", + Kyrgyzstan: "KG", + 'Lao People"S Democratic Republic': "LA", + Laos: "LA", + Latvia: "LV", + Lebanon: "LB", + Lesotho: "LS", + Liberia: "LR", + "Libyan Arab Jamahiriya": "LY", + Liechtenstein: "LI", + Lithuania: "LT", + Luxembourg: "LU", + Macao: "MO", + "Macedonia, The Former Yugoslav Republic of": "MK", + Madagascar: "MG", + Malawi: "MW", + Malaysia: "MY", + Maldives: "MV", + Mali: "ML", + Malta: "MT", + "Marshall Islands": "MH", + Martinique: "MQ", + Mauritania: "MR", + Mauritius: "MU", + Mayotte: "YT", + Mexico: "MX", + "Micronesia, Federated States of": "FM", + "Moldova, Republic of": "MD", + Monaco: "MC", + Mongolia: "MN", + Montenegro: "ME", + Montserrat: "MS", + Morocco: "MA", + Mozambique: "MZ", + Myanmar: "MM", + Namibia: "NA", + Nauru: "NR", + Nepal: "NP", + Netherlands: "NL", + "Netherlands Antilles": "AN", + "New Caledonia": "NC", + "New Zealand": "NZ", + Nicaragua: "NI", + Niger: "NE", + Nigeria: "NG", + Niue: "NU", + "Norfolk Island": "NF", + "Northern Mariana Islands": "MP", + Norway: "NO", + Oman: "OM", + Pakistan: "PK", + Palau: "PW", + "Palestinian Territory, Occupied": "PS", + Panama: "PA", + "Papua New Guinea": "PG", + Paraguay: "PY", + Peru: "PE", + Philippines: "PH", + Pitcairn: "PN", + Poland: "PL", + Portugal: "PT", + "Puerto Rico": "PR", + Qatar: "QA", + RWANDA: "RW", + Reunion: "RE", + Romania: "RO", + "Russian Federation": "RU", + "Saint Barthelemy": "BL", + "Saint Helena": "SH", + "Saint Kitts and Nevis": "KN", + "Saint Lucia": "LC", + "Saint Martin": "MF", + "Saint Pierre and Miquelon": "PM", + "Saint Vincent and the Grenadines": "VC", + Samoa: "WS", + "San Marino": "SM", + "Sao Tome and Principe": "ST", + "Saudi Arabia": "SA", + Senegal: "SN", + Serbia: "RS", + Seychelles: "SC", + "Sierra Leone": "SL", + Singapore: "SG", + "Sint Maarten": "SX", + Slovakia: "SK", + Slovenia: "SI", + "Solomon Islands": "SB", + Somalia: "SO", + "South Africa": "ZA", + "South Georgia and the South Sandwich Islands": "GS", + "South Sudan": "SS", + Spain: "ES", + "Sri Lanka": "LK", + Sudan: "SD", + Suriname: "SR", + "Svalbard and Jan Mayen": "SJ", + Swaziland: "SZ", + Sweden: "SE", + Switzerland: "CH", + "Syrian Arab Republic": "SY", + Taiwan: "TW", + Tajikistan: "TJ", + "Tanzania, United Republic of": "TZ", + Thailand: "TH", + "Timor-Leste": "TL", + Togo: "TG", + Tokelau: "TK", + Tonga: "TO", + "Trinidad and Tobago": "TT", + Tunisia: "TN", + Turkey: "TR", + Turkmenistan: "TM", + "Turks and Caicos Islands": "TC", + Tuvalu: "TV", + Uganda: "UG", + Ukraine: "UA", + "United Arab Emirates": "AE", + "United Kingdom": "GB", + "United States": "US", + "United States Minor Outlying Islands": "UM", + Uruguay: "UY", + Uzbekistan: "UZ", + Vanuatu: "VU", + Venezuela: "VE", + "Viet Nam": "VN", + Vietnam: "VN", + "Virgin Islands, British": "VG", + "Virgin Islands, U.S.": "VI", + "Wallis and Futuna": "WF", + "Western Sahara": "EH", + Yemen: "YE", + Zambia: "ZM", + Zimbabwe: "ZW", + "Åland Islands": "AX", +} as const + +export const countries = Object.keys(countriesMap).map((country) => ({ + code: countriesMap[country as keyof typeof countriesMap], + name: country as keyof typeof countriesMap, +})) diff --git a/components/TempDesignSystem/Form/Country/index.tsx b/components/TempDesignSystem/Form/Country/index.tsx index 3394d4144..e30e175d0 100644 --- a/components/TempDesignSystem/Form/Country/index.tsx +++ b/components/TempDesignSystem/Form/Country/index.tsx @@ -35,21 +35,8 @@ export default function CountrySelect({ name, rules: registerOptions, }) - const [selectedKey, setSelectedKey] = useState(() => { - if (field.value) { - const selected = countries.find( - (country) => - country.name === field.value || country.code === field.value - ) - if (selected) { - return selected.name - } - } - return "" - }) function handleChange(country: Key) { - setSelectedKey(String(country)) setValue(name, country) } @@ -69,7 +56,7 @@ export default function CountrySelect({ onBlur={field.onBlur} onSelectionChange={handleChange} ref={field.ref} - selectedKey={selectedKey} + selectedKey={field.value} >
{country.name} diff --git a/components/TempDesignSystem/Form/Phone/index.tsx b/components/TempDesignSystem/Form/Phone/index.tsx index aadf36761..981939060 100644 --- a/components/TempDesignSystem/Form/Phone/index.tsx +++ b/components/TempDesignSystem/Form/Phone/index.tsx @@ -12,7 +12,8 @@ import styles from "./phone.module.css" import type { PhoneProps } from "./phone" export default function Phone({ - name = "phone", + countrySelectName = "country", + name = "phoneNumber", placeholder = "", registerOptions = { required: true, @@ -20,11 +21,11 @@ export default function Phone({ }: PhoneProps) { const phoneRef = useRef(null) const { control, formState } = useFormContext() - const countryValue = useWatch({ name: "country" }) + const countryValue = useWatch({ name: countrySelectName }) const defaultCountry = getCountry({ countries: defaultCountries, - field: "name", - value: countryValue, + field: "iso2", + value: String(countryValue).toLowerCase(), }) /** * Holds the previous selected country to be able to update @@ -44,13 +45,13 @@ export default function Phone({ (country: string) => { const selectedCountry = getCountry({ countries: defaultCountries, - field: "name", - value: country, + field: "iso2", + value: country.toLowerCase(), }) if (selectedCountry) { phoneRef.current?.setCountry(selectedCountry.iso2) - prevSelectedCountry.current = country + prevSelectedCountry.current = country.toLowerCase() } }, [phoneRef.current, prevSelectedCountry.current] @@ -63,8 +64,8 @@ export default function Phone({ if (prevSelectedCountry.current !== countryValue) { const selectedCountryPrev = getCountry({ countries: defaultCountries, - field: "name", - value: prevSelectedCountry.current, + field: "iso2", + value: prevSelectedCountry.current.toLowerCase(), }) if ( field.value.replace("+", "") === selectedCountryPrev?.dialCode diff --git a/components/TempDesignSystem/Form/Phone/phone.ts b/components/TempDesignSystem/Form/Phone/phone.ts index b1cd0697f..57142e829 100644 --- a/components/TempDesignSystem/Form/Phone/phone.ts +++ b/components/TempDesignSystem/Form/Phone/phone.ts @@ -1,6 +1,7 @@ import type { RegisterOptions } from "react-hook-form" export type PhoneProps = { + countrySelectName?: string name?: string placeholder?: string registerOptions?: RegisterOptions diff --git a/env/server.ts b/env/server.ts index 084f79cd9..469b7fe17 100644 --- a/env/server.ts +++ b/env/server.ts @@ -10,6 +10,7 @@ export const env = createEnv({ isServer: typeof window === "undefined" || "Deno" in window, server: { ADOBE_SCRIPT_SRC: z.string().optional(), + API_BASEURL: z.string(), BUILD_ID: z.string().default("64rYXBu8o2eHp0Jf"), CMS_ACCESS_TOKEN: z.string(), CMS_API_KEY: z.string(), @@ -39,6 +40,7 @@ export const env = createEnv({ emptyStringAsUndefined: true, runtimeEnv: { ADOBE_SCRIPT_SRC: process.env.ADOBE_SCRIPT_SRC, + API_BASEURL: process.env.API_BASEURL, BUILD_ID: process.env.BUILD_ID, CMS_ACCESS_TOKEN: process.env.CMS_ACCESS_TOKEN, CMS_API_KEY: process.env.CMS_API_KEY, diff --git a/lib/api/endpoints.ts b/lib/api/endpoints.ts new file mode 100644 index 000000000..85ae58336 --- /dev/null +++ b/lib/api/endpoints.ts @@ -0,0 +1,10 @@ +/** + * Nested enum requires namespace + */ +export namespace endpoints { + export const enum v0 { + profile = "profile/v0/Profile", + } +} + +export type Endpoint = endpoints.v0 diff --git a/lib/api/index.ts b/lib/api/index.ts new file mode 100644 index 000000000..e8adb5be8 --- /dev/null +++ b/lib/api/index.ts @@ -0,0 +1,60 @@ +import merge from "deepmerge" + +import { env } from "@/env/server" + +import type { + RequestOptionsWithJSONBody, + RequestOptionsWithOutBody, +} from "@/types/fetch" +import type { Endpoint } from "./endpoints" + +export { endpoints } from "./endpoints" + +const defaultOptions: RequestInit = { + headers: { + "Content-Type": "application/json", + }, + mode: "cors", +} + +export async function get( + endpoint: Endpoint, + options: RequestOptionsWithOutBody +) { + defaultOptions.method = "GET" + return fetch(`${env.API_BASEURL}/${endpoint}`, merge(defaultOptions, options)) +} + +export async function patch( + endpoint: Endpoint, + options: RequestOptionsWithJSONBody +) { + const { body, ...requestOptions } = options + defaultOptions.body = JSON.stringify(body) + defaultOptions.method = "PATCH" + return fetch( + `${env.API_BASEURL}/${endpoint}`, + merge(defaultOptions, requestOptions) + ) +} + +export async function post( + endpoint: Endpoint, + options: RequestOptionsWithJSONBody +) { + const { body, ...requestOptions } = options + defaultOptions.body = JSON.stringify(body) + defaultOptions.method = "POST" + return fetch( + `${env.API_BASEURL}/${endpoint}`, + merge(defaultOptions, requestOptions) + ) +} + +export async function remove( + endpoint: Endpoint, + options: RequestOptionsWithOutBody +) { + defaultOptions.method = "DELETE" + return fetch(`${env.API_BASEURL}/${endpoint}`, merge(defaultOptions, options)) +} diff --git a/lib/trpc/Provider.tsx b/lib/trpc/Provider.tsx index d1ef255c7..4153002ad 100644 --- a/lib/trpc/Provider.tsx +++ b/lib/trpc/Provider.tsx @@ -4,11 +4,11 @@ import { useState } from "react" import { QueryClient, QueryClientProvider } from "@tanstack/react-query" import { httpBatchLink } from "@trpc/client" import { env } from "@/env/client" -import { api } from "./client" +import { trpc } from "./client" import { transformer } from "@/server/transformer" function initializeTrpcClient() { - return api.createClient({ + return trpc.createClient({ links: [ httpBatchLink({ transformer, @@ -25,8 +25,8 @@ export default function TrpcProvider({ children }: React.PropsWithChildren) { const [queryClient] = useState(() => new QueryClient({})) const [trpcClient] = useState(() => initializeTrpcClient()) return ( - + {children} - + ) } diff --git a/lib/trpc/client.ts b/lib/trpc/client.ts index 6bed0d895..f3be409d3 100644 --- a/lib/trpc/client.ts +++ b/lib/trpc/client.ts @@ -2,4 +2,4 @@ import { createTRPCReact } from "@trpc/react-query" import type { AppRouter } from "@/server" -export const api = createTRPCReact({}) +export const trpc = createTRPCReact({}) diff --git a/package-lock.json b/package-lock.json index 0f32e8603..b35017f0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "@trpc/server": "^11.0.0-next-beta.318", "class-variance-authority": "^0.7.0", "dayjs": "^1.11.10", + "deepmerge": "^4.3.1", "graphql": "^16.8.1", "graphql-request": "^6.1.0", "graphql-tag": "^2.12.6", @@ -4091,6 +4092,14 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/defer-to-connect": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", diff --git a/package.json b/package.json index 3b3e438c5..2c4077e7f 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@trpc/server": "^11.0.0-next-beta.318", "class-variance-authority": "^0.7.0", "dayjs": "^1.11.10", + "deepmerge": "^4.3.1", "graphql": "^16.8.1", "graphql-request": "^6.1.0", "graphql-tag": "^2.12.6", diff --git a/server/errors/trpc.ts b/server/errors/trpc.ts index 56fe8db92..851f1729d 100644 --- a/server/errors/trpc.ts +++ b/server/errors/trpc.ts @@ -11,12 +11,10 @@ export function unauthorizedError() { }) } -export function internalServerError() { +export function forbiddenError() { return new TRPCError({ - code: TRPC_ERROR_CODES_BY_NUMBER[ - TRPC_ERROR_CODES_BY_KEY.INTERNAL_SERVER_ERROR - ], - message: `Internal Server Error!`, + code: TRPC_ERROR_CODES_BY_NUMBER[TRPC_ERROR_CODES_BY_KEY.FORBIDDEN], + message: `You do not have permission!`, }) } @@ -26,3 +24,12 @@ export function badRequestError(msg = "Bad request!") { message: msg, }) } + +export function internalServerError() { + return new TRPCError({ + code: TRPC_ERROR_CODES_BY_NUMBER[ + TRPC_ERROR_CODES_BY_KEY.INTERNAL_SERVER_ERROR + ], + message: `Internal Server Error!`, + }) +} diff --git a/server/routers/user/output.ts b/server/routers/user/output.ts index 5de77f4f3..f7f0ae23e 100644 --- a/server/routers/user/output.ts +++ b/server/routers/user/output.ts @@ -1,27 +1,24 @@ +import { countriesMap } from "@/components/TempDesignSystem/Form/Country/countries" import { z } from "zod" -/** - * Return value from jsonplaceholder.com/users/1 - * Add proper user object expectation when fetching - * from Scandic API - */ export const getUserSchema = z.object({ address: z.object({ - city: z.string(), - geo: z.object({}), - street: z.string(), - suite: z.string(), - zipcode: z.string(), - }), - company: z.object({ - bs: z.string(), - catchPhrase: z.string(), - name: z.string(), + city: z.string().optional(), + country: z.nativeEnum(countriesMap), + streetAddress: z.string().optional(), + zipCode: z.string(), }), email: z.string().email(), - id: z.number(), + gender: z.string(), name: z.string(), - phone: z.string(), - username: z.string(), - website: z.string(), + language: z.string(), + lastName: z.string(), + membership: z.object({ + currentPoints: z.number(), + expirationDate: z.string(), + membershipNumber: z.string(), + memberSince: z.string(), + }), + phoneNumber: z.string(), + profileId: z.string(), }) diff --git a/server/routers/user/query.ts b/server/routers/user/query.ts index 60b3186ca..2b7c4d9a3 100644 --- a/server/routers/user/query.ts +++ b/server/routers/user/query.ts @@ -1,112 +1,77 @@ -import { badRequestError, internalServerError } from "@/server/errors/trpc" +import * as api from "@/lib/api" +import { benefits, extendedUser, nextLevelPerks } from "./temp" +import { + badRequestError, + forbiddenError, + internalServerError, + unauthorizedError, +} from "@/server/errors/trpc" import { protectedProcedure, router } from "@/server/trpc" import { getUserSchema } from "./output" -import { extendedUser } from "./temp" +function fakingRequest(payload: T): Promise { + return new Promise((resolve) => { + setTimeout(() => { + resolve(payload) + }, 1500) + }) +} export const userQueryRouter = router({ - get: protectedProcedure.query(async function (opts) { - // TODO: Make request to get user data from Scandic API - const response = await fetch( - "https://jsonplaceholder.typicode.com/users/1", - { + get: protectedProcedure.query(async function ({ ctx }) { + try { + const apiResponse = await api.get(api.endpoints.v0.profile, { cache: "no-store", + headers: { + Authorization: `Bearer ${ctx.session.token.access_token}`, + }, + }) + + if (!apiResponse.ok) { + switch (apiResponse.status) { + case 400: + throw badRequestError() + case 401: + throw unauthorizedError() + case 403: + throw forbiddenError() + default: + throw internalServerError() + } } - ) - if (!response.ok) { + const apiJson = await apiResponse.json() + if (!apiJson.data?.length) { + throw internalServerError() + } + + const verifiedData = getUserSchema.safeParse(apiJson.data[0].attributes) + if (!verifiedData.success) { + console.info(`Get User - Verified Data Error`) + console.error(verifiedData.error) + throw badRequestError() + } + + return { + ...extendedUser, + ...verifiedData.data, + firstName: verifiedData.data.name, + name: `${verifiedData.data.name} ${verifiedData.data.lastName}`, + } + } catch (error) { + console.info(`GEt User Error`) + console.error(error) throw internalServerError() } - const json = await response.json() - const validJson = getUserSchema.safeParse(json) - if (!validJson.success) { - throw badRequestError() - } - - const [firstname, lastname] = validJson.data.name.split(" ") - const [phone] = validJson.data.phone.split(" ") - return { - ...validJson.data, - firstname, - lastname, - phone, - ...extendedUser, - } }), benefits: router({ current: protectedProcedure.query(async function (opts) { // TODO: Make request to get user data from Scandic API - - const currentBenefits = [ - { - id: 1, - value: "€5 voucher", - explanation: "to spend in bar & restaurant for each night", - subtitle: - "Lorem ipsum dolor sit amet consectetur. Pharetra lectus sagittis turpis blandit feugiat amet enim massa.", - href: "#", - }, - { - id: 2, - value: "Breakfast to go", - explanation: "for early birds, when staying", - subtitle: - "Lorem ipsum dolor sit amet consectetur. Pharetra lectus sagittis turpis blandit feugiat amet enim massa.", - href: "#", - }, - { - id: 3, - value: "15% discount", - explanation: "in the restaurant & the bar", - subtitle: - "Lorem ipsum dolor sit amet consectetur. Pharetra lectus sagittis turpis blandit feugiat amet enim massa.", - href: "#", - }, - ] - const response = currentBenefits - return response - - // if (!response.ok) { - // throw internalServerError() - // } - // const json = await response.json() - // const validJson = getUserSchema.parse(json) - // if (!validJson) { - // throw badRequestError() - // } - // return validJson + return await fakingRequest(benefits) }), nextLevel: protectedProcedure.query(async function (opts) { // TODO: Make request to get user data from Scandic API - - const nextLevelPerks = [ - { - id: 1, - - explanation: "Free soft drink voucher for the kids when staying", - }, - { - id: 2, - - explanation: "Free early check in", - }, - { - id: 3, - explanation: "25% extra bonus points on each stay", - }, - ] - const response = { nextLevel: "Close Friend", perks: nextLevelPerks } - return response - - // if (!response.ok) { - // throw internalServerError() - // } - // const json = await response.json() - // const validJson = getUserSchema.parse(json) - // if (!validJson) { - // throw badRequestError() - // } - // return validJson + return await fakingRequest(nextLevelPerks) }), }), }) diff --git a/server/routers/user/temp.ts b/server/routers/user/temp.ts index a58c9afe4..b7d555b3c 100644 --- a/server/routers/user/temp.ts +++ b/server/routers/user/temp.ts @@ -1,5 +1,32 @@ import { dt } from "@/lib/dt" +export const benefits = [ + { + id: 1, + value: "€5 voucher", + explanation: "to spend in bar & restaurant for each night", + subtitle: + "Lorem ipsum dolor sit amet consectetur. Pharetra lectus sagittis turpis blandit feugiat amet enim massa.", + href: "#", + }, + { + id: 2, + value: "Breakfast to go", + explanation: "for early birds, when staying", + subtitle: + "Lorem ipsum dolor sit amet consectetur. Pharetra lectus sagittis turpis blandit feugiat amet enim massa.", + href: "#", + }, + { + id: 3, + value: "15% discount", + explanation: "in the restaurant & the bar", + subtitle: + "Lorem ipsum dolor sit amet consectetur. Pharetra lectus sagittis turpis blandit feugiat amet enim massa.", + href: "#", + }, +] + export const challenges = { journeys: [ { @@ -27,6 +54,26 @@ export const challenges = { ], } +export const nextLevelPerks = { + nextLevel: "Close Friend", + perks: [ + { + id: 1, + + explanation: "Free soft drink voucher for the kids when staying", + }, + { + id: 2, + + explanation: "Free early check in", + }, + { + id: 3, + explanation: "25% extra bonus points on each stay", + }, + ], +} + export const shortcuts = [ { href: "#", @@ -88,13 +135,9 @@ export const stays = [ ] export const extendedUser = { - country: "United States", dob: dt("1977-07-05").format("YYYY-MM-DD"), journeys: challenges.journeys, - membershipId: 30812404844732, nights: 14, - points: 20720, - qualifyingPoints: 5000, shortcuts, stays, victories: challenges.victories, diff --git a/server/trpc.ts b/server/trpc.ts index 53aa09f96..428e38796 100644 --- a/server/trpc.ts +++ b/server/trpc.ts @@ -14,17 +14,16 @@ export const publicProcedure = t.procedure export const protectedProcedure = t.procedure.use(async function (opts) { const authRequired = opts.meta?.authRequired ?? true const session = await opts.ctx.auth() - if (authRequired) { - if (!session?.user) { - throw unauthorizedError() - } - } else { - if (env.NODE_ENV === "development") { - console.info( - `❌❌❌❌ You are opting out of authorization, if its done on purpose maybe you should use the publicProcedure instead. ❌❌❌❌` - ) - console.info(`path: ${opts.path} | type: ${opts.type}`) - } + + if (!authRequired && env.NODE_ENV === "development") { + console.info( + `❌❌❌❌ You are opting out of authorization, if its done on purpose maybe you should use the publicProcedure instead. ❌❌❌❌` + ) + console.info(`path: ${opts.path} | type: ${opts.type}`) + } + + if (!session?.user) { + throw unauthorizedError() } return opts.next({ diff --git a/types/auth.d.ts b/types/auth.d.ts index 1b6db41a6..ace3d3ff9 100644 --- a/types/auth.d.ts +++ b/types/auth.d.ts @@ -1,4 +1,4 @@ -import "next-auth" +import type { JWT } from "next-auth/jwt" // Module augmentation // https://authjs.dev/getting-started/typescript#popular-interfaces-to-augment @@ -20,10 +20,7 @@ declare module "next-auth" { /** * Returned by `useSession`, `auth`, contains information about the active session. */ - interface Session {} -} - -declare module "next-auth/jwt" { - /** Returned by the `jwt` callback and `auth`, when using JWT sessions */ - interface JWT {} + interface Session { + token: JWT + } } diff --git a/types/components/myPages/myProfile/container.ts b/types/components/myPages/myProfile/container.ts new file mode 100644 index 000000000..8569083aa --- /dev/null +++ b/types/components/myPages/myProfile/container.ts @@ -0,0 +1,6 @@ +import type { CardProps } from "./card/card" +import type { User } from "@/types/user" + +export interface ContainerProps extends CardProps { + user: User +} diff --git a/types/components/myPages/myProfile/profile.ts b/types/components/myPages/myProfile/profile.ts deleted file mode 100644 index 7cd4d7752..000000000 --- a/types/components/myPages/myProfile/profile.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { User } from "@/types/user" - -export interface ProfileProps extends React.HTMLAttributes { - user: User -} diff --git a/types/fetch.ts b/types/fetch.ts new file mode 100644 index 000000000..585421fd4 --- /dev/null +++ b/types/fetch.ts @@ -0,0 +1,7 @@ +export interface RequestOptionsWithJSONBody + extends Omit { + body: Record +} + +export interface RequestOptionsWithOutBody + extends Omit {} diff --git a/types/jwt.d.ts b/types/jwt.d.ts new file mode 100644 index 000000000..97fa95704 --- /dev/null +++ b/types/jwt.d.ts @@ -0,0 +1,8 @@ +// Module augmentation +// https://authjs.dev/getting-started/typescript#popular-interfaces-to-augment +declare module "next-auth/jwt" { + /** Returned by the `jwt` callback and `auth`, when using JWT sessions */ + interface JWT { + access_token: string + } +} diff --git a/types/next/error.ts b/types/next/error.ts new file mode 100644 index 000000000..6694d1357 --- /dev/null +++ b/types/next/error.ts @@ -0,0 +1,8 @@ +interface NextError extends Error { + digest?: string +} + +export interface ErrorPage { + error: NextError + reset: () => void +} diff --git a/types/user.ts b/types/user.ts index 35d94c110..599da605f 100644 --- a/types/user.ts +++ b/types/user.ts @@ -24,16 +24,15 @@ type Victory = { title: string } +/** + * All extended field needs to be added by API team to response or + * we have to get the values from elsewhere + */ export interface User extends z.infer { - country: string dob: string - firstname: string + firstName: string journeys: Journey[] - lastname: string - membershipId: number nights: number - points: number - qualifyingPoints: number shortcuts: ShortcutLink[] stays: Stay[] victories: Victory[]