From 9bdc27d75e95ffa9bfca3ac53d907cd5dff1d520 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Tue, 25 Jun 2024 09:23:13 +0545 Subject: [PATCH 1/5] refactor(user): add grace period --- packages/user/src/supertokens/helpers.ts | 59 ++++++++++++++----- packages/user/src/supertokens/index.ts | 7 +-- .../src/supertokens/profileValidationClaim.ts | 38 +++++++++--- 3 files changed, 74 insertions(+), 30 deletions(-) diff --git a/packages/user/src/supertokens/helpers.ts b/packages/user/src/supertokens/helpers.ts index b16cac159..74bee7c90 100644 --- a/packages/user/src/supertokens/helpers.ts +++ b/packages/user/src/supertokens/helpers.ts @@ -92,28 +92,55 @@ const isEmailVerified = async (): Promise => { * - `undefined` if the profile validation is disabled in the api. * */ -const isProfileCompleted = async (): Promise => { - if (await Session.doesSessionExist()) { - const validatorFailures = await Session.validateClaims({ - overrideGlobalClaimValidators: () => { - // Only check for profile validation - return [ProfileValidationClaim.validators.isTrue()]; - }, - }); +const isProfileCompleted = async (): Promise< + | { isVerified: true } + | { isVerified: false; isGracePeriodEnded?: boolean } + | undefined +> => { + const profileClaim = await Session.getClaimValue({ + claim: new ProfileValidationClaim(), + }); + + console.log("profileValidation claim value", profileClaim); + + if (!profileClaim) { + return; + } - if ( - validatorFailures.length && - validatorFailures[0].reason.actualValue === undefined - ) { - return; - } + if (profileClaim.isVerified) { + return { isVerified: true }; + } - return !validatorFailures.length; + if (profileClaim.gracePeriodEndsAt) { + return { + isVerified: false, + isGracePeriodEnded: profileClaim.gracePeriodEndsAt < Date.now(), + }; } - return false; + return { isVerified: false }; }; +// const isProfileCompleted = async (): Promise => { +// if (await Session.doesSessionExist()) { +// const validatorFailures = await Session.validateClaims({ +// overrideGlobalClaimValidators: () => { +// // Only check for profile validation +// return [ProfileValidationClaim.validators.]; +// }, +// }); +// +// if ( +// validatorFailures.length && +// validatorFailures[0].reason.actualValue === undefined +// ) { +// return; +// } +// +// return !validatorFail; +// } +// }; + export { getUserRoles, isEmailVerified, diff --git a/packages/user/src/supertokens/index.ts b/packages/user/src/supertokens/index.ts index 6e693a751..37b9c8d77 100644 --- a/packages/user/src/supertokens/index.ts +++ b/packages/user/src/supertokens/index.ts @@ -6,8 +6,6 @@ import ThirdPartyEmailPassword from "supertokens-web-js/recipe/thirdpartyemailpa import { SUPERTOKENS_API_BASE_PATH_DEFAULT } from "@/constants"; -import ProfileValidationClaim from "./profileValidationClaim"; - const superTokens = (config: AppConfig) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const recipeLists: Array = [ @@ -18,10 +16,7 @@ const superTokens = (config: AppConfig) => { return { ...originalImplementation, getGlobalClaimValidators: function (input) { - return [ - ...input.claimValidatorsAddedByOtherRecipes, - ProfileValidationClaim.validators.isTrue(), - ]; + return [...input.claimValidatorsAddedByOtherRecipes]; }, }; }, diff --git a/packages/user/src/supertokens/profileValidationClaim.ts b/packages/user/src/supertokens/profileValidationClaim.ts index 55870af42..07180547f 100644 --- a/packages/user/src/supertokens/profileValidationClaim.ts +++ b/packages/user/src/supertokens/profileValidationClaim.ts @@ -1,10 +1,32 @@ -import { BooleanClaim } from "supertokens-web-js/recipe/session"; - -const ProfileValidationClaim = new BooleanClaim({ - id: "profileValidation", - // eslint-disable-next-line @typescript-eslint/no-empty-function - refresh: async () => {}, - defaultMaxAgeInSeconds: undefined, -}); +import { SessionClaim } from "supertokens-web-js/lib/build/recipe/session"; + +interface Response { + gracePeriodEndsAt?: number; + isVerified: boolean; +} + +class ProfileValidationClaim implements SessionClaim { + public static defaultMaxAgeInSeconds: number | undefined = undefined; + public static id = "profileValidation"; + + constructor() { + /* empty */ + } + + getLastFetchedTime(): number | undefined { + return undefined; + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + getValueFromPayload(payload: any): Response | undefined { + return payload[ProfileValidationClaim.id] !== undefined + ? payload[ProfileValidationClaim.id].v + : undefined; + } + + async refresh(): Promise { + /* empty */ + } +} export default ProfileValidationClaim; From 68bc8b8fe6bf7cd65ae3f57ca170fec669817d41 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Tue, 25 Jun 2024 09:38:27 +0545 Subject: [PATCH 2/5] feat(user): add isOnGracePeriod on user type --- packages/user/src/context/UserProvider.tsx | 5 ++++ packages/user/src/index.ts | 7 +++++- packages/user/src/supertokens/helpers.ts | 28 +++++++++++----------- packages/user/src/supertokens/index.ts | 15 +----------- packages/user/src/types/types.ts | 1 + 5 files changed, 27 insertions(+), 29 deletions(-) diff --git a/packages/user/src/context/UserProvider.tsx b/packages/user/src/context/UserProvider.tsx index ddd43ed63..5e328fc8d 100644 --- a/packages/user/src/context/UserProvider.tsx +++ b/packages/user/src/context/UserProvider.tsx @@ -6,6 +6,7 @@ import { getUserData, removeUserData, setUserData } from "../helpers"; import { useConfig } from "../hooks"; import { isEmailVerified, + isOnGracePeriod, isProfileCompleted, verifySessionRoles, } from "../supertokens/helpers"; @@ -70,6 +71,10 @@ const UserProvider = ({ children }: Properties) => { userData.isProfileCompleted = await isProfileCompleted(); + if (userData.isProfileCompleted === false) { + userData.isOnGracePeriod = await isOnGracePeriod(); + } + await setUserData(userData); setUser(userData); diff --git a/packages/user/src/index.ts b/packages/user/src/index.ts index 69888827a..a67d1d1ad 100644 --- a/packages/user/src/index.ts +++ b/packages/user/src/index.ts @@ -37,7 +37,11 @@ import superTokens from "./supertokens"; import changePassword from "./supertokens/change-password"; import { forgotPassword } from "./supertokens/forgot-password"; import googleLogin from "./supertokens/google-login"; -import { verifySessionRoles, isProfileCompleted } from "./supertokens/helpers"; +import { + verifySessionRoles, + isProfileCompleted, + isOnGracePeriod, +} from "./supertokens/helpers"; import login from "./supertokens/login"; import logout from "./supertokens/logout"; import ProfileValidationClaim from "./supertokens/profileValidationClaim"; @@ -131,6 +135,7 @@ export { superTokens, useEmailVerification, useFirstUserSignup, + isOnGracePeriod, isProfileCompleted, useProfileCompletion, useUser, diff --git a/packages/user/src/supertokens/helpers.ts b/packages/user/src/supertokens/helpers.ts index 74bee7c90..45e8c24e3 100644 --- a/packages/user/src/supertokens/helpers.ts +++ b/packages/user/src/supertokens/helpers.ts @@ -92,33 +92,32 @@ const isEmailVerified = async (): Promise => { * - `undefined` if the profile validation is disabled in the api. * */ -const isProfileCompleted = async (): Promise< - | { isVerified: true } - | { isVerified: false; isGracePeriodEnded?: boolean } - | undefined -> => { +const isProfileCompleted = async (): Promise => { const profileClaim = await Session.getClaimValue({ claim: new ProfileValidationClaim(), }); - console.log("profileValidation claim value", profileClaim); - if (!profileClaim) { return; } - if (profileClaim.isVerified) { - return { isVerified: true }; + return profileClaim.isVerified; +}; + +const isOnGracePeriod = async (): Promise => { + const profileClaim = await Session.getClaimValue({ + claim: new ProfileValidationClaim(), + }); + + if (!profileClaim || profileClaim.isVerified) { + return; } if (profileClaim.gracePeriodEndsAt) { - return { - isVerified: false, - isGracePeriodEnded: profileClaim.gracePeriodEndsAt < Date.now(), - }; + return profileClaim.gracePeriodEndsAt >= Date.now(); } - return { isVerified: false }; + return false; }; // const isProfileCompleted = async (): Promise => { @@ -144,6 +143,7 @@ const isProfileCompleted = async (): Promise< export { getUserRoles, isEmailVerified, + isOnGracePeriod, isProfileCompleted, verifySessionRoles, }; diff --git a/packages/user/src/supertokens/index.ts b/packages/user/src/supertokens/index.ts index 37b9c8d77..49c7470f9 100644 --- a/packages/user/src/supertokens/index.ts +++ b/packages/user/src/supertokens/index.ts @@ -9,20 +9,7 @@ import { SUPERTOKENS_API_BASE_PATH_DEFAULT } from "@/constants"; const superTokens = (config: AppConfig) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const recipeLists: Array = [ - Session.init({ - ...(config?.user?.supertokens?.sessionConfig || {}), - override: { - functions: (originalImplementation) => { - return { - ...originalImplementation, - getGlobalClaimValidators: function (input) { - return [...input.claimValidatorsAddedByOtherRecipes]; - }, - }; - }, - ...(config?.user?.supertokens?.sessionConfig?.override || {}), - }, - }), + Session.init(config?.user?.supertokens?.sessionConfig), ThirdPartyEmailPassword.init( config?.user?.supertokens?.thirdPartyEmailPasswordConfig, ), diff --git a/packages/user/src/types/types.ts b/packages/user/src/types/types.ts index 3115e0d4f..970d78692 100644 --- a/packages/user/src/types/types.ts +++ b/packages/user/src/types/types.ts @@ -4,6 +4,7 @@ export interface UserType extends EmailPasswordUserType { disabled?: boolean; givenName: string | null; isEmailVerified?: boolean; + isOnGracePeriod?: boolean; isProfileCompleted?: boolean; lastLoginAt: number; middleNames: string | null; From 078a610f21e6eeda854dc574ee79a1dcdbacc03d Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Tue, 25 Jun 2024 10:32:25 +0545 Subject: [PATCH 3/5] chore(user): update locales --- packages/i18n/src/locales/en/user.json | 3 +- packages/i18n/src/locales/fr/user.json | 3 +- packages/user/src/context/UserProvider.tsx | 6 ++-- .../src/views/ProfileCompletionReminder.tsx | 34 +++++++++++++++++++ 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/packages/i18n/src/locales/en/user.json b/packages/i18n/src/locales/en/user.json index 82dc52244..c773ff26d 100644 --- a/packages/i18n/src/locales/en/user.json +++ b/packages/i18n/src/locales/en/user.json @@ -226,7 +226,8 @@ "reminder": "Please complete your profile to access this page." }, "buttons": { - "gotoProfile": "Go to profile" + "gotoProfile": "Go to profile", + "skipProfile": "Skip" } } } diff --git a/packages/i18n/src/locales/fr/user.json b/packages/i18n/src/locales/fr/user.json index c893eb796..e03471302 100644 --- a/packages/i18n/src/locales/fr/user.json +++ b/packages/i18n/src/locales/fr/user.json @@ -225,7 +225,8 @@ "reminder": "Please complete your profile to access this page. (fr)" }, "buttons": { - "gotoProfile": "Go to profile (fr)" + "gotoProfile": "Go to profile (fr)", + "skipProfile": "Skip" } } } diff --git a/packages/user/src/context/UserProvider.tsx b/packages/user/src/context/UserProvider.tsx index 5e328fc8d..f965cc010 100644 --- a/packages/user/src/context/UserProvider.tsx +++ b/packages/user/src/context/UserProvider.tsx @@ -71,9 +71,9 @@ const UserProvider = ({ children }: Properties) => { userData.isProfileCompleted = await isProfileCompleted(); - if (userData.isProfileCompleted === false) { - userData.isOnGracePeriod = await isOnGracePeriod(); - } + // if (userData.isProfileCompleted === false) { + // userData.isOnGracePeriod = await isOnGracePeriod(); + // } await setUserData(userData); diff --git a/packages/user/src/views/ProfileCompletionReminder.tsx b/packages/user/src/views/ProfileCompletionReminder.tsx index 02961ac41..3a2f0b1f1 100644 --- a/packages/user/src/views/ProfileCompletionReminder.tsx +++ b/packages/user/src/views/ProfileCompletionReminder.tsx @@ -1,15 +1,44 @@ import { useTranslation } from "@dzangolab/react-i18n"; import { Button, Page } from "@dzangolab/react-ui"; +import { useEffect, useState } from "react"; + +import { isOnGracePeriod } from ".."; +import { useUser } from "../hooks"; const ProfileCompletionReminder = ({ centered = true, onClick, + onSkip, }: { centered?: boolean; onClick?: () => void; + onSkip?: () => void; }) => { + const [isGracePeriod, setIsGracePeriod] = useState( + false, + ); + + const { user, setUser } = useUser(); const { t } = useTranslation("user"); + const handleSkip = async () => { + await onSkip?.(); + + if (user) { + user.isOnGracePeriod = true; + + setUser(user); + } + }; + + useEffect(() => { + const getGracePeriod = async () => { + setIsGracePeriod(await isOnGracePeriod()); + }; + + getGracePeriod(); + }, []); + return ( {t("profileCompletion.buttons.gotoProfile")} + {isGracePeriod && ( + + )} ); }; From f1236ab8c9e9a461747246614d791dcaef1d57e7 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Tue, 25 Jun 2024 10:41:14 +0545 Subject: [PATCH 4/5] chore(user): add css for profile completion reminder --- packages/i18n/src/locales/fr/user.json | 2 +- packages/user/src/assets/css/profile-completion-reminder.css | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/i18n/src/locales/fr/user.json b/packages/i18n/src/locales/fr/user.json index e03471302..c1fd211b3 100644 --- a/packages/i18n/src/locales/fr/user.json +++ b/packages/i18n/src/locales/fr/user.json @@ -226,7 +226,7 @@ }, "buttons": { "gotoProfile": "Go to profile (fr)", - "skipProfile": "Skip" + "skipProfile": "Skip (fr)" } } } diff --git a/packages/user/src/assets/css/profile-completion-reminder.css b/packages/user/src/assets/css/profile-completion-reminder.css index 3237b8e93..ba4832ae2 100644 --- a/packages/user/src/assets/css/profile-completion-reminder.css +++ b/packages/user/src/assets/css/profile-completion-reminder.css @@ -9,4 +9,5 @@ .profile-completion-reminder > .dz-page-content button { margin-top: 1rem; + width: 18rem; } From fed5e3500911d2c98398f246667d438210e8f65c Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Tue, 25 Jun 2024 11:07:07 +0545 Subject: [PATCH 5/5] chore: remove comment --- packages/user/src/supertokens/helpers.ts | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/packages/user/src/supertokens/helpers.ts b/packages/user/src/supertokens/helpers.ts index 45e8c24e3..6aadca55d 100644 --- a/packages/user/src/supertokens/helpers.ts +++ b/packages/user/src/supertokens/helpers.ts @@ -120,26 +120,6 @@ const isOnGracePeriod = async (): Promise => { return false; }; -// const isProfileCompleted = async (): Promise => { -// if (await Session.doesSessionExist()) { -// const validatorFailures = await Session.validateClaims({ -// overrideGlobalClaimValidators: () => { -// // Only check for profile validation -// return [ProfileValidationClaim.validators.]; -// }, -// }); -// -// if ( -// validatorFailures.length && -// validatorFailures[0].reason.actualValue === undefined -// ) { -// return; -// } -// -// return !validatorFail; -// } -// }; - export { getUserRoles, isEmailVerified,