diff --git a/packages/bedrock-components/package-lock.json b/packages/bedrock-components/package-lock.json index f91429beb5..2c8ea8cd9c 100644 --- a/packages/bedrock-components/package-lock.json +++ b/packages/bedrock-components/package-lock.json @@ -1,17 +1,17 @@ { "name": "@saasquatch/bedrock-components", - "version": "1.4.8", + "version": "1.5.0-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@saasquatch/bedrock-components", - "version": "1.4.8", + "version": "1.5.0-0", "hasInstallScript": true, "license": "MIT", "dependencies": { "@raisins/stencil-docs-target": "^1.1.0", - "@saasquatch/component-boilerplate": "^1.6.7", + "@saasquatch/component-boilerplate": "^1.6.8-0", "@saasquatch/dom-context-hooks": "^1.0.5", "@saasquatch/stencil-hooks": "^2.0.2", "@saasquatch/stencilbook": "^1.0.0", @@ -2658,12 +2658,12 @@ } }, "node_modules/@saasquatch/component-boilerplate": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@saasquatch/component-boilerplate/-/component-boilerplate-1.6.7.tgz", - "integrity": "sha512-jmAr6TTg3NiKMvkgmTl+6r5EmDsyODWZzsyFMG4pVuZdAND/yVx51j+WyXc+yLcOdSgSS4rs55vNI/TBY/b6sA==", + "version": "1.6.8-0", + "resolved": "https://registry.npmjs.org/@saasquatch/component-boilerplate/-/component-boilerplate-1.6.8-0.tgz", + "integrity": "sha512-OeNxcLdM4sSUKJFhyW2al9KPRqdzpuFPu6E9C0EwuBfml1GGx5pdlqN2ePwzy3EEwW6BSQNf4o/nJDXDzBOJHw==", "dependencies": { "@formatjs/intl": "^1.8.2", - "@saasquatch/component-environment": "^1.0.8", + "@saasquatch/component-environment": "^1.0.9-0", "@saasquatch/dom-context-hooks": "^1.0.5", "@saasquatch/stencil-hooks": "^2.0.2", "@saasquatch/universal-hooks": "^1.0.0", @@ -2695,9 +2695,9 @@ } }, "node_modules/@saasquatch/component-environment": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@saasquatch/component-environment/-/component-environment-1.0.8.tgz", - "integrity": "sha512-ORs5VIo9RnW/8yb72eWpmo2sjQOhs2DiO4IJ3gLmKTC5lYy7iRu5LpOR352o6dwdbdbwGG7JZtKgrqYYUl61Yw==", + "version": "1.0.9-0", + "resolved": "https://registry.npmjs.org/@saasquatch/component-environment/-/component-environment-1.0.9-0.tgz", + "integrity": "sha512-Ulxv0p5PyFHTbY0ard/bdgtTZqsq9ald0Pxx2qVpNuHlgc8BkzivpF8jEG2JFLdBfRHAyr+wiRZOUVe1hUbpHQ==", "dependencies": { "@wry/equality": "^0.5.2", "dom-context": "^1.3.1", @@ -14027,12 +14027,12 @@ } }, "@saasquatch/component-boilerplate": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@saasquatch/component-boilerplate/-/component-boilerplate-1.6.7.tgz", - "integrity": "sha512-jmAr6TTg3NiKMvkgmTl+6r5EmDsyODWZzsyFMG4pVuZdAND/yVx51j+WyXc+yLcOdSgSS4rs55vNI/TBY/b6sA==", + "version": "1.6.8-0", + "resolved": "https://registry.npmjs.org/@saasquatch/component-boilerplate/-/component-boilerplate-1.6.8-0.tgz", + "integrity": "sha512-OeNxcLdM4sSUKJFhyW2al9KPRqdzpuFPu6E9C0EwuBfml1GGx5pdlqN2ePwzy3EEwW6BSQNf4o/nJDXDzBOJHw==", "requires": { "@formatjs/intl": "^1.8.2", - "@saasquatch/component-environment": "^1.0.8", + "@saasquatch/component-environment": "^1.0.9-0", "@saasquatch/dom-context-hooks": "^1.0.5", "@saasquatch/stencil-hooks": "^2.0.2", "@saasquatch/universal-hooks": "^1.0.0", @@ -14063,9 +14063,9 @@ } }, "@saasquatch/component-environment": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@saasquatch/component-environment/-/component-environment-1.0.8.tgz", - "integrity": "sha512-ORs5VIo9RnW/8yb72eWpmo2sjQOhs2DiO4IJ3gLmKTC5lYy7iRu5LpOR352o6dwdbdbwGG7JZtKgrqYYUl61Yw==", + "version": "1.0.9-0", + "resolved": "https://registry.npmjs.org/@saasquatch/component-environment/-/component-environment-1.0.9-0.tgz", + "integrity": "sha512-Ulxv0p5PyFHTbY0ard/bdgtTZqsq9ald0Pxx2qVpNuHlgc8BkzivpF8jEG2JFLdBfRHAyr+wiRZOUVe1hUbpHQ==", "requires": { "@wry/equality": "^0.5.2", "dom-context": "^1.3.1", diff --git a/packages/bedrock-components/package.json b/packages/bedrock-components/package.json index 3ebcb65c6a..0e07c4eafc 100644 --- a/packages/bedrock-components/package.json +++ b/packages/bedrock-components/package.json @@ -1,7 +1,7 @@ { "name": "@saasquatch/bedrock-components", "title": "Bedrock Components", - "version": "1.4.8", + "version": "1.5.0-0", "description": "Component library that adds advanced logic to your widgets and pages. Built and maintained by Saasquatch.", "icon": "https://res.cloudinary.com/saasquatch/image/upload/v1652219900/squatch-assets/For_Bedrock.svg", "main": "dist/index.cjs.js", @@ -36,7 +36,7 @@ }, "dependencies": { "@raisins/stencil-docs-target": "^1.1.0", - "@saasquatch/component-boilerplate": "^1.6.7", + "@saasquatch/component-boilerplate": "^1.6.8-0", "@saasquatch/dom-context-hooks": "^1.0.5", "@saasquatch/stencil-hooks": "^2.0.2", "@saasquatch/stencilbook": "^1.0.0", diff --git a/packages/bedrock-components/src/components/sqb-auth-template-switch/useAuthTemplateSwitch.ts b/packages/bedrock-components/src/components/sqb-auth-template-switch/useAuthTemplateSwitch.ts index c1a7b443fa..87b4858492 100644 --- a/packages/bedrock-components/src/components/sqb-auth-template-switch/useAuthTemplateSwitch.ts +++ b/packages/bedrock-components/src/components/sqb-auth-template-switch/useAuthTemplateSwitch.ts @@ -5,15 +5,15 @@ import { useTemplateChildren } from '../../utils/useTemplateChildren'; const debug = debugFn('sq:useAuthTemplateSwitch'); export function useAuthTemplateSwitch() { - const authToken = useToken(); + const token = useToken(); const [container, setContainer] = useState(undefined); const [slot, setSlot] = useState(undefined); - if (!authToken) debug('No user identity available'); + if (!token) debug('No user identity available'); const updateTemplates = useCallback(() => { - const isAuth = !!authToken; + const isAuth = !!token; const templates = slot.querySelectorAll(`template`); const template = Array.from(templates).find(t => t.slot === (isAuth ? 'logged-in' : 'logged-out')); @@ -69,7 +69,7 @@ export function useAuthTemplateSwitch() { target.style.height = '25px'; }); } - }, [container, slot, authToken]); + }, [container, slot, token]); useEffect(() => { if (!container || !slot) { @@ -81,7 +81,7 @@ export function useAuthTemplateSwitch() { updateTemplates(); return useTemplateChildren({ parent: slot, callback: updateTemplates }); - }, [slot, container, authToken]); + }, [slot, container, token]); return { setSlot, diff --git a/packages/component-boilerplate/package-lock.json b/packages/component-boilerplate/package-lock.json index 85ceddaa56..fa227fe9b1 100644 --- a/packages/component-boilerplate/package-lock.json +++ b/packages/component-boilerplate/package-lock.json @@ -1,16 +1,16 @@ { "name": "@saasquatch/component-boilerplate", - "version": "1.6.7", + "version": "1.6.8-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@saasquatch/component-boilerplate", - "version": "1.6.7", + "version": "1.6.8-0", "license": "MIT", "dependencies": { "@formatjs/intl": "^1.8.2", - "@saasquatch/component-environment": "^1.0.8", + "@saasquatch/component-environment": "^1.0.9-0", "@saasquatch/dom-context-hooks": "^1.0.5", "@saasquatch/stencil-hooks": "^2.0.2", "@saasquatch/universal-hooks": "^1.0.0", @@ -3096,9 +3096,9 @@ } }, "node_modules/@saasquatch/component-environment": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@saasquatch/component-environment/-/component-environment-1.0.8.tgz", - "integrity": "sha512-ORs5VIo9RnW/8yb72eWpmo2sjQOhs2DiO4IJ3gLmKTC5lYy7iRu5LpOR352o6dwdbdbwGG7JZtKgrqYYUl61Yw==", + "version": "1.0.9-0", + "resolved": "https://registry.npmjs.org/@saasquatch/component-environment/-/component-environment-1.0.9-0.tgz", + "integrity": "sha512-Ulxv0p5PyFHTbY0ard/bdgtTZqsq9ald0Pxx2qVpNuHlgc8BkzivpF8jEG2JFLdBfRHAyr+wiRZOUVe1hUbpHQ==", "dependencies": { "@wry/equality": "^0.5.2", "dom-context": "^1.3.1", @@ -18888,9 +18888,9 @@ } }, "@saasquatch/component-environment": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@saasquatch/component-environment/-/component-environment-1.0.8.tgz", - "integrity": "sha512-ORs5VIo9RnW/8yb72eWpmo2sjQOhs2DiO4IJ3gLmKTC5lYy7iRu5LpOR352o6dwdbdbwGG7JZtKgrqYYUl61Yw==", + "version": "1.0.9-0", + "resolved": "https://registry.npmjs.org/@saasquatch/component-environment/-/component-environment-1.0.9-0.tgz", + "integrity": "sha512-Ulxv0p5PyFHTbY0ard/bdgtTZqsq9ald0Pxx2qVpNuHlgc8BkzivpF8jEG2JFLdBfRHAyr+wiRZOUVe1hUbpHQ==", "requires": { "@wry/equality": "^0.5.2", "dom-context": "^1.3.1", diff --git a/packages/component-boilerplate/package.json b/packages/component-boilerplate/package.json index b72124e750..fcb78b3123 100644 --- a/packages/component-boilerplate/package.json +++ b/packages/component-boilerplate/package.json @@ -1,6 +1,6 @@ { "name": "@saasquatch/component-boilerplate", - "version": "1.6.7", + "version": "1.6.8-0", "private": false, "description": "Boilerplate for writing web components for the SaaSquatch widget environments", "source": "src/index.ts", @@ -40,7 +40,7 @@ }, "dependencies": { "@formatjs/intl": "^1.8.2", - "@saasquatch/component-environment": "^1.0.8", + "@saasquatch/component-environment": "^1.0.9-0", "@saasquatch/dom-context-hooks": "^1.0.5", "@saasquatch/stencil-hooks": "^2.0.2", "@saasquatch/universal-hooks": "^1.0.0", diff --git a/packages/component-boilerplate/src/hooks/environment/index.ts b/packages/component-boilerplate/src/hooks/environment/index.ts index 545ca8a466..47e9fbea05 100644 --- a/packages/component-boilerplate/src/hooks/environment/index.ts +++ b/packages/component-boilerplate/src/hooks/environment/index.ts @@ -12,6 +12,7 @@ export const useEngagementMedium = getEngagementMedium; export { getEnvironmentSDK, setUserIdentity, + setVerificationContext, setProgramId, isDemo, DecodedSquatchJWT, @@ -20,3 +21,4 @@ export { export * from "./useLocale"; export * from "./useProgramId"; export * from "./useUserIdentity"; +export * from "./useVerificationContext"; diff --git a/packages/component-boilerplate/src/hooks/environment/useQueryToken.ts b/packages/component-boilerplate/src/hooks/environment/useQueryToken.ts new file mode 100644 index 0000000000..a3f1399297 --- /dev/null +++ b/packages/component-boilerplate/src/hooks/environment/useQueryToken.ts @@ -0,0 +1,9 @@ +import { useUserIdentity } from "./useUserIdentity"; +import { useVerificationContext } from "./useVerificationContext"; + +export function useQueryToken() { + const verificationToken = useVerificationContext()?.token; + const userJwt = useUserIdentity()?.jwt; + + return verificationToken || userJwt; +} diff --git a/packages/component-boilerplate/src/hooks/environment/useVerificationContext.ts b/packages/component-boilerplate/src/hooks/environment/useVerificationContext.ts new file mode 100644 index 0000000000..47d32372de --- /dev/null +++ b/packages/component-boilerplate/src/hooks/environment/useVerificationContext.ts @@ -0,0 +1,30 @@ +import { + checkVerificationToken, + isDemo, + lazilyStartVerificationContext, + setVerificationContext, + VERIFICATION_CONTEXT_NAME, + VerificationContext, +} from "@saasquatch/component-environment"; +import { useDomContext } from "@saasquatch/dom-context-hooks"; +import { useHost } from "../useHost"; + +export function useVerificationContext(): VerificationContext | undefined { + lazilyStartVerificationContext(); + const host = useHost(); + const context = useDomContext(host, VERIFICATION_CONTEXT_NAME) as + | VerificationContext + | undefined; + + const valid = checkVerificationToken(context?.token); + if (!isDemo() && context && !valid) { + setVerificationContext(undefined); + return undefined; + } + + return context; +} + +export function useVerificationToken() { + return useVerificationContext()?.token; +} diff --git a/packages/component-boilerplate/src/hooks/graphql/useBaseQuery.ts b/packages/component-boilerplate/src/hooks/graphql/useBaseQuery.ts index 26ca8ea333..f186361a06 100644 --- a/packages/component-boilerplate/src/hooks/graphql/useBaseQuery.ts +++ b/packages/component-boilerplate/src/hooks/graphql/useBaseQuery.ts @@ -80,12 +80,12 @@ function reducer( export function useBaseQuery( query: GqlType, initialState: BaseQueryData, - options = { batch: true } + options = { batch: true, protected: false } ): [ BaseQueryData, (variables: unknown, skipLoading?: boolean) => Promise ] { - const client = useGraphQLClient(); + const client = useGraphQLClient({ protected: options.protected }); const [state, dispatch] = useReducer, Action>( reducer, initialState diff --git a/packages/component-boilerplate/src/hooks/graphql/useGraphQLClient.ts b/packages/component-boilerplate/src/hooks/graphql/useGraphQLClient.ts index e52254fd36..24e3b09479 100644 --- a/packages/component-boilerplate/src/hooks/graphql/useGraphQLClient.ts +++ b/packages/component-boilerplate/src/hooks/graphql/useGraphQLClient.ts @@ -3,6 +3,12 @@ import memoize from "fast-memoize"; import { BatchedGraphQLClient } from "../../BatchedGraphQLClient"; import { useAppDomain, useTenantAlias, useToken } from "../environment"; import { useHost } from "../useHost"; +import { useQueryToken } from "../environment/useQueryToken"; +import { useVerificationContext } from "../environment/useVerificationContext"; + +type Options = { + protected?: boolean; +}; export const GRAPHQL_CONTEXT = "sq:graphql-client"; @@ -23,8 +29,9 @@ function createGraphQlClient( export const memoizedGraphQLClient = memoize(createGraphQlClient); -function useGraphQLClient(): BatchedGraphQLClient { +function useGraphQLClient(options?: Options): BatchedGraphQLClient { const token = useToken(); + const verifiedToken = useVerificationContext()?.token; const appDomain = useAppDomain(); const tenantAlias = useTenantAlias(); @@ -32,7 +39,7 @@ function useGraphQLClient(): BatchedGraphQLClient { const localClient: BatchedGraphQLClient = memoizedGraphQLClient( appDomain, tenantAlias, - token + options?.protected ? verifiedToken : token ); const host = useHost(); const clientFromContext = useDomContext( diff --git a/packages/component-boilerplate/src/hooks/graphql/useQuery.ts b/packages/component-boilerplate/src/hooks/graphql/useQuery.ts index c4a8ae6389..8f4cc733d1 100644 --- a/packages/component-boilerplate/src/hooks/graphql/useQuery.ts +++ b/packages/component-boilerplate/src/hooks/graphql/useQuery.ts @@ -36,13 +36,14 @@ export function useQuery( query: GqlType, variables: unknown, skip?: boolean, - options?: { batch?: boolean } + options?: { batch?: boolean; protected?: boolean } ): QueryData { const [state, update] = useBaseQuery( query, initialQueryState as BaseQueryData, { batch: true, + protected: false, ...(options || {}), } ); diff --git a/packages/component-environment/package.json b/packages/component-environment/package.json index 85b1f085cf..1fdd6d4222 100644 --- a/packages/component-environment/package.json +++ b/packages/component-environment/package.json @@ -1,6 +1,6 @@ { "name": "@saasquatch/component-environment", - "version": "1.0.8", + "version": "1.0.9-0", "description": "Environment and contexts for SaaSquatch components", "main": "dist/index.js", "module": "dist/index.mjs", diff --git a/packages/component-environment/src/contexts/UserIdentityContext.ts b/packages/component-environment/src/contexts/UserIdentityContext.ts index e4cd790831..789ef8db1d 100644 --- a/packages/component-environment/src/contexts/UserIdentityContext.ts +++ b/packages/component-environment/src/contexts/UserIdentityContext.ts @@ -62,6 +62,7 @@ export function userIdentityFromJwt(jwt?: string): UserIdentity | undefined { let userId: string | undefined = undefined; let accountId: string | undefined = undefined; + let email: string | undefined = undefined; if (isDecodedWidgetAPIJWT(decoded)) { // Pull the accountId and userId from the subject and Base64-decode them @@ -72,6 +73,7 @@ export function userIdentityFromJwt(jwt?: string): UserIdentity | undefined { } else if (isDecodedSquatchJWT(decoded)) { accountId = decoded.user.accountId; userId = decoded.user.id; + email = decoded.user.email; } if (!userId || !accountId) { @@ -88,6 +90,7 @@ export function userIdentityFromJwt(jwt?: string): UserIdentity | undefined { return { id: userId, accountId: accountId, + email, jwt, }; } catch (e) { @@ -112,6 +115,7 @@ function _getInitialValue(): UserIdentity | undefined { return { id: sdk.widgetIdent.userId, accountId: sdk.widgetIdent.accountId, + email: sdk.widgetIdent.email, jwt: sdk.widgetIdent.token, }; case "SquatchPortal": diff --git a/packages/component-environment/src/contexts/VerificationContext.ts b/packages/component-environment/src/contexts/VerificationContext.ts new file mode 100644 index 0000000000..9acf3acacd --- /dev/null +++ b/packages/component-environment/src/contexts/VerificationContext.ts @@ -0,0 +1,62 @@ +import equal from "@wry/equality"; +import decode from "jwt-decode"; +import { ContextProvider } from "dom-context"; +import { + DecodedSquatchJWT, + VERIFICATION_CONTEXT_NAME, + VerificationContext, +} from "../types"; +import { getUserIdentity } from "./UserIdentityContext"; +import { debug as _debug } from "../debug"; + +const debug = (...args: any[]) => _debug(VERIFICATION_CONTEXT_NAME, ...args); + +export function lazilyStartVerificationContext() { + let globalProvider = window.squatchVerification; + if (!globalProvider) { + globalProvider = new ContextProvider({ + element: document.documentElement, + // Unless we want to do session management, verified status should be renewed on each widget load + initialState: undefined, + contextName: VERIFICATION_CONTEXT_NAME, + }).start(); + + window.squatchVerification = globalProvider; + } + + return globalProvider; +} + +export function checkVerificationToken(token: string) { + if (!token) return false; + + const decoded = decode(token); + + // Check if token is valid object + const isSquatchJWT = + decoded.user && decoded.user.id && decoded.user.accountId; + if (!isSquatchJWT) return false; + + // Check if verification JWT credentials match with current user + const currentUser = getUserIdentity(); + if ( + decoded.user.id !== currentUser?.id || + decoded.user.accountId !== currentUser?.accountId + ) + return false; + + return true; +} + +export function setVerificationContext(context: VerificationContext) { + const globalProvider = lazilyStartVerificationContext(); + + if (!equal(globalProvider.context, context)) { + debug(`Setting verification context to [${JSON.stringify(context)}]`); + globalProvider.context = context; + } +} + +export function getVerificationContext() { + return window.squatchVerification?.context; +} diff --git a/packages/component-environment/src/index.ts b/packages/component-environment/src/index.ts index 5501ee171e..7e809a3712 100644 --- a/packages/component-environment/src/index.ts +++ b/packages/component-environment/src/index.ts @@ -5,3 +5,4 @@ export * from "./history"; export * from "./contexts/UserIdentityContext"; export * from "./contexts/LocaleContext"; export * from "./contexts/ProgramContext"; +export * from "./contexts/VerificationContext"; diff --git a/packages/component-environment/src/types.ts b/packages/component-environment/src/types.ts index 4faea9e7ae..cd4d0aa3b2 100644 --- a/packages/component-environment/src/types.ts +++ b/packages/component-environment/src/types.ts @@ -7,17 +7,25 @@ declare global { squatchUserIdentity?: ContextProvider; squatchLocale?: ContextProvider; squatchProgramId?: ContextProvider; + squatchVerification?: ContextProvider; } } export type UserContextName = "sq:user-identity"; export type LocaleContextName = "sq:locale"; export type ProgramContextName = "sq:program-id"; +export type VerificationContextName = "sq:verification"; export const USER_CONTEXT_NAME: UserContextName = "sq:user-identity" as const; export const LOCALE_CONTEXT_NAME: LocaleContextName = "sq:locale" as const; export const PROGRAM_CONTEXT_NAME: ProgramContextName = "sq:program-id" as const; +export const VERIFICATION_CONTEXT_NAME: VerificationContextName = + "sq:verification" as const; + +export type VerificationContext = { + token: string; +}; /** * The value stored in the UserContext @@ -25,6 +33,7 @@ export const PROGRAM_CONTEXT_NAME: ProgramContextName = export type UserIdentity = { id: string; accountId: string; + email?: string; jwt?: string; managedIdentity?: { email: string; @@ -43,6 +52,7 @@ export interface DecodedSquatchJWT { user: { accountId: string; id: string; + email?: string; }; } @@ -68,6 +78,7 @@ export interface WidgetIdent { token: string; userId: string; accountId: string; + email?: string; locale?: string; engagementMedium?: "POPUP" | "EMBED"; programId?: string; diff --git a/packages/end-user-testing/.gitignore b/packages/end-user-testing/.gitignore new file mode 100644 index 0000000000..a547bf36d8 --- /dev/null +++ b/packages/end-user-testing/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/packages/end-user-testing/index.html b/packages/end-user-testing/index.html new file mode 100644 index 0000000000..e6e29dad16 --- /dev/null +++ b/packages/end-user-testing/index.html @@ -0,0 +1,68 @@ + + + + + + + + Vite + TS + + + +
+ +
+
X
+
+ + + + +
+
+ + + + +
+
+
+
+
+ + + diff --git a/packages/end-user-testing/package-lock.json b/packages/end-user-testing/package-lock.json new file mode 100644 index 0000000000..ad71656bcc --- /dev/null +++ b/packages/end-user-testing/package-lock.json @@ -0,0 +1,1254 @@ +{ + "name": "end-user-testing", + "version": "0.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "end-user-testing", + "version": "0.0.0", + "devDependencies": { + "typescript": "~5.6.2", + "vite": "^5.4.10" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.3.tgz", + "integrity": "sha512-EzxVSkIvCFxUd4Mgm4xR9YXrcp976qVaHnqom/Tgm+vU79k4vV4eYTjmRvGfeoW8m9LVcsAy/lGjcgVegKEhLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.3.tgz", + "integrity": "sha512-LJc5pDf1wjlt9o/Giaw9Ofl+k/vLUaYsE2zeQGH85giX2F+wn/Cg8b3c5CDP3qmVmeO5NzwVUzQQxwZvC2eQKw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.3.tgz", + "integrity": "sha512-OuRysZ1Mt7wpWJ+aYKblVbJWtVn3Cy52h8nLuNSzTqSesYw1EuN6wKp5NW/4eSre3mp12gqFRXOKTcN3AI3LqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.3.tgz", + "integrity": "sha512-xW//zjJMlJs2sOrCmXdB4d0uiilZsOdlGQIC/jjmMWT47lkLLoB1nsNhPUcnoqyi5YR6I4h+FjBpILxbEy8JRg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.3.tgz", + "integrity": "sha512-58E0tIcwZ+12nK1WiLzHOD8I0d0kdrY/+o7yFVPRHuVGY3twBwzwDdTIBGRxLmyjciMYl1B/U515GJy+yn46qw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.3.tgz", + "integrity": "sha512-78fohrpcVwTLxg1ZzBMlwEimoAJmY6B+5TsyAZ3Vok7YabRBUvjYTsRXPTjGEvv/mfgVBepbW28OlMEz4w8wGA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.3.tgz", + "integrity": "sha512-h2Ay79YFXyQi+QZKo3ISZDyKaVD7uUvukEHTOft7kh00WF9mxAaxZsNs3o/eukbeKuH35jBvQqrT61fzKfAB/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.3.tgz", + "integrity": "sha512-Sv2GWmrJfRY57urktVLQ0VKZjNZGogVtASAgosDZ1aUB+ykPxSi3X1nWORL5Jk0sTIIwQiPH7iE3BMi9zGWfkg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.3.tgz", + "integrity": "sha512-FPoJBLsPW2bDNWjSrwNuTPUt30VnfM8GPGRoLCYKZpPx0xiIEdFip3dH6CqgoT0RnoGXptaNziM0WlKgBc+OWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.3.tgz", + "integrity": "sha512-TKxiOvBorYq4sUpA0JT+Fkh+l+G9DScnG5Dqx7wiiqVMiRSkzTclP35pE6eQQYjP4Gc8yEkJGea6rz4qyWhp3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.3.tgz", + "integrity": "sha512-v2M/mPvVUKVOKITa0oCFksnQQ/TqGrT+yD0184/cWHIu0LoIuYHwox0Pm3ccXEz8cEQDLk6FPKd1CCm+PlsISw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.3.tgz", + "integrity": "sha512-LdrI4Yocb1a/tFVkzmOE5WyYRgEBOyEhWYJe4gsDWDiwnjYKjNs7PS6SGlTDB7maOHF4kxevsuNBl2iOcj3b4A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.3.tgz", + "integrity": "sha512-d4wVu6SXij/jyiwPvI6C4KxdGzuZOvJ6y9VfrcleHTwo68fl8vZC5ZYHsCVPUi4tndCfMlFniWgwonQ5CUpQcA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.3.tgz", + "integrity": "sha512-/6bn6pp1fsCGEY5n3yajmzZQAh+mW4QPItbiWxs69zskBzJuheb3tNynEjL+mKOsUSFK11X4LYF2BwwXnzWleA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.3.tgz", + "integrity": "sha512-nBXOfJds8OzUT1qUreT/en3eyOXd2EH5b0wr2bVB5999qHdGKkzGzIyKYaKj02lXk6wpN71ltLIaQpu58YFBoQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.3.tgz", + "integrity": "sha512-ogfbEVQgIZOz5WPWXF2HVb6En+kWzScuxJo/WdQTqEgeyGkaa2ui5sQav9Zkr7bnNCLK48uxmmK0TySm22eiuw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.3.tgz", + "integrity": "sha512-ecE36ZBMLINqiTtSNQ1vzWc5pXLQHlf/oqGp/bSbi7iedcjcNb6QbCBNG73Euyy2C+l/fn8qKWEwxr+0SSfs3w==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.3.tgz", + "integrity": "sha512-vliZLrDmYKyaUoMzEbMTg2JkerfBjn03KmAw9CykO0Zzkzoyd7o3iZNam/TpyWNjNT+Cz2iO3P9Smv2wgrR+Eg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.3.tgz", + "integrity": "sha512-SLsCOnlmGt9VoZ9Ek8yBK8tAdmPHeppkw+Xa7yDlCEhDTvwYei03JlWo1fdc7YTfLZ4tD8riJCUyAgTbszk1fQ==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.27.3", + "@rollup/rollup-android-arm64": "4.27.3", + "@rollup/rollup-darwin-arm64": "4.27.3", + "@rollup/rollup-darwin-x64": "4.27.3", + "@rollup/rollup-freebsd-arm64": "4.27.3", + "@rollup/rollup-freebsd-x64": "4.27.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.27.3", + "@rollup/rollup-linux-arm-musleabihf": "4.27.3", + "@rollup/rollup-linux-arm64-gnu": "4.27.3", + "@rollup/rollup-linux-arm64-musl": "4.27.3", + "@rollup/rollup-linux-powerpc64le-gnu": "4.27.3", + "@rollup/rollup-linux-riscv64-gnu": "4.27.3", + "@rollup/rollup-linux-s390x-gnu": "4.27.3", + "@rollup/rollup-linux-x64-gnu": "4.27.3", + "@rollup/rollup-linux-x64-musl": "4.27.3", + "@rollup/rollup-win32-arm64-msvc": "4.27.3", + "@rollup/rollup-win32-ia32-msvc": "4.27.3", + "@rollup/rollup-win32-x64-msvc": "4.27.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + } + }, + "dependencies": { + "@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-android-arm-eabi": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.3.tgz", + "integrity": "sha512-EzxVSkIvCFxUd4Mgm4xR9YXrcp976qVaHnqom/Tgm+vU79k4vV4eYTjmRvGfeoW8m9LVcsAy/lGjcgVegKEhLQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-android-arm64": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.3.tgz", + "integrity": "sha512-LJc5pDf1wjlt9o/Giaw9Ofl+k/vLUaYsE2zeQGH85giX2F+wn/Cg8b3c5CDP3qmVmeO5NzwVUzQQxwZvC2eQKw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-arm64": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.3.tgz", + "integrity": "sha512-OuRysZ1Mt7wpWJ+aYKblVbJWtVn3Cy52h8nLuNSzTqSesYw1EuN6wKp5NW/4eSre3mp12gqFRXOKTcN3AI3LqA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-x64": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.3.tgz", + "integrity": "sha512-xW//zjJMlJs2sOrCmXdB4d0uiilZsOdlGQIC/jjmMWT47lkLLoB1nsNhPUcnoqyi5YR6I4h+FjBpILxbEy8JRg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-freebsd-arm64": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.3.tgz", + "integrity": "sha512-58E0tIcwZ+12nK1WiLzHOD8I0d0kdrY/+o7yFVPRHuVGY3twBwzwDdTIBGRxLmyjciMYl1B/U515GJy+yn46qw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-freebsd-x64": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.3.tgz", + "integrity": "sha512-78fohrpcVwTLxg1ZzBMlwEimoAJmY6B+5TsyAZ3Vok7YabRBUvjYTsRXPTjGEvv/mfgVBepbW28OlMEz4w8wGA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.3.tgz", + "integrity": "sha512-h2Ay79YFXyQi+QZKo3ISZDyKaVD7uUvukEHTOft7kh00WF9mxAaxZsNs3o/eukbeKuH35jBvQqrT61fzKfAB/Q==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-musleabihf": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.3.tgz", + "integrity": "sha512-Sv2GWmrJfRY57urktVLQ0VKZjNZGogVtASAgosDZ1aUB+ykPxSi3X1nWORL5Jk0sTIIwQiPH7iE3BMi9zGWfkg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-gnu": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.3.tgz", + "integrity": "sha512-FPoJBLsPW2bDNWjSrwNuTPUt30VnfM8GPGRoLCYKZpPx0xiIEdFip3dH6CqgoT0RnoGXptaNziM0WlKgBc+OWQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-musl": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.3.tgz", + "integrity": "sha512-TKxiOvBorYq4sUpA0JT+Fkh+l+G9DScnG5Dqx7wiiqVMiRSkzTclP35pE6eQQYjP4Gc8yEkJGea6rz4qyWhp3g==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.3.tgz", + "integrity": "sha512-v2M/mPvVUKVOKITa0oCFksnQQ/TqGrT+yD0184/cWHIu0LoIuYHwox0Pm3ccXEz8cEQDLk6FPKd1CCm+PlsISw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-riscv64-gnu": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.3.tgz", + "integrity": "sha512-LdrI4Yocb1a/tFVkzmOE5WyYRgEBOyEhWYJe4gsDWDiwnjYKjNs7PS6SGlTDB7maOHF4kxevsuNBl2iOcj3b4A==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-s390x-gnu": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.3.tgz", + "integrity": "sha512-d4wVu6SXij/jyiwPvI6C4KxdGzuZOvJ6y9VfrcleHTwo68fl8vZC5ZYHsCVPUi4tndCfMlFniWgwonQ5CUpQcA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-gnu": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.3.tgz", + "integrity": "sha512-/6bn6pp1fsCGEY5n3yajmzZQAh+mW4QPItbiWxs69zskBzJuheb3tNynEjL+mKOsUSFK11X4LYF2BwwXnzWleA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-musl": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.3.tgz", + "integrity": "sha512-nBXOfJds8OzUT1qUreT/en3eyOXd2EH5b0wr2bVB5999qHdGKkzGzIyKYaKj02lXk6wpN71ltLIaQpu58YFBoQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-arm64-msvc": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.3.tgz", + "integrity": "sha512-ogfbEVQgIZOz5WPWXF2HVb6En+kWzScuxJo/WdQTqEgeyGkaa2ui5sQav9Zkr7bnNCLK48uxmmK0TySm22eiuw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-ia32-msvc": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.3.tgz", + "integrity": "sha512-ecE36ZBMLINqiTtSNQ1vzWc5pXLQHlf/oqGp/bSbi7iedcjcNb6QbCBNG73Euyy2C+l/fn8qKWEwxr+0SSfs3w==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-x64-msvc": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.3.tgz", + "integrity": "sha512-vliZLrDmYKyaUoMzEbMTg2JkerfBjn03KmAw9CykO0Zzkzoyd7o3iZNam/TpyWNjNT+Cz2iO3P9Smv2wgrR+Eg==", + "dev": true, + "optional": true + }, + "@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, + "nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true + }, + "picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "dev": true, + "requires": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + } + }, + "rollup": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.3.tgz", + "integrity": "sha512-SLsCOnlmGt9VoZ9Ek8yBK8tAdmPHeppkw+Xa7yDlCEhDTvwYei03JlWo1fdc7YTfLZ4tD8riJCUyAgTbszk1fQ==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.27.3", + "@rollup/rollup-android-arm64": "4.27.3", + "@rollup/rollup-darwin-arm64": "4.27.3", + "@rollup/rollup-darwin-x64": "4.27.3", + "@rollup/rollup-freebsd-arm64": "4.27.3", + "@rollup/rollup-freebsd-x64": "4.27.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.27.3", + "@rollup/rollup-linux-arm-musleabihf": "4.27.3", + "@rollup/rollup-linux-arm64-gnu": "4.27.3", + "@rollup/rollup-linux-arm64-musl": "4.27.3", + "@rollup/rollup-linux-powerpc64le-gnu": "4.27.3", + "@rollup/rollup-linux-riscv64-gnu": "4.27.3", + "@rollup/rollup-linux-s390x-gnu": "4.27.3", + "@rollup/rollup-linux-x64-gnu": "4.27.3", + "@rollup/rollup-linux-x64-musl": "4.27.3", + "@rollup/rollup-win32-arm64-msvc": "4.27.3", + "@rollup/rollup-win32-ia32-msvc": "4.27.3", + "@rollup/rollup-win32-x64-msvc": "4.27.3", + "@types/estree": "1.0.6", + "fsevents": "~2.3.2" + } + }, + "source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true + }, + "typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true + }, + "vite": { + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "dev": true, + "requires": { + "esbuild": "^0.21.3", + "fsevents": "~2.3.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + } + } + } +} diff --git a/packages/end-user-testing/package.json b/packages/end-user-testing/package.json new file mode 100644 index 0000000000..cbdff2028d --- /dev/null +++ b/packages/end-user-testing/package.json @@ -0,0 +1,15 @@ +{ + "name": "end-user-testing", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "typescript": "~5.6.2", + "vite": "^5.4.10" + } +} diff --git a/packages/end-user-testing/public/vite.svg b/packages/end-user-testing/public/vite.svg new file mode 100644 index 0000000000..e7b8dfb1b2 --- /dev/null +++ b/packages/end-user-testing/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/end-user-testing/src/main.ts b/packages/end-user-testing/src/main.ts new file mode 100644 index 0000000000..e5e1c379e4 --- /dev/null +++ b/packages/end-user-testing/src/main.ts @@ -0,0 +1,157 @@ +import { renderWidget } from "./renderWidget"; +import "./style.css"; + +const prefix = "micro-testing"; +const defaults = { + tenantAlias: undefined, + mintSource: + "https://fast-staging.ssqt.io/npm/@saasquatch/mint-components@1.8.4/dist/mint-components/mint-components.esm.min.js", + bedrockSource: + "https://fast.ssqt.io/npm/@saasquatch/bedrock-components@1.4.5/dist/bedrock-components/bedrock-components.esm.min.js", +} as Record; + +const localStorage = window.localStorage; + +let routerScript: HTMLScriptElement | null = null; + +function get(key: string) { + const value = localStorage.getItem(`${prefix}:${key}`); + return value && value !== "undefined" ? value : undefined; +} +function set(key: string, value: string) { + return localStorage.setItem(`${prefix}:${key}`, value); +} + +export function sync() { + return { + tenantAlias: get("tenantAlias") || defaults["tenantAlias"], + mintSource: get("mintSource") || defaults["mintSource"], + bedrockSource: get("bedrockSource") || defaults["bedrockSource"], + }; +} + +function setupForm() { + const current = sync(); + + const form = document.querySelector("form#setupForm")!; + + form.addEventListener("submit", (e) => { + e.preventDefault(); + + const formData = new FormData(e.target as HTMLFormElement); + for (const f of formData.entries()) { + set(f[0], f[1] as string); + } + + window.location.reload(); + }); + + const mintScript = document.createElement("script"); + mintScript.type = "module"; + mintScript.src = current.mintSource; + + const bedrockScript = document.createElement("script"); + bedrockScript.type = "module"; + bedrockScript.src = current.bedrockSource; + + if (mintScript.src) document.head.appendChild(mintScript); + if (bedrockScript.src) document.head.appendChild(bedrockScript); + + const inputs = form.querySelectorAll("input"); + inputs.forEach((input) => { + if (!get(input.name)) + input.value = current[input.name as keyof typeof current] || ""; + else input.value = get(input.name)!; + }); +} + +function showMicrosite() { + wipeWidget(); + + const { tenantAlias } = sync(); + // @ts-expect-error + window.SquatchPortal = { + tenantAlias: tenantAlias, + appDomain: "https://staging.referralsaasquatch.com", + } as any; + + const routerSrc = + "https://staging.referralsaasquatch.com/npm/@saasquatch/microsite-router@next/dist/bundle.js"; + + if (!routerScript) { + routerScript = document.createElement("script"); + routerScript.src = routerSrc; + routerScript.type = "module"; + document.body.appendChild(routerScript); + } + + setTimeout(() => { + // Force load event + dispatchEvent(new Event("load")); + }, 1000); +} + +function wipeWidget() { + const frame = document.body.querySelector("iframe#squatchFrame"); + frame?.remove(); +} + +function wipeMicrosite() { + // @ts-expect-error + window.SquatchPortal = null; + // @ts-expect-error + delete window.SquatchPortal; + + if (routerScript) { + document.body.removeChild(routerScript); + routerScript = null; + } + const micrositeBase = document.getElementById("microsite-base"); + micrositeBase?.remove(); +} + +function showWidget() { + wipeMicrosite(); + + renderWidget({ + programId: "27462", + widgetType: "p/27462/w/referrerWidget", + }); +} + +function registerBtns() { + const hideBtn = document.querySelector("#hideBtn")!; + const showBtn = document.querySelector("#showBtn")!; + const setup = document.querySelector("div#formWrapper")!; + + hideBtn.addEventListener("click", () => { + setup.className = "hide"; + showBtn.className = "show"; + }); + showBtn.addEventListener("click", () => { + setup.className = "show"; + showBtn.className = "hide"; + }); +} + +function registerModeBtns() { + const micrositeBtn = document.querySelector("#micrositeBtn")!; + const widgetBtn = document.querySelector("#widgetBtn")!; + + micrositeBtn.addEventListener("click", () => { + micrositeBtn.className = "active"; + widgetBtn.className = ""; + + showMicrosite(); + }); + widgetBtn.addEventListener("click", () => { + micrositeBtn.className = ""; + widgetBtn.className = "active"; + + showWidget(); + }); +} + +setupForm(); +registerBtns(); +registerModeBtns(); diff --git a/packages/end-user-testing/src/renderWidget.ts b/packages/end-user-testing/src/renderWidget.ts new file mode 100644 index 0000000000..3315aa4db9 --- /dev/null +++ b/packages/end-user-testing/src/renderWidget.ts @@ -0,0 +1,66 @@ +import { sync } from "./main"; +import { decodeUserJwt } from "./utils"; +import html from "./widgetTemplate.html?raw"; + +export type WidgetConfig = { + programId: string; + context?: string; + widgetType: string; +}; + +declare global { + interface Window { + squatch: any; + widgetIdent: any; + } +} +export function renderWidget(config: WidgetConfig) { + const { mintSource, bedrockSource, tenantAlias } = sync(); + + if (!window.squatch) { + console.log("squatchjs hasn't loaded yet"); + } + + const app = document.getElementById("app"); + const content = ` + + + + + + + + ${html} + + + `; + const widget = new window.squatch.EmbedWidget({ + type: config.widgetType, + container: app, + content: content, + context: { + type: "passwordless", // remove if setting user info + token + }, + npmCdn: "https://fast.ssqt.io/npm", + }); + widget.load(); + const frame = app!.querySelector( + "iframe#squatchFrame" + ) as HTMLIFrameElement | null; + + // const token = + // "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IklSTVhzWXk2WVlxcTQ2OTQzN21HOEVSUXQ4UW9LRkJhRzEifQ.eyJ1c2VyIjp7ImlkIjoiNzZmMzRjNDdhN2JmNzYwMzgyMGYwZDQ5MGYwZThhNzAyMWM3YTM1OGNhNTU5YTZhM2Q3OGU5Y2UyMWQ5ZTA4ZCIsImFjY291bnRJZCI6Ijc2ZjM0YzQ3YTdiZjc2MDM4MjBmMGQ0OTBmMGU4YTcwMjFjN2EzNThjYTU1OWE2YTNkNzhlOWNlMjFkOWUwOGQiLCJlbWFpbCI6ImNvbGV0b24uYW5uZXR0K3Rlc3Q4MjM0MjM0QGltcGFjdC5jb20ifX0.GHwg1f2yveU8fnMIkCvzony77LOMVbjK4EWVA-cP1dE"; + const token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IklSTVhzWXk2WVlxcTQ2OTQzN21HOEVSUXQ4UW9LRkJhRzEifQ.eyJ1c2VyIjp7ImlkIjoiNGVlMzhlMzJmMGNhN2Q2MTk5N2I5Mzc1ZWM2NzE2NzA4MjZmOGE5ODliYjc3MWZmZWRjZjYxMDExNDY5MTEwMCIsImFjY291bnRJZCI6IjRlZTM4ZTMyZjBjYTdkNjE5OTdiOTM3NWVjNjcxNjcwODI2ZjhhOTg5YmI3NzFmZmVkY2Y2MTAxMTQ2OTExMDAiLCJlbWFpbCI6ImNvbGV0b24uYW5uZXR0K3Rlc3Q5NzcyNjk3MjM0QGltcGFjdC5jb20ifX0.hwokWeSiz9HRYaT5H7uaWNRmOV6GzySQuakiEnUMepw"; + const userObj = decodeUserJwt(token); + + frame!.contentWindow!.widgetIdent = { + programId: config.programId, + appDomain: "https://staging.referralsaasquatch.com", + tenantAlias, + userId: userObj?.id, + accountId: userObj?.accountId, + email: userObj?.email, + token, + }; +} diff --git a/packages/end-user-testing/src/style.css b/packages/end-user-testing/src/style.css new file mode 100644 index 0000000000..166dfc3945 --- /dev/null +++ b/packages/end-user-testing/src/style.css @@ -0,0 +1,45 @@ +#setup { + border: 1px solid black; + position: absolute; + padding: 8px; + background: white; + z-index: 99999; + right: 8px; +} + +form { + display: flex; + flex-direction: column; + gap: 8px; +} + +form.buttons { + display: flex; + gap: 8px; +} + +.hide { + display: none; +} +.show { + display: block; +} +button { + padding: 8px; + color: white; + background: black; + border-radius: 4px; + border: 1px solid black; +} +button.active { + background: grey; + border-color: white; +} + +input[type="text"] { + height: 24px; + width: 450px; + padding: 4px; + border: 1px solid #eee; + border-radius: 4px; +} diff --git a/packages/end-user-testing/src/utils.ts b/packages/end-user-testing/src/utils.ts new file mode 100644 index 0000000000..2568e80d02 --- /dev/null +++ b/packages/end-user-testing/src/utils.ts @@ -0,0 +1,20 @@ +export type User = { + id: string; + accountId: string; + [key: string]: any; +}; + +export function b64decode(input: string) { + return atob(input.replace(/_/g, "/").replace(/-/g, "+")); +} + +export function decodeUserJwt(tokenStr: string): User | null { + try { + const base64Url = tokenStr.split(".")[1]; + if (base64Url === undefined) return null; + const jsonStr = b64decode(base64Url); + return JSON.parse(jsonStr)?.user; + } catch (e) { + return null; + } +} diff --git a/packages/end-user-testing/src/vite-env.d.ts b/packages/end-user-testing/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/packages/end-user-testing/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/end-user-testing/src/widgetTemplate.html b/packages/end-user-testing/src/widgetTemplate.html new file mode 100644 index 0000000000..79162fcf9b --- /dev/null +++ b/packages/end-user-testing/src/widgetTemplate.html @@ -0,0 +1,110 @@ + + + + + + + + + + + + +

Referrals

+
+ +

Cash Balance

+
+
+ +
+ + +

+ Get rewarded when your friend uses our product +

+
+ +

+ They’ll get a $50 credit towards a new account and + you’ll get: +

+ + + + + + + + +
+ +

Choose how you want to share

+

Your unique referral link:

+ + + + + Share via email + + + Share on LinkedIn + + + Post about us + + +
+ + + + + + + + + + + + + +
+
+ + + + +
+
+
\ No newline at end of file diff --git a/packages/end-user-testing/tsconfig.json b/packages/end-user-testing/tsconfig.json new file mode 100644 index 0000000000..8847c9afe4 --- /dev/null +++ b/packages/end-user-testing/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/packages/mint-components/package-lock.json b/packages/mint-components/package-lock.json index ec978a6303..35b0f100ed 100644 --- a/packages/mint-components/package-lock.json +++ b/packages/mint-components/package-lock.json @@ -1,22 +1,23 @@ { "name": "@saasquatch/mint-components", - "version": "1.11.0", + "version": "1.12.0-15", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@saasquatch/mint-components", - "version": "1.11.0", + "version": "1.12.0-15", "hasInstallScript": true, "license": "MIT", "dependencies": { "@raisins/stencil-docs-target": "^1.1.1", - "@saasquatch/component-boilerplate": "^1.6.7", + "@saasquatch/component-boilerplate": "^1.6.8-0", "@saasquatch/dom-context-hooks": "^1.0.5", "@saasquatch/shoelace": "^1.0.0", "@saasquatch/stencil-hooks": "^2.0.2", "@saasquatch/stencilbook": "^1.1.0", "@saasquatch/universal-hooks": "^1.0.0", + "@veriff/incontext-sdk": "^2.3.1", "babel-polyfill": "^6.26.0", "canvas-confetti": "^1.4.0", "color2k": "^1.2.4", @@ -2994,12 +2995,12 @@ } }, "node_modules/@saasquatch/component-boilerplate": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@saasquatch/component-boilerplate/-/component-boilerplate-1.6.7.tgz", - "integrity": "sha512-jmAr6TTg3NiKMvkgmTl+6r5EmDsyODWZzsyFMG4pVuZdAND/yVx51j+WyXc+yLcOdSgSS4rs55vNI/TBY/b6sA==", + "version": "1.6.8-0", + "resolved": "https://registry.npmjs.org/@saasquatch/component-boilerplate/-/component-boilerplate-1.6.8-0.tgz", + "integrity": "sha512-OeNxcLdM4sSUKJFhyW2al9KPRqdzpuFPu6E9C0EwuBfml1GGx5pdlqN2ePwzy3EEwW6BSQNf4o/nJDXDzBOJHw==", "dependencies": { "@formatjs/intl": "^1.8.2", - "@saasquatch/component-environment": "^1.0.8", + "@saasquatch/component-environment": "^1.0.9-0", "@saasquatch/dom-context-hooks": "^1.0.5", "@saasquatch/stencil-hooks": "^2.0.2", "@saasquatch/universal-hooks": "^1.0.0", @@ -3026,9 +3027,9 @@ } }, "node_modules/@saasquatch/component-environment": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@saasquatch/component-environment/-/component-environment-1.0.8.tgz", - "integrity": "sha512-ORs5VIo9RnW/8yb72eWpmo2sjQOhs2DiO4IJ3gLmKTC5lYy7iRu5LpOR352o6dwdbdbwGG7JZtKgrqYYUl61Yw==", + "version": "1.0.9-0", + "resolved": "https://registry.npmjs.org/@saasquatch/component-environment/-/component-environment-1.0.9-0.tgz", + "integrity": "sha512-Ulxv0p5PyFHTbY0ard/bdgtTZqsq9ald0Pxx2qVpNuHlgc8BkzivpF8jEG2JFLdBfRHAyr+wiRZOUVe1hUbpHQ==", "dependencies": { "@wry/equality": "^0.5.2", "dom-context": "^1.3.1", @@ -3413,6 +3414,15 @@ "@types/node": "*" } }, + "node_modules/@veriff/incontext-sdk": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@veriff/incontext-sdk/-/incontext-sdk-2.3.1.tgz", + "integrity": "sha512-ToNsRwD1caQYVStqKT3T6pTvV3KZxomnXvC3DYJDW2f45F3Zr7hvR9Fub551M/yKLmBpkwdqGXlFU1tI33XdPQ==", + "dependencies": { + "dom-focus-lock": "1.0.4", + "tua-body-scroll-lock": "1.2.1" + } + }, "node_modules/@wry/equality": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.5.7.tgz", @@ -5518,6 +5528,14 @@ "resolved": "https://registry.npmjs.org/dom-context/-/dom-context-1.3.1.tgz", "integrity": "sha512-u54uWFrh+18+HfnnL/h+a/FUH3TWFgF1oOdk2svbX2l5bzX+uj5YVjmcazrigk2o+3Q1bAROdP0JLKDNLQcQHw==" }, + "node_modules/dom-focus-lock": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dom-focus-lock/-/dom-focus-lock-1.0.4.tgz", + "integrity": "sha512-Tiz2EYedB14UW0/lY5PEzFAie66e9itoEVKhz0M9xz1cHqP4Jd0XUg/AT2m6DtbhCFa9DHo6nQyePuLH7Mx9+A==", + "dependencies": { + "focus-lock": "^0.6.2" + } + }, "node_modules/dom-serializer": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", @@ -6300,6 +6318,11 @@ "micromatch": "^4.0.2" } }, + "node_modules/focus-lock": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-0.6.8.tgz", + "integrity": "sha512-vkHTluRCoq9FcsrldC0ulQHiyBYgVJB2CX53I8r0nTC6KnEij7Of0jpBspjt3/CuNb6fyoj3aOh9J2HgQUM0og==" + }, "node_modules/for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -15155,6 +15178,11 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" }, + "node_modules/tua-body-scroll-lock": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/tua-body-scroll-lock/-/tua-body-scroll-lock-1.2.1.tgz", + "integrity": "sha512-DuGbQxIBSbz7/aAwh0oMThOwCwDdYzsU8nF5XQPAvoeOBWp/qhjrZfOtc84gj6CQLZu5L1+TgMNX6fW3OuafHA==" + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -18065,12 +18093,12 @@ } }, "@saasquatch/component-boilerplate": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@saasquatch/component-boilerplate/-/component-boilerplate-1.6.7.tgz", - "integrity": "sha512-jmAr6TTg3NiKMvkgmTl+6r5EmDsyODWZzsyFMG4pVuZdAND/yVx51j+WyXc+yLcOdSgSS4rs55vNI/TBY/b6sA==", + "version": "1.6.8-0", + "resolved": "https://registry.npmjs.org/@saasquatch/component-boilerplate/-/component-boilerplate-1.6.8-0.tgz", + "integrity": "sha512-OeNxcLdM4sSUKJFhyW2al9KPRqdzpuFPu6E9C0EwuBfml1GGx5pdlqN2ePwzy3EEwW6BSQNf4o/nJDXDzBOJHw==", "requires": { "@formatjs/intl": "^1.8.2", - "@saasquatch/component-environment": "^1.0.8", + "@saasquatch/component-environment": "^1.0.9-0", "@saasquatch/dom-context-hooks": "^1.0.5", "@saasquatch/stencil-hooks": "^2.0.2", "@saasquatch/universal-hooks": "^1.0.0", @@ -18096,9 +18124,9 @@ } }, "@saasquatch/component-environment": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@saasquatch/component-environment/-/component-environment-1.0.8.tgz", - "integrity": "sha512-ORs5VIo9RnW/8yb72eWpmo2sjQOhs2DiO4IJ3gLmKTC5lYy7iRu5LpOR352o6dwdbdbwGG7JZtKgrqYYUl61Yw==", + "version": "1.0.9-0", + "resolved": "https://registry.npmjs.org/@saasquatch/component-environment/-/component-environment-1.0.9-0.tgz", + "integrity": "sha512-Ulxv0p5PyFHTbY0ard/bdgtTZqsq9ald0Pxx2qVpNuHlgc8BkzivpF8jEG2JFLdBfRHAyr+wiRZOUVe1hUbpHQ==", "requires": { "@wry/equality": "^0.5.2", "dom-context": "^1.3.1", @@ -18453,6 +18481,15 @@ "@types/node": "*" } }, + "@veriff/incontext-sdk": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@veriff/incontext-sdk/-/incontext-sdk-2.3.1.tgz", + "integrity": "sha512-ToNsRwD1caQYVStqKT3T6pTvV3KZxomnXvC3DYJDW2f45F3Zr7hvR9Fub551M/yKLmBpkwdqGXlFU1tI33XdPQ==", + "requires": { + "dom-focus-lock": "1.0.4", + "tua-body-scroll-lock": "1.2.1" + } + }, "@wry/equality": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.5.7.tgz", @@ -20075,6 +20112,14 @@ "resolved": "https://registry.npmjs.org/dom-context/-/dom-context-1.3.1.tgz", "integrity": "sha512-u54uWFrh+18+HfnnL/h+a/FUH3TWFgF1oOdk2svbX2l5bzX+uj5YVjmcazrigk2o+3Q1bAROdP0JLKDNLQcQHw==" }, + "dom-focus-lock": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dom-focus-lock/-/dom-focus-lock-1.0.4.tgz", + "integrity": "sha512-Tiz2EYedB14UW0/lY5PEzFAie66e9itoEVKhz0M9xz1cHqP4Jd0XUg/AT2m6DtbhCFa9DHo6nQyePuLH7Mx9+A==", + "requires": { + "focus-lock": "^0.6.2" + } + }, "dom-serializer": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", @@ -20686,6 +20731,11 @@ "micromatch": "^4.0.2" } }, + "focus-lock": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-0.6.8.tgz", + "integrity": "sha512-vkHTluRCoq9FcsrldC0ulQHiyBYgVJB2CX53I8r0nTC6KnEij7Of0jpBspjt3/CuNb6fyoj3aOh9J2HgQUM0og==" + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -27459,6 +27509,11 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" }, + "tua-body-scroll-lock": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/tua-body-scroll-lock/-/tua-body-scroll-lock-1.2.1.tgz", + "integrity": "sha512-DuGbQxIBSbz7/aAwh0oMThOwCwDdYzsU8nF5XQPAvoeOBWp/qhjrZfOtc84gj6CQLZu5L1+TgMNX6fW3OuafHA==" + }, "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index 6e4d92c700..c460d47598 100644 --- a/packages/mint-components/package.json +++ b/packages/mint-components/package.json @@ -1,7 +1,7 @@ { "name": "@saasquatch/mint-components", "title": "Mint Components", - "version": "1.11.0", + "version": "1.12.0-21", "description": "A minimal design library with components for referral and loyalty experiences. Built with Shoelace components by Saasquatch.", "icon": "https://res.cloudinary.com/saasquatch/image/upload/v1652219900/squatch-assets/For_Mint.svg", "raisins": "docs/raisins.json", @@ -71,12 +71,13 @@ }, "dependencies": { "@raisins/stencil-docs-target": "^1.1.1", - "@saasquatch/component-boilerplate": "^1.6.7", + "@saasquatch/component-boilerplate": "^1.6.8-0", "@saasquatch/dom-context-hooks": "^1.0.5", "@saasquatch/shoelace": "^1.0.0", "@saasquatch/stencil-hooks": "^2.0.2", "@saasquatch/stencilbook": "^1.1.0", "@saasquatch/universal-hooks": "^1.0.0", + "@veriff/incontext-sdk": "^2.3.1", "babel-polyfill": "^6.26.0", "canvas-confetti": "^1.4.0", "color2k": "^1.2.4", diff --git a/packages/mint-components/src/components.d.ts b/packages/mint-components/src/components.d.ts index 2ace1882bd..0c55620b73 100644 --- a/packages/mint-components/src/components.d.ts +++ b/packages/mint-components/src/components.d.ts @@ -10,10 +10,12 @@ import { AssetCardViewProps } from "./components/sqm-asset-card/sqm-asset-card-v import { BankingInfoFormViewProps } from "./components/tax-and-cash/sqm-banking-info-form/sqm-banking-info-form-view"; import { BigStatViewProps } from "./components/sqm-big-stat/sqm-big-stat-view"; import { CheckboxFieldViewProps } from "./components/sqm-checkbox-field/sqm-checkbox-field-view"; +import { WidgetCodeVerificationViewProps } from "./components/sqm-widget-verification/sqm-code-verification/sqm-code-verification-view"; import { CouponCodeViewProps } from "./components/sqm-coupon-code/sqm-coupon-code-view"; import { UseDocusignFormResult } from "./components/tax-and-cash/sqm-docusign-form/useDocusignForm"; import { DropdownFieldViewProps } from "./components/sqm-dropdown-field/sqm-dropdown-field-view"; import { EditProfileViewProps } from "./components/sqm-edit-profile/sqm-edit-profile-view"; +import { WidgetEmailVerificationViewProps } from "./components/sqm-widget-verification/sqm-email-verification/sqm-email-verification-view"; import { Spacing } from "./global/mixins"; import { FunctionalComponent, VNode } from "@stencil/core"; import { UseIndirectTaxFormResult } from "./components/tax-and-cash/sqm-indirect-tax-form/useIndirectTaxForm"; @@ -27,6 +29,8 @@ import { NameFieldsViewProps } from "./components/sqm-name-fields/sqm-name-field import { NavigationMenuViewProps } from "./components/sqm-navigation-menu/sqm-navigation-menu-view"; import { NavigationSidebarItemViewProps } from "./components/sqm-navigation-sidebar-item/sqm-navigation-sidebar-item-view"; import { PasswordFieldViewDemoProps } from "./components/sqm-password-field/sqm-password-field"; +import { PayoutButtonScrollViewProps } from "./components/sqm-payout-button-scroll/sqm-payout-button-scroll-view"; +import { PayoutStatusAlertViewProps } from "./components/tax-and-cash/sqm-payout-status-alert/sqm-payout-status-alert-view"; import { PortalChangePasswordViewProps } from "./components/sqm-portal-change-password/sqm-portal-change-password-view"; import { PortalEmailVerificationViewProps } from "./components/sqm-portal-email-verification/sqm-portal-email-verification-view"; import { PortalForgotPasswordViewProps } from "./components/sqm-portal-forgot-password/sqm-portal-forgot-password-view"; @@ -286,6 +290,10 @@ export namespace Components { * @uiName Country field placeholder text */ "searchForCountryText": string; + /** + * @uiName Support link text + */ + "supportLink": string; /** * @uiName SWIFT code field label */ @@ -307,6 +315,15 @@ export namespace Components { * @uiName PayPal payout option */ "toPayPalAccount": string; + /** + * @uiName Verify code widget header text + */ + "verifyEmailDescriptionText": string; + /** + * Text for verify email dialog + * @uiName Verify email header + */ + "verifyEmailHeaderText": string; /** * @uiName VO code field label */ @@ -416,6 +433,54 @@ export namespace Components { */ "color": string; } + interface SqmCodeVerification { + /** + * Link text displayed under verify button + * @uiName Resend code text + */ + "codeResentSuccessfullyText": string; + /** + * @undocumented + * @uiType object + */ + "demoData"?: DemoData; + /** + * Error text displayed under verification input + * @uiName Invalid code text + */ + "invalidCodeText": string; + /** + * Displayed when the email verification fails due to a network error. The participant can try refreshing the page. + * @uiName Network error message + */ + "networkErrorMessage": string; + /** + * @undocumented + */ + "onVerification"?: (token: string) => any; + /** + * The link that appears in the resend code link + * @uiName Resend code label + */ + "resendCodeLabel": string; + /** + * Text displayed under verify button + * @uiName Resend code text + */ + "resendCodeText": string; + /** + * @uiName Reverify code widget header text + */ + "reverifyCodeHeaderText": string; + /** + * @uiName Verify code widget header text + */ + "verifyCodeHeaderText": string; + /** + * @uiName Verify code button text + */ + "verifyText": string; + } interface SqmContextRouter { "contextName": string; } @@ -608,6 +673,10 @@ export namespace Components { * @uiName Refresh page button label */ "refreshButton": string; + /** + * @uiName Support link text + */ + "supportLink": string; /** * Displayed at the top of the page on all set up steps. * @uiName Page description @@ -720,6 +789,41 @@ export namespace Components { */ "updatetext": string; } + interface SqmEmailVerification { + /** + * @undocumented + * @uiType object + */ + "demoData"?: DemoData; + /** + * @uiName Email input label + */ + "emailLabel": string; + /** + * @uiName Send code button text + */ + "emailValidationErrorText": string; + /** + * @uiName Send code to email alert description + */ + "sendCodeErrorDescription": string; + /** + * @uiName Send code to email alert header + */ + "sendCodeErrorHeader": string; + /** + * @uiName Send code button text + */ + "sendCodeText": string; + /** + * @uiName Support link text + */ + "supportLink": string; + /** + * @uiName Verify email widget header text + */ + "verifyEmailHeaderText": string; + } interface SqmEmpty { /** * @uiName Title @@ -1088,6 +1192,10 @@ export namespace Components { * @uiName Income tax field label */ "subRegionTaxNumberLabel": string; + /** + * @uiName Support link text + */ + "supportLink": string; /** * Displayed at the top of the page on all set up steps. * @uiName Page description @@ -1583,6 +1691,22 @@ export namespace Components { */ "uppercaseErrorText": string; } + interface SqmPayoutButtonScroll { + /** + * @undocumented + * @uiType object + */ + "demoData"?: DemoData; + /** + * Description text under payout button + * @uiName Payout button description text + */ + "payoutButtonDescription": string; + /** + * @uiName Payout button text + */ + "payoutButtonText": string; + } interface SqmPayoutDetailsCard { /** * Shown before the participant’s bank account information. @@ -1593,16 +1717,6 @@ export namespace Components { * @undocumented */ "demoData"?: DemoData; - /** - * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. - * @uiName Payout error message description - */ - "errorDescriptionText": string; - /** - * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. - * @uiName Payout error message title - */ - "errorTitleText": string; /** * Text displayed for existing publishers that do not have saved banking information. * @uiName Payout missing information subtext @@ -1619,6 +1733,85 @@ export namespace Components { */ "thresholdPayoutText": string; } + interface SqmPayoutStatusAlert { + /** + * @uiName Cash & Payouts Microsite Page (only set if alert is used in a microsite) + */ + "cashPayoutsPageUrl": string; + /** + * @undocumented + * @uiType object + */ + "demoData"?: DemoData; + /** + * @uiName Error description + */ + "errorDescription": string; + /** + * @uiName Error header + */ + "errorHeader": string; + /** + * @uiName Payout on hold alert description + */ + "holdDescription": string; + /** + * @uiName Payout on hold alert header + */ + "holdHeader": string; + /** + * @uiName Info required alert button text + */ + "informationRequiredButtonText": string; + /** + * @uiName Info required alert description + */ + "informationRequiredDescription": string; + /** + * @uiName Info required alert header + */ + "informationRequiredHeader": string; + /** + * @uiName Support link text + */ + "supportLink": string; + /** + * @uiName Verification failed internal alert description + */ + "verificationFailedInternalDescription": string; + /** + * @uiName Verification failed internal alert header + */ + "verificationFailedInternalHeader": string; + /** + * @uiName Verification required alert button text + */ + "verificationRequiredButtonText": string; + /** + * @uiName Verification required alert description + */ + "verificationRequiredDescription": string; + /** + * @uiName Verification required alert header + */ + "verificationRequiredHeader": string; + /** + * @uiName Verification required internal alert description + */ + "verificationRequiredInternalDescription": string; + /** + * @uiName Verification required internal alert header + */ + "verificationRequiredInternalHeader": string; + /** + * @uiName Verification review internal alert description + */ + "verificationReviewInternalDescription": string; + /** + * @uiName Verification review internal alert header + */ + "verificationReviewInternalHeader": string; + } interface SqmPopupContainer { /** * Display a close button on the popup @@ -3843,20 +4036,6 @@ export namespace Components { * @uiGroup Dashboard Properties */ "dashboard_editPaymentInformationButton": string; - /** - * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. - * @uiName Payout error message description - * @uiGroup Dashboard Properties - * @uiWidget textArea - */ - "dashboard_errorDescriptionText": string; - /** - * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. - * @uiName Payout error message title - * @uiGroup Dashboard Properties - * @uiWidget textArea - */ - "dashboard_errorTitleText": string; /** * Part of the Invoice table displayed at the bottom of the page. * @uiName Indirect tax column title @@ -4129,6 +4308,20 @@ export namespace Components { * @uiWidget textArea */ "loadingErrorAlertHeader": string; + /** + * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. + * @uiName Payout error message description + * @uiGroup Dashboard Properties + * @uiWidget textArea + */ + "payoutHoldAlertDescription": string; + /** + * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. + * @uiName Payout error message title + * @uiGroup Dashboard Properties + * @uiWidget textArea + */ + "payoutHoldAlertHeader": string; /** * Placeholder text displayed in the country search dropdown * @uiName Country field placeholder text @@ -4620,6 +4813,12 @@ export namespace Components { * @uiGroup Step 4 Properties */ "step4_voCodeLabel": string; + /** + * Link text for contacting support team + * @uiName Suport link text + * @uiGroup General Form Properties + */ + "supportLink": string; /** * Displayed at the top of the page on all set up steps. * @uiName Page description @@ -4680,18 +4879,6 @@ export namespace Components { * @uiName Edit payment info button label */ "editPaymentInformationButton": string; - /** - * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. - * @uiName Payout error message description - * @uiWidget textArea - */ - "errorDescriptionText": string; - /** - * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. - * @uiName Payout error message title - * @uiWidget textArea - */ - "errorTitleText": string; /** * Part of the alert displayed at the top of the page. * @uiName Form submission error message description @@ -4806,6 +4993,18 @@ export namespace Components { * @uiWidget textArea */ "payoutFromImpact": string; + /** + * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. + * @uiName Payout error message description + * @uiWidget textArea + */ + "payoutHoldAlertDescription": string; + /** + * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. + * @uiName Payout error message title + * @uiWidget textArea + */ + "payoutHoldAlertHeader": string; /** * Text displayed for existing publishers that do not have saved banking information. * @uiName Payout missing information subtext @@ -4845,6 +5044,10 @@ export namespace Components { * @uiName Spain sub-region indirect tax number */ "subRegionTaxNumber": string; + /** + * @uiName Support link text + */ + "supportLink": string; /** * Part of the alert displayed at the top of the page. * @uiName Inactive W-8 error message title @@ -4887,6 +5090,47 @@ export namespace Components { * @uiName Payout schedule by threshold text */ "thresholdPayoutText": string; + /** + * @uiName Verification failed internal alert description + */ + "verificationFailedInternalDescription": string; + /** + * @uiName Verification failed internal alert header + */ + "verificationFailedInternalHeader": string; + /** + * Part of the alert displayed at the top of the page when the user needs to verify their identity. + * @uiName Verification required alert button text + */ + "verificationRequiredButtonText": string; + /** + * Part of the alert displayed at the top of the page when the user needs to verify their identity + * @uiName Verification required alert message description + * @uiWidget textArea + */ + "verificationRequiredDescription": string; + /** + * Part of the alert displayed at the top of the page when the user needs to verify their identity. + * @uiName Verification required alert message title + * @uiWidget textArea + */ + "verificationRequiredHeader": string; + /** + * @uiName Verification required internal alert description + */ + "verificationRequiredInternalDescription": string; + /** + * @uiName Verification required internal alert header + */ + "verificationRequiredInternalHeader": string; + /** + * @uiName Verification review internal alert description + */ + "verificationReviewInternalDescription": string; + /** + * @uiName Verification review internal alert header + */ + "verificationReviewInternalHeader": string; } interface SqmText { } @@ -5100,6 +5344,10 @@ export namespace Components { * @uiName State field label */ "state": string; + /** + * @uiName Support link text + */ + "supportLink": string; /** * Displayed at the top of the page on all set up steps. * @uiName Page description @@ -5133,6 +5381,95 @@ export namespace Components { */ "loadingText": string; } + interface SqmWidgetVerification { + /** + * Link text displayed under verify button + * @uiName Resend code text + * @uiGroup Code Verification Step + */ + "codeStep_codeResentSuccessfullyText": string; + /** + * Error text displayed under verification input + * @uiName Invalid code text + * @uiGroup Code Verification Step + */ + "codeStep_invalidCodeText": string; + /** + * Displayed when the email verification fails due to a network error. The participant can try refreshing the page. + * @uiName Network error message + * @uiGroup Code Verification Step + */ + "codeStep_networkErrorMessage": string; + /** + * The link that appears in the resend code link + * @uiName Resend code label + * @uiGroup Code Verification Step + */ + "codeStep_resendCodeLabel": string; + /** + * Text displayed under verify button + * @uiName Resend code text + * @uiGroup Code Verification Step + */ + "codeStep_resendCodeText": string; + /** + * @uiName Reverify code widget header text + * @uiGroup Code Verification Step + */ + "codeStep_reverifyCodeHeaderText": string; + /** + * @uiName Verify code widget header text + * @uiGroup Code Verification Step + */ + "codeStep_verifyCodeHeaderText": string; + /** + * @uiName Verify code button text + * @uiGroup Code Verification Step + */ + "codeStep_verifyText": string; + /** + * @uiName Email input label + * @uiGroup Email Verification Step + */ + "emailStep_emailLabel": string; + /** + * @uiName Send code button text + * @uiGroup Email Verification Step + */ + "emailStep_emailValidationErrorText": string; + /** + * @uiName Send code to email alert description + * @uiGroup Email Verification Step + */ + "emailStep_sendCodeErrorDescription": string; + /** + * @uiName Send code to email alert header + * @uiGroup Email Verification Step + */ + "emailStep_sendCodeErrorHeader": string; + /** + * @uiName Send code button text + * @uiGroup Email Verification Step + */ + "emailStep_sendCodeText": string; + /** + * @uiName Verify email widget header text + * @uiGroup Email Verification Step + */ + "emailStep_verifyEmailHeaderText": string; + /** + * @uiName General verify widget description text + * @uiGroup General Text + */ + "general_verifyEmailDescription": string; + /** + * @uiName General verify widget header text + * @uiGroup General Text + */ + "general_verifyEmailHeader": string; + } + interface SqmWidgetVerificationController { + } } declare global { interface HTMLRaisinsPlopTargetElement extends Components.RaisinsPlopTarget, HTMLStencilElement { @@ -5183,6 +5520,12 @@ declare global { prototype: HTMLSqmCloseButtonElement; new (): HTMLSqmCloseButtonElement; }; + interface HTMLSqmCodeVerificationElement extends Components.SqmCodeVerification, HTMLStencilElement { + } + var HTMLSqmCodeVerificationElement: { + prototype: HTMLSqmCodeVerificationElement; + new (): HTMLSqmCodeVerificationElement; + }; interface HTMLSqmContextRouterElement extends Components.SqmContextRouter, HTMLStencilElement { } var HTMLSqmContextRouterElement: { @@ -5219,6 +5562,12 @@ declare global { prototype: HTMLSqmEditProfileElement; new (): HTMLSqmEditProfileElement; }; + interface HTMLSqmEmailVerificationElement extends Components.SqmEmailVerification, HTMLStencilElement { + } + var HTMLSqmEmailVerificationElement: { + prototype: HTMLSqmEmailVerificationElement; + new (): HTMLSqmEmailVerificationElement; + }; interface HTMLSqmEmptyElement extends Components.SqmEmpty, HTMLStencilElement { } var HTMLSqmEmptyElement: { @@ -5381,12 +5730,24 @@ declare global { prototype: HTMLSqmPasswordFieldElement; new (): HTMLSqmPasswordFieldElement; }; + interface HTMLSqmPayoutButtonScrollElement extends Components.SqmPayoutButtonScroll, HTMLStencilElement { + } + var HTMLSqmPayoutButtonScrollElement: { + prototype: HTMLSqmPayoutButtonScrollElement; + new (): HTMLSqmPayoutButtonScrollElement; + }; interface HTMLSqmPayoutDetailsCardElement extends Components.SqmPayoutDetailsCard, HTMLStencilElement { } var HTMLSqmPayoutDetailsCardElement: { prototype: HTMLSqmPayoutDetailsCardElement; new (): HTMLSqmPayoutDetailsCardElement; }; + interface HTMLSqmPayoutStatusAlertElement extends Components.SqmPayoutStatusAlert, HTMLStencilElement { + } + var HTMLSqmPayoutStatusAlertElement: { + prototype: HTMLSqmPayoutStatusAlertElement; + new (): HTMLSqmPayoutStatusAlertElement; + }; interface HTMLSqmPopupContainerElement extends Components.SqmPopupContainer, HTMLStencilElement { } var HTMLSqmPopupContainerElement: { @@ -5783,6 +6144,18 @@ declare global { prototype: HTMLSqmUserNameElement; new (): HTMLSqmUserNameElement; }; + interface HTMLSqmWidgetVerificationElement extends Components.SqmWidgetVerification, HTMLStencilElement { + } + var HTMLSqmWidgetVerificationElement: { + prototype: HTMLSqmWidgetVerificationElement; + new (): HTMLSqmWidgetVerificationElement; + }; + interface HTMLSqmWidgetVerificationControllerElement extends Components.SqmWidgetVerificationController, HTMLStencilElement { + } + var HTMLSqmWidgetVerificationControllerElement: { + prototype: HTMLSqmWidgetVerificationControllerElement; + new (): HTMLSqmWidgetVerificationControllerElement; + }; interface HTMLElementTagNameMap { "raisins-plop-target": HTMLRaisinsPlopTargetElement; "sqm-asset-card": HTMLSqmAssetCardElement; @@ -5792,12 +6165,14 @@ declare global { "sqm-card-feed": HTMLSqmCardFeedElement; "sqm-checkbox-field": HTMLSqmCheckboxFieldElement; "sqm-close-button": HTMLSqmCloseButtonElement; + "sqm-code-verification": HTMLSqmCodeVerificationElement; "sqm-context-router": HTMLSqmContextRouterElement; "sqm-coupon-code": HTMLSqmCouponCodeElement; "sqm-divided-layout": HTMLSqmDividedLayoutElement; "sqm-docusign-form": HTMLSqmDocusignFormElement; "sqm-dropdown-field": HTMLSqmDropdownFieldElement; "sqm-edit-profile": HTMLSqmEditProfileElement; + "sqm-email-verification": HTMLSqmEmailVerificationElement; "sqm-empty": HTMLSqmEmptyElement; "sqm-form-message": HTMLSqmFormMessageElement; "sqm-graphql-client-provider": HTMLSqmGraphqlClientProviderElement; @@ -5825,7 +6200,9 @@ declare global { "sqm-navigation-sidebar": HTMLSqmNavigationSidebarElement; "sqm-navigation-sidebar-item": HTMLSqmNavigationSidebarItemElement; "sqm-password-field": HTMLSqmPasswordFieldElement; + "sqm-payout-button-scroll": HTMLSqmPayoutButtonScrollElement; "sqm-payout-details-card": HTMLSqmPayoutDetailsCardElement; + "sqm-payout-status-alert": HTMLSqmPayoutStatusAlertElement; "sqm-popup-container": HTMLSqmPopupContainerElement; "sqm-portal-change-password": HTMLSqmPortalChangePasswordElement; "sqm-portal-container": HTMLSqmPortalContainerElement; @@ -5892,6 +6269,8 @@ declare global { "sqm-titled-section": HTMLSqmTitledSectionElement; "sqm-user-info-form": HTMLSqmUserInfoFormElement; "sqm-user-name": HTMLSqmUserNameElement; + "sqm-widget-verification": HTMLSqmWidgetVerificationElement; + "sqm-widget-verification-controller": HTMLSqmWidgetVerificationControllerElement; } } declare namespace LocalJSX { @@ -6132,6 +6511,10 @@ declare namespace LocalJSX { * @uiName Country field placeholder text */ "searchForCountryText"?: string; + /** + * @uiName Support link text + */ + "supportLink"?: string; /** * @uiName SWIFT code field label */ @@ -6153,6 +6536,15 @@ declare namespace LocalJSX { * @uiName PayPal payout option */ "toPayPalAccount"?: string; + /** + * @uiName Verify code widget header text + */ + "verifyEmailDescriptionText"?: string; + /** + * Text for verify email dialog + * @uiName Verify email header + */ + "verifyEmailHeaderText"?: string; /** * @uiName VO code field label */ @@ -6262,6 +6654,54 @@ declare namespace LocalJSX { */ "color"?: string; } + interface SqmCodeVerification { + /** + * Link text displayed under verify button + * @uiName Resend code text + */ + "codeResentSuccessfullyText"?: string; + /** + * @undocumented + * @uiType object + */ + "demoData"?: DemoData; + /** + * Error text displayed under verification input + * @uiName Invalid code text + */ + "invalidCodeText"?: string; + /** + * Displayed when the email verification fails due to a network error. The participant can try refreshing the page. + * @uiName Network error message + */ + "networkErrorMessage"?: string; + /** + * @undocumented + */ + "onVerification"?: (token: string) => any; + /** + * The link that appears in the resend code link + * @uiName Resend code label + */ + "resendCodeLabel"?: string; + /** + * Text displayed under verify button + * @uiName Resend code text + */ + "resendCodeText"?: string; + /** + * @uiName Reverify code widget header text + */ + "reverifyCodeHeaderText"?: string; + /** + * @uiName Verify code widget header text + */ + "verifyCodeHeaderText"?: string; + /** + * @uiName Verify code button text + */ + "verifyText"?: string; + } interface SqmContextRouter { "contextName"?: string; } @@ -6454,6 +6894,10 @@ declare namespace LocalJSX { * @uiName Refresh page button label */ "refreshButton"?: string; + /** + * @uiName Support link text + */ + "supportLink"?: string; /** * Displayed at the top of the page on all set up steps. * @uiName Page description @@ -6566,6 +7010,41 @@ declare namespace LocalJSX { */ "updatetext"?: string; } + interface SqmEmailVerification { + /** + * @undocumented + * @uiType object + */ + "demoData"?: DemoData; + /** + * @uiName Email input label + */ + "emailLabel"?: string; + /** + * @uiName Send code button text + */ + "emailValidationErrorText"?: string; + /** + * @uiName Send code to email alert description + */ + "sendCodeErrorDescription"?: string; + /** + * @uiName Send code to email alert header + */ + "sendCodeErrorHeader"?: string; + /** + * @uiName Send code button text + */ + "sendCodeText"?: string; + /** + * @uiName Support link text + */ + "supportLink"?: string; + /** + * @uiName Verify email widget header text + */ + "verifyEmailHeaderText"?: string; + } interface SqmEmpty { /** * @uiName Title @@ -6934,6 +7413,10 @@ declare namespace LocalJSX { * @uiName Income tax field label */ "subRegionTaxNumberLabel"?: string; + /** + * @uiName Support link text + */ + "supportLink"?: string; /** * Displayed at the top of the page on all set up steps. * @uiName Page description @@ -7423,6 +7906,22 @@ declare namespace LocalJSX { */ "uppercaseErrorText"?: string; } + interface SqmPayoutButtonScroll { + /** + * @undocumented + * @uiType object + */ + "demoData"?: DemoData; + /** + * Description text under payout button + * @uiName Payout button description text + */ + "payoutButtonDescription"?: string; + /** + * @uiName Payout button text + */ + "payoutButtonText"?: string; + } interface SqmPayoutDetailsCard { /** * Shown before the participant’s bank account information. @@ -7433,16 +7932,6 @@ declare namespace LocalJSX { * @undocumented */ "demoData"?: DemoData; - /** - * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. - * @uiName Payout error message description - */ - "errorDescriptionText"?: string; - /** - * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. - * @uiName Payout error message title - */ - "errorTitleText"?: string; /** * Text displayed for existing publishers that do not have saved banking information. * @uiName Payout missing information subtext @@ -7459,6 +7948,85 @@ declare namespace LocalJSX { */ "thresholdPayoutText"?: string; } + interface SqmPayoutStatusAlert { + /** + * @uiName Cash & Payouts Microsite Page (only set if alert is used in a microsite) + */ + "cashPayoutsPageUrl"?: string; + /** + * @undocumented + * @uiType object + */ + "demoData"?: DemoData; + /** + * @uiName Error description + */ + "errorDescription"?: string; + /** + * @uiName Error header + */ + "errorHeader"?: string; + /** + * @uiName Payout on hold alert description + */ + "holdDescription"?: string; + /** + * @uiName Payout on hold alert header + */ + "holdHeader"?: string; + /** + * @uiName Info required alert button text + */ + "informationRequiredButtonText"?: string; + /** + * @uiName Info required alert description + */ + "informationRequiredDescription"?: string; + /** + * @uiName Info required alert header + */ + "informationRequiredHeader"?: string; + /** + * @uiName Support link text + */ + "supportLink"?: string; + /** + * @uiName Verification failed internal alert description + */ + "verificationFailedInternalDescription"?: string; + /** + * @uiName Verification failed internal alert header + */ + "verificationFailedInternalHeader"?: string; + /** + * @uiName Verification required alert button text + */ + "verificationRequiredButtonText"?: string; + /** + * @uiName Verification required alert description + */ + "verificationRequiredDescription"?: string; + /** + * @uiName Verification required alert header + */ + "verificationRequiredHeader"?: string; + /** + * @uiName Verification required internal alert description + */ + "verificationRequiredInternalDescription"?: string; + /** + * @uiName Verification required internal alert header + */ + "verificationRequiredInternalHeader"?: string; + /** + * @uiName Verification review internal alert description + */ + "verificationReviewInternalDescription"?: string; + /** + * @uiName Verification review internal alert header + */ + "verificationReviewInternalHeader"?: string; + } interface SqmPopupContainer { /** * Display a close button on the popup @@ -9659,20 +10227,6 @@ declare namespace LocalJSX { * @uiGroup Dashboard Properties */ "dashboard_editPaymentInformationButton"?: string; - /** - * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. - * @uiName Payout error message description - * @uiGroup Dashboard Properties - * @uiWidget textArea - */ - "dashboard_errorDescriptionText"?: string; - /** - * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. - * @uiName Payout error message title - * @uiGroup Dashboard Properties - * @uiWidget textArea - */ - "dashboard_errorTitleText"?: string; /** * Part of the Invoice table displayed at the bottom of the page. * @uiName Indirect tax column title @@ -9945,6 +10499,20 @@ declare namespace LocalJSX { * @uiWidget textArea */ "loadingErrorAlertHeader"?: string; + /** + * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. + * @uiName Payout error message description + * @uiGroup Dashboard Properties + * @uiWidget textArea + */ + "payoutHoldAlertDescription"?: string; + /** + * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. + * @uiName Payout error message title + * @uiGroup Dashboard Properties + * @uiWidget textArea + */ + "payoutHoldAlertHeader"?: string; /** * Placeholder text displayed in the country search dropdown * @uiName Country field placeholder text @@ -10436,6 +11004,12 @@ declare namespace LocalJSX { * @uiGroup Step 4 Properties */ "step4_voCodeLabel"?: string; + /** + * Link text for contacting support team + * @uiName Suport link text + * @uiGroup General Form Properties + */ + "supportLink"?: string; /** * Displayed at the top of the page on all set up steps. * @uiName Page description @@ -10496,18 +11070,6 @@ declare namespace LocalJSX { * @uiName Edit payment info button label */ "editPaymentInformationButton"?: string; - /** - * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. - * @uiName Payout error message description - * @uiWidget textArea - */ - "errorDescriptionText"?: string; - /** - * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. - * @uiName Payout error message title - * @uiWidget textArea - */ - "errorTitleText"?: string; /** * Part of the alert displayed at the top of the page. * @uiName Form submission error message description @@ -10622,6 +11184,18 @@ declare namespace LocalJSX { * @uiWidget textArea */ "payoutFromImpact"?: string; + /** + * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. + * @uiName Payout error message description + * @uiWidget textArea + */ + "payoutHoldAlertDescription"?: string; + /** + * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. + * @uiName Payout error message title + * @uiWidget textArea + */ + "payoutHoldAlertHeader"?: string; /** * Text displayed for existing publishers that do not have saved banking information. * @uiName Payout missing information subtext @@ -10661,6 +11235,10 @@ declare namespace LocalJSX { * @uiName Spain sub-region indirect tax number */ "subRegionTaxNumber"?: string; + /** + * @uiName Support link text + */ + "supportLink"?: string; /** * Part of the alert displayed at the top of the page. * @uiName Inactive W-8 error message title @@ -10703,6 +11281,47 @@ declare namespace LocalJSX { * @uiName Payout schedule by threshold text */ "thresholdPayoutText"?: string; + /** + * @uiName Verification failed internal alert description + */ + "verificationFailedInternalDescription"?: string; + /** + * @uiName Verification failed internal alert header + */ + "verificationFailedInternalHeader"?: string; + /** + * Part of the alert displayed at the top of the page when the user needs to verify their identity. + * @uiName Verification required alert button text + */ + "verificationRequiredButtonText"?: string; + /** + * Part of the alert displayed at the top of the page when the user needs to verify their identity + * @uiName Verification required alert message description + * @uiWidget textArea + */ + "verificationRequiredDescription"?: string; + /** + * Part of the alert displayed at the top of the page when the user needs to verify their identity. + * @uiName Verification required alert message title + * @uiWidget textArea + */ + "verificationRequiredHeader"?: string; + /** + * @uiName Verification required internal alert description + */ + "verificationRequiredInternalDescription"?: string; + /** + * @uiName Verification required internal alert header + */ + "verificationRequiredInternalHeader"?: string; + /** + * @uiName Verification review internal alert description + */ + "verificationReviewInternalDescription"?: string; + /** + * @uiName Verification review internal alert header + */ + "verificationReviewInternalHeader"?: string; } interface SqmText { } @@ -10915,6 +11534,10 @@ declare namespace LocalJSX { * @uiName State field label */ "state"?: string; + /** + * @uiName Support link text + */ + "supportLink"?: string; /** * Displayed at the top of the page on all set up steps. * @uiName Page description @@ -10948,6 +11571,95 @@ declare namespace LocalJSX { */ "loadingText"?: string; } + interface SqmWidgetVerification { + /** + * Link text displayed under verify button + * @uiName Resend code text + * @uiGroup Code Verification Step + */ + "codeStep_codeResentSuccessfullyText"?: string; + /** + * Error text displayed under verification input + * @uiName Invalid code text + * @uiGroup Code Verification Step + */ + "codeStep_invalidCodeText"?: string; + /** + * Displayed when the email verification fails due to a network error. The participant can try refreshing the page. + * @uiName Network error message + * @uiGroup Code Verification Step + */ + "codeStep_networkErrorMessage"?: string; + /** + * The link that appears in the resend code link + * @uiName Resend code label + * @uiGroup Code Verification Step + */ + "codeStep_resendCodeLabel"?: string; + /** + * Text displayed under verify button + * @uiName Resend code text + * @uiGroup Code Verification Step + */ + "codeStep_resendCodeText"?: string; + /** + * @uiName Reverify code widget header text + * @uiGroup Code Verification Step + */ + "codeStep_reverifyCodeHeaderText"?: string; + /** + * @uiName Verify code widget header text + * @uiGroup Code Verification Step + */ + "codeStep_verifyCodeHeaderText"?: string; + /** + * @uiName Verify code button text + * @uiGroup Code Verification Step + */ + "codeStep_verifyText"?: string; + /** + * @uiName Email input label + * @uiGroup Email Verification Step + */ + "emailStep_emailLabel"?: string; + /** + * @uiName Send code button text + * @uiGroup Email Verification Step + */ + "emailStep_emailValidationErrorText"?: string; + /** + * @uiName Send code to email alert description + * @uiGroup Email Verification Step + */ + "emailStep_sendCodeErrorDescription"?: string; + /** + * @uiName Send code to email alert header + * @uiGroup Email Verification Step + */ + "emailStep_sendCodeErrorHeader"?: string; + /** + * @uiName Send code button text + * @uiGroup Email Verification Step + */ + "emailStep_sendCodeText"?: string; + /** + * @uiName Verify email widget header text + * @uiGroup Email Verification Step + */ + "emailStep_verifyEmailHeaderText"?: string; + /** + * @uiName General verify widget description text + * @uiGroup General Text + */ + "general_verifyEmailDescription"?: string; + /** + * @uiName General verify widget header text + * @uiGroup General Text + */ + "general_verifyEmailHeader"?: string; + } + interface SqmWidgetVerificationController { + } interface IntrinsicElements { "raisins-plop-target": RaisinsPlopTarget; "sqm-asset-card": SqmAssetCard; @@ -10957,12 +11669,14 @@ declare namespace LocalJSX { "sqm-card-feed": SqmCardFeed; "sqm-checkbox-field": SqmCheckboxField; "sqm-close-button": SqmCloseButton; + "sqm-code-verification": SqmCodeVerification; "sqm-context-router": SqmContextRouter; "sqm-coupon-code": SqmCouponCode; "sqm-divided-layout": SqmDividedLayout; "sqm-docusign-form": SqmDocusignForm; "sqm-dropdown-field": SqmDropdownField; "sqm-edit-profile": SqmEditProfile; + "sqm-email-verification": SqmEmailVerification; "sqm-empty": SqmEmpty; "sqm-form-message": SqmFormMessage; "sqm-graphql-client-provider": SqmGraphqlClientProvider; @@ -10990,7 +11704,9 @@ declare namespace LocalJSX { "sqm-navigation-sidebar": SqmNavigationSidebar; "sqm-navigation-sidebar-item": SqmNavigationSidebarItem; "sqm-password-field": SqmPasswordField; + "sqm-payout-button-scroll": SqmPayoutButtonScroll; "sqm-payout-details-card": SqmPayoutDetailsCard; + "sqm-payout-status-alert": SqmPayoutStatusAlert; "sqm-popup-container": SqmPopupContainer; "sqm-portal-change-password": SqmPortalChangePassword; "sqm-portal-container": SqmPortalContainer; @@ -11057,6 +11773,8 @@ declare namespace LocalJSX { "sqm-titled-section": SqmTitledSection; "sqm-user-info-form": SqmUserInfoForm; "sqm-user-name": SqmUserName; + "sqm-widget-verification": SqmWidgetVerification; + "sqm-widget-verification-controller": SqmWidgetVerificationController; } } export { LocalJSX as JSX }; @@ -11071,12 +11789,14 @@ declare module "@stencil/core" { "sqm-card-feed": LocalJSX.SqmCardFeed & JSXBase.HTMLAttributes; "sqm-checkbox-field": LocalJSX.SqmCheckboxField & JSXBase.HTMLAttributes; "sqm-close-button": LocalJSX.SqmCloseButton & JSXBase.HTMLAttributes; + "sqm-code-verification": LocalJSX.SqmCodeVerification & JSXBase.HTMLAttributes; "sqm-context-router": LocalJSX.SqmContextRouter & JSXBase.HTMLAttributes; "sqm-coupon-code": LocalJSX.SqmCouponCode & JSXBase.HTMLAttributes; "sqm-divided-layout": LocalJSX.SqmDividedLayout & JSXBase.HTMLAttributes; "sqm-docusign-form": LocalJSX.SqmDocusignForm & JSXBase.HTMLAttributes; "sqm-dropdown-field": LocalJSX.SqmDropdownField & JSXBase.HTMLAttributes; "sqm-edit-profile": LocalJSX.SqmEditProfile & JSXBase.HTMLAttributes; + "sqm-email-verification": LocalJSX.SqmEmailVerification & JSXBase.HTMLAttributes; "sqm-empty": LocalJSX.SqmEmpty & JSXBase.HTMLAttributes; "sqm-form-message": LocalJSX.SqmFormMessage & JSXBase.HTMLAttributes; "sqm-graphql-client-provider": LocalJSX.SqmGraphqlClientProvider & JSXBase.HTMLAttributes; @@ -11104,7 +11824,9 @@ declare module "@stencil/core" { "sqm-navigation-sidebar": LocalJSX.SqmNavigationSidebar & JSXBase.HTMLAttributes; "sqm-navigation-sidebar-item": LocalJSX.SqmNavigationSidebarItem & JSXBase.HTMLAttributes; "sqm-password-field": LocalJSX.SqmPasswordField & JSXBase.HTMLAttributes; + "sqm-payout-button-scroll": LocalJSX.SqmPayoutButtonScroll & JSXBase.HTMLAttributes; "sqm-payout-details-card": LocalJSX.SqmPayoutDetailsCard & JSXBase.HTMLAttributes; + "sqm-payout-status-alert": LocalJSX.SqmPayoutStatusAlert & JSXBase.HTMLAttributes; "sqm-popup-container": LocalJSX.SqmPopupContainer & JSXBase.HTMLAttributes; "sqm-portal-change-password": LocalJSX.SqmPortalChangePassword & JSXBase.HTMLAttributes; "sqm-portal-container": LocalJSX.SqmPortalContainer & JSXBase.HTMLAttributes; @@ -11171,6 +11893,8 @@ declare module "@stencil/core" { "sqm-titled-section": LocalJSX.SqmTitledSection & JSXBase.HTMLAttributes; "sqm-user-info-form": LocalJSX.SqmUserInfoForm & JSXBase.HTMLAttributes; "sqm-user-name": LocalJSX.SqmUserName & JSXBase.HTMLAttributes; + "sqm-widget-verification": LocalJSX.SqmWidgetVerification & JSXBase.HTMLAttributes; + "sqm-widget-verification-controller": LocalJSX.SqmWidgetVerificationController & JSXBase.HTMLAttributes; } } } diff --git a/packages/mint-components/src/components/sqm-big-stat/useBigStat.tsx b/packages/mint-components/src/components/sqm-big-stat/useBigStat.tsx index ddacd3f7cc..7a2dbb54d2 100644 --- a/packages/mint-components/src/components/sqm-big-stat/useBigStat.tsx +++ b/packages/mint-components/src/components/sqm-big-stat/useBigStat.tsx @@ -898,9 +898,12 @@ const payoutBalanceQuery = () => { query payoutBalance { viewer: viewer { ... on User { - publisher { - payoutsAccount { - balance + id + impactConnection { + publisher { + payoutsAccount { + balance + } } } } diff --git a/packages/mint-components/src/components/sqm-form-message/readme.md b/packages/mint-components/src/components/sqm-form-message/readme.md index a471e0a309..b745a797ce 100644 --- a/packages/mint-components/src/components/sqm-form-message/readme.md +++ b/packages/mint-components/src/components/sqm-form-message/readme.md @@ -17,6 +17,7 @@ ### Used by + - [sqm-code-verification](../sqm-widget-verification/sqm-code-verification) - [sqm-coupon-code](../sqm-coupon-code) - [sqm-edit-profile](../sqm-edit-profile) - [sqm-instant-access-registration](../sqm-instant-access-registration) @@ -35,6 +36,7 @@ ### Graph ```mermaid graph TD; + sqm-code-verification --> sqm-form-message sqm-coupon-code --> sqm-form-message sqm-edit-profile --> sqm-form-message sqm-instant-access-registration --> sqm-form-message diff --git a/packages/mint-components/src/components/sqm-payout-button-scroll/PayoutButtonScroll.stories.tsx b/packages/mint-components/src/components/sqm-payout-button-scroll/PayoutButtonScroll.stories.tsx new file mode 100644 index 0000000000..0dd680926e --- /dev/null +++ b/packages/mint-components/src/components/sqm-payout-button-scroll/PayoutButtonScroll.stories.tsx @@ -0,0 +1,46 @@ +import { h } from "@stencil/core"; + +export default { + title: "Components/Payout Scroll Button", +}; + +const defaultProps = { + payoutSettingsComplete: true, +}; + +export const CompletedTaxForm = () => ( + +); + +export const CompletedTaxFormWithStatsContainer = () => ( + + + +

Referrals

+
+ +

Cash Balance

+
+
+ + +
+); diff --git a/packages/mint-components/src/components/sqm-payout-button-scroll/readme.md b/packages/mint-components/src/components/sqm-payout-button-scroll/readme.md new file mode 100644 index 0000000000..e1f1e00cc1 --- /dev/null +++ b/packages/mint-components/src/components/sqm-payout-button-scroll/readme.md @@ -0,0 +1,37 @@ +# sqm-payout-button-scroll + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| ------------------------- | --------------------------- | ------------------------------------ | ---------------------------------------------------- | ------------------------------------------------------------------- | +| `demoData` | -- | | `{ states?: { payoutSettingsComplete: boolean; }; }` | `undefined` | +| `payoutButtonDescription` | `payout-button-description` | Description text under payout button | `string` | `"Check your payout settings to see when you’ll get paid out next"` | +| `payoutButtonText` | `payout-button-text` | | `string` | `"Payouts & Tax Settings"` | + + +## Dependencies + +### Used by + + - [sqm-stencilbook](../sqm-stencilbook) + +### Depends on + +- [sqm-scroll](../sqm-scroll) + +### Graph +```mermaid +graph TD; + sqm-payout-button-scroll --> sqm-scroll + sqm-stencilbook --> sqm-payout-button-scroll + style sqm-payout-button-scroll fill:#f9f,stroke:#333,stroke-width:4px +``` + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/packages/mint-components/src/components/sqm-payout-button-scroll/sqm-payout-button-scroll-view.tsx b/packages/mint-components/src/components/sqm-payout-button-scroll/sqm-payout-button-scroll-view.tsx new file mode 100644 index 0000000000..d5bd0d6392 --- /dev/null +++ b/packages/mint-components/src/components/sqm-payout-button-scroll/sqm-payout-button-scroll-view.tsx @@ -0,0 +1,46 @@ +import { h } from "@stencil/core"; +import { createStyleSheet } from "../../styling/JSS"; + +export interface PayoutButtonScrollViewProps { + states: { + payoutSettingsComplete: boolean; + }; + text: { + payoutButtonText: string; + payoutButtonDescription: string; + }; +} + +const style = { + Wrapper: { + maxWidth: "200px", + marginLeft: "auto", + }, + PayoutButtonDescription: { + color: "var(--sl-color-neutral-500)", + fontSize: "var(--sl-font-size-small)", + margin: "0", + }, +}; + +const sheet = createStyleSheet(style); +const styleString = sheet.toString(); + +export function PayoutButtonScrollView(props: PayoutButtonScrollViewProps) { + const { text, states } = props; + + if (!states.payoutSettingsComplete) return
; + return ( +
+ + +

+ {text.payoutButtonDescription} +

+
+ ); +} diff --git a/packages/mint-components/src/components/sqm-payout-button-scroll/sqm-payout-button-scroll.tsx b/packages/mint-components/src/components/sqm-payout-button-scroll/sqm-payout-button-scroll.tsx new file mode 100644 index 0000000000..d9fc869b4b --- /dev/null +++ b/packages/mint-components/src/components/sqm-payout-button-scroll/sqm-payout-button-scroll.tsx @@ -0,0 +1,71 @@ +import { Component, h, Prop, State } from "@stencil/core"; +import { withHooks } from "@saasquatch/stencil-hooks"; +import { isDemo } from "@saasquatch/component-boilerplate"; +import { DemoData } from "../../global/demo"; +import { getProps } from "../../utils/utils"; +import { + PayoutButtonScrollView, + PayoutButtonScrollViewProps, +} from "./sqm-payout-button-scroll-view"; +import deepmerge from "deepmerge"; +import { usePayoutButton } from "./usePayoutButton"; + +/** + * @uiName Payout Button Scroll + */ +@Component({ + tag: "sqm-payout-button-scroll", + shadow: true, +}) +export class PayoutButtonScroll { + /** + * @uiName Payout button text + */ + @Prop() payoutButtonText: string = "Payouts & Tax Settings"; + /** + * Description text under payout button + * @uiName Payout button description text + */ + @Prop() payoutButtonDescription: string = + "Check your payout settings to see when you’ll get paid out next"; + + /** + * @undocumented + * @uiType object + */ + @Prop() demoData?: DemoData; + + @State() + ignored = true; + + constructor() { + withHooks(this); + } + disconnectedCallback() {} + getTextProps() { + return getProps(this); + } + + render() { + const props = isDemo() + ? useDemoPayoutButtonScroll(this) + : usePayoutButton(this); + + return ; + } +} + +function useDemoPayoutButtonScroll( + props: PayoutButtonScroll +): PayoutButtonScrollViewProps { + return deepmerge( + { + states: { + payoutSettingsComplete: false, + }, + text: props.getTextProps(), + }, + props.demoData || {}, + { arrayMerge: (_, a) => a } + ); +} diff --git a/packages/mint-components/src/components/sqm-payout-button-scroll/usePayoutButton.ts b/packages/mint-components/src/components/sqm-payout-button-scroll/usePayoutButton.ts new file mode 100644 index 0000000000..aa80f345be --- /dev/null +++ b/packages/mint-components/src/components/sqm-payout-button-scroll/usePayoutButton.ts @@ -0,0 +1,41 @@ +import { gql } from "graphql-request"; +import { PayoutButtonScroll } from "./sqm-payout-button-scroll"; +import { useQuery } from "@saasquatch/component-boilerplate"; +import { useEffect, useState } from "@saasquatch/universal-hooks"; + +const GET_PAYOUT_DETAILS = gql` + query getPayoutDetails { + user: viewer { + ... on User { + id + impactConnection { + connected + publisher { + id + payoutsAccount { + hold + balance + } + } + } + } + } + } +`; +export function usePayoutButton(props: PayoutButtonScroll) { + const { data, refetch } = useQuery(GET_PAYOUT_DETAILS, {}); + + useEffect(() => { + const cb = () => refetch(); + window.addEventListener("sqm:tax-form-updated", cb); + return () => window.removeEventListener("sqm:tax-form-updated", cb); + }, []); + + return { + states: { + payoutSettingsComplete: + !!data?.user?.impactConnection?.publisher?.payoutsAccount, + }, + text: props.getTextProps(), + }; +} diff --git a/packages/mint-components/src/components/sqm-referral-table/ReferralTable.stories.tsx b/packages/mint-components/src/components/sqm-referral-table/ReferralTable.stories.tsx index 2c0ad67018..06a92541c4 100644 --- a/packages/mint-components/src/components/sqm-referral-table/ReferralTable.stories.tsx +++ b/packages/mint-components/src/components/sqm-referral-table/ReferralTable.stories.tsx @@ -14,6 +14,7 @@ import { PendingPartnerCreation, PendingTaxReview, PendingTaxSubmission, + PayoutCancelled, } from "./ReferralTableRewardsCell.stories"; import { DateCell, @@ -180,6 +181,12 @@ const simpleUserTableProps = { , , ], + [ + , + , + , + , + ], [ , , diff --git a/packages/mint-components/src/components/sqm-referral-table/ReferralTableRewardsCell.stories.tsx b/packages/mint-components/src/components/sqm-referral-table/ReferralTableRewardsCell.stories.tsx index 4d1b59f6ce..34a2c19ca9 100644 --- a/packages/mint-components/src/components/sqm-referral-table/ReferralTableRewardsCell.stories.tsx +++ b/packages/mint-components/src/components/sqm-referral-table/ReferralTableRewardsCell.stories.tsx @@ -139,6 +139,9 @@ const deniedReward = { const payoutFailedReward = { statuses: ["PAYOUT_FAILED"], }; +const payoutCancelledReward = { + statuses: ["PAYOUT_CANCELLED"], +}; const pendingReviewReward = { statuses: ["PENDING_REVIEW"], }; @@ -233,9 +236,9 @@ function getYears() { } const statusText = - "{status, select, AVAILABLE {Available} PAYOUT_SENT {Payout Sent} CANCELLED {Cancelled} PAYOUT_FAILED {Payout Failed} PENDING {Pending} PENDING_REVIEW {Pending} PENDING_TAX_REVIEW {Pending} PENDING_NEW_TAX_FORM {Pending} PENDING_TAX_SUBMISSION {Pending} PENDING_PARTNER_CREATION {Pending} DENIED {Denied} EXPIRED {Expired} REDEEMED {Redeemed} other {Not available} }"; + "{status, select, AVAILABLE {Available} CANCELLED {Cancelled} PENDING {Pending} PENDING_REVIEW {Pending} PAYOUT_APPROVED {Payout Approved} PAYOUT_FAILED {Payout Failed} PAYOUT_CANCELLED {Payout Cancelled} PENDING_TAX_REVIEW {Pending} PENDING_NEW_TAX_FORM {Pending} PENDING_TAX_SUBMISSION {Pending} PENDING_PARTNER_CREATION {Pending} DENIED {Denied} EXPIRED {Expired} REDEEMED {Redeemed} other {Not available} }"; const statusLongText = - "{status, select, AVAILABLE {Reward expiring on} PAYOUT_SENT {Reward approved for payout and was scheduled for payment based on your settings.} DENIED {Denied on} EXPIRED {Reward expired on} REDEEMED {Redeemed} other {Not available} }"; + "{status, select, AVAILABLE {Reward expiring on} CANCELLED {Reward cancelled on} PENDING {Available on} PENDING_REVIEW {Pending since} PAYOUT_APPROVED {Reward approved for payout and was scheduled for payment based on your settings.} PAYOUT_FAILED {Payout failed due to a fulfillment issue and is currently being retried.} PAYOUT_CANCELLED {If you think this is a mistake, contact our Support team.} PENDING_TAX_REVIEW {Awaiting tax form review} PENDING_NEW_TAX_FORM {Invalid tax form. Submit a new form to receive your rewards.} PENDING_TAX_SUBMISSION {Submit your tax documents to receive your rewards} PENDING_PARTNER_CREATION {Complete your tax and cash payout setup to receive your rewards} DENIED {Denied on} EXPIRED {Reward expired on} other {Not available} }"; export const PendingNoUnpend = () => { return ( @@ -569,8 +572,7 @@ export const PayoutFailed = () => { ); }; -// AL: TODO payout cancelled -export const PayoutCancelled = () => { +export const PayoutDenied = () => { return ( { ); }; +export const PayoutCancelled = () => { + return ( + + ); +}; + export const PendingReview = () => { return ( { export const SourceCellDeletedReferral = () => { return ( - // AL: TODO { const pending = { statuses: ["PENDING"], }; -// const pendingTaxReview = { -// statuses: ["PENDING_TAX_REVIEW"], -// }; -// const pendingNewTaxForm = { -// statuses: ["PENDING_NEW_TAX_FORM"], -// }; -// const pendingTaxSubmission = { -// statuses: ["PENDING_TAX_SUBMISSION"], -// }; -// const pendingPartnerCreation = { -// statuses: ["PENDING_PARTNER_CREATION"], -// }; const payoutSent = { statuses: ["PAYOUT_SENT"], }; const payoutFailed = { statuses: ["PAYOUT_FAILED"], }; +const payoutCancelled = { + statuses: ["PAYOUT_CANCELLED"], + dateCancelled: 1355612521321, +}; const us_tax = { pendingReasons: ["US_TAX"], }; @@ -559,6 +550,15 @@ export const StatusCellPayoutFailed = () => { ); }; +export const StatusCellPayoutCancelled = () => { + return ( + + ); +}; + export const StatusCellPendingWithLongText = () => { return (
diff --git a/packages/mint-components/src/components/sqm-rewards-table/columns/sqm-rewards-table-source-column.feature b/packages/mint-components/src/components/sqm-rewards-table/columns/sqm-rewards-table-source-column.feature index 7a4a036483..f70413f5da 100644 --- a/packages/mint-components/src/components/sqm-rewards-table/columns/sqm-rewards-table-source-column.feature +++ b/packages/mint-components/src/components/sqm-rewards-table/columns/sqm-rewards-table-source-column.feature @@ -98,7 +98,6 @@ Feature: Reward Table Source Column | rewardSource | referral | sourceText | | "FRIEND_SIGNUP" | referrer | Referral to | | "REFERRED" | referred | Referred by | -# AL: TODO DELETED REFERRAL SPEC @motivating @ui Scenario Outline: The source column displays referral as "Deleted referral" if the referral user is deleted in the system diff --git a/packages/mint-components/src/components/sqm-scroll/readme.md b/packages/mint-components/src/components/sqm-scroll/readme.md index 8c7a09f891..9a3fba3a7e 100644 --- a/packages/mint-components/src/components/sqm-scroll/readme.md +++ b/packages/mint-components/src/components/sqm-scroll/readme.md @@ -25,11 +25,15 @@ ### Used by + - [sqm-payout-button-scroll](../sqm-payout-button-scroll) + - [sqm-payout-status-alert](../tax-and-cash/sqm-payout-status-alert) - [sqm-stencilbook](../sqm-stencilbook) ### Graph ```mermaid graph TD; + sqm-payout-button-scroll --> sqm-scroll + sqm-payout-status-alert --> sqm-scroll sqm-stencilbook --> sqm-scroll style sqm-scroll fill:#f9f,stroke:#333,stroke-width:4px ``` diff --git a/packages/mint-components/src/components/sqm-stencilbook/readme.md b/packages/mint-components/src/components/sqm-stencilbook/readme.md index 4cbd61cc7c..da8ced3122 100644 --- a/packages/mint-components/src/components/sqm-stencilbook/readme.md +++ b/packages/mint-components/src/components/sqm-stencilbook/readme.md @@ -88,6 +88,10 @@ - [sqm-invoice-table-download-column](../sqm-invoice-table/columns) - [sqm-invoice-table-date-column](../sqm-invoice-table/columns) - [sqm-invoice-table-data-column](../sqm-invoice-table/columns) +- [sqm-email-verification](../sqm-widget-verification/sqm-email-verification) +- [sqm-code-verification](../sqm-widget-verification/sqm-code-verification) +- [sqm-payout-button-scroll](../sqm-payout-button-scroll) +- [sqm-payout-status-alert](../tax-and-cash/sqm-payout-status-alert) ### Graph ```mermaid @@ -171,6 +175,10 @@ graph TD; sqm-stencilbook --> sqm-invoice-table-download-column sqm-stencilbook --> sqm-invoice-table-date-column sqm-stencilbook --> sqm-invoice-table-data-column + sqm-stencilbook --> sqm-email-verification + sqm-stencilbook --> sqm-code-verification + sqm-stencilbook --> sqm-payout-button-scroll + sqm-stencilbook --> sqm-payout-status-alert sqm-empty --> sqm-portal-container sqm-empty --> sqm-titled-section sqm-empty --> sqm-text @@ -204,6 +212,8 @@ graph TD; sqm-user-info-form --> sqm-banking-info-form sqm-user-info-form --> sqm-tax-and-cash-dashboard sqm-user-info-form --> sqm-tax-and-cash + sqm-banking-info-form --> sqm-code-verification + sqm-code-verification --> sqm-form-message sqm-tax-and-cash-dashboard --> sqm-payout-details-card sqm-tax-and-cash-dashboard --> sqm-invoice-table sqm-tax-and-cash-dashboard --> sqm-invoice-table-download-column @@ -220,6 +230,8 @@ graph TD; sqm-tax-and-cash --> sqm-docusign-form sqm-tax-and-cash --> sqm-banking-info-form sqm-tax-and-cash --> sqm-tax-and-cash-dashboard + sqm-payout-button-scroll --> sqm-scroll + sqm-payout-status-alert --> sqm-scroll style sqm-stencilbook fill:#f9f,stroke:#333,stroke-width:4px ``` diff --git a/packages/mint-components/src/components/sqm-stencilbook/sqm-stencilbook.tsx b/packages/mint-components/src/components/sqm-stencilbook/sqm-stencilbook.tsx index 42ded3f49e..2fd171c542 100644 --- a/packages/mint-components/src/components/sqm-stencilbook/sqm-stencilbook.tsx +++ b/packages/mint-components/src/components/sqm-stencilbook/sqm-stencilbook.tsx @@ -84,6 +84,10 @@ import * as BankingInfoForm from "../tax-and-cash/BankingForm.stories"; import * as InvoiceTable from "../sqm-invoice-table/InvoiceTable.stories"; import * as InvoiceTableCells from "../sqm-invoice-table/InvoiceTableCell.stories"; import * as UseInvoiceTableCells from "../sqm-invoice-table/UseInvoiceTable.stories"; +import * as WidgetEmailVerification from "../sqm-widget-verification/sqm-email-verification/WidgetEmailVerification.stories"; +import * as WidgetCodeVerification from "../sqm-widget-verification/sqm-code-verification/WidgetCodeVerification.stories"; +import * as PayoutButtonScroll from "../sqm-payout-button-scroll/PayoutButtonScroll.stories"; +import * as PayoutStatusAlert from "../tax-and-cash/sqm-payout-status-alert/PayoutStatusAlert.stories"; import * as Themes from "./Themes"; import { CucumberAddon } from "./CucumberAddon"; @@ -174,6 +178,10 @@ const stories = [ TaxFormSlots, PayoutDetailsCard, BankingInfoForm, + WidgetEmailVerification, + WidgetCodeVerification, + PayoutButtonScroll, + PayoutStatusAlert, ]; /** diff --git a/packages/mint-components/src/components/sqm-text/sqm-text.tsx b/packages/mint-components/src/components/sqm-text/sqm-text.tsx index ca08ff163f..b1b3628b6e 100644 --- a/packages/mint-components/src/components/sqm-text/sqm-text.tsx +++ b/packages/mint-components/src/components/sqm-text/sqm-text.tsx @@ -29,7 +29,7 @@ const vanillaStyle = ` sqm-text h4 { font-size: var(--sl-font-size-large); - font-weight: var(--sl-font-weight-semibold); + font-weight: var(--sl-font-weight-bold); color: var(--sl-color-neutral-800); margin: 0; } diff --git a/packages/mint-components/src/components/sqm-widget-verification/keys.ts b/packages/mint-components/src/components/sqm-widget-verification/keys.ts new file mode 100644 index 0000000000..c46ba9d8cf --- /dev/null +++ b/packages/mint-components/src/components/sqm-widget-verification/keys.ts @@ -0,0 +1,4 @@ +export const SHOW_CODE_NAMESPACE = "sq:code-verification"; +export const VERIFICATION_EMAIL_NAMESPACE = "sq:verification-email"; +export const VERIFICATION_PARENT_NAMESPACE = "sq:verification-context"; +export const VERIFICATION_EVENT_KEY = "sq:code-verified"; diff --git a/packages/mint-components/src/components/sqm-widget-verification/readme.md b/packages/mint-components/src/components/sqm-widget-verification/readme.md new file mode 100644 index 0000000000..230b41727b --- /dev/null +++ b/packages/mint-components/src/components/sqm-widget-verification/readme.md @@ -0,0 +1,10 @@ +# sqm-widget-verification + + + + + + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/WidgetCodeVerification.stories.tsx b/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/WidgetCodeVerification.stories.tsx new file mode 100644 index 0000000000..ce89972281 --- /dev/null +++ b/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/WidgetCodeVerification.stories.tsx @@ -0,0 +1,63 @@ +import { h } from "@stencil/core"; + +export default { + title: "Components/Widget Code Verification", +}; + +const defaultProps = { + initialiseLoading: false, + email: "testemail@example.com", + loading: false, + verifyFailed: false, + emailResent: false, +}; + +export const Default = () => ( + +); + +export const InitialLoading = () => ( + +); + +export const Loading = () => ( + +); + +export const CodeResent = () => ( + +); + +export const VerificationFailed = () => ( + +); diff --git a/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/readme.md b/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/readme.md new file mode 100644 index 0000000000..0e3b2042bf --- /dev/null +++ b/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/readme.md @@ -0,0 +1,48 @@ +# sqm-code-verification + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| ---------------------------- | ------------------------------- | ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | +| `codeResentSuccessfullyText` | `code-resent-successfully-text` | Link text displayed under verify button | `string` | `"Another code has been sent to {email}"` | +| `demoData` | -- | | `{ states?: { initialiseLoading: boolean; loading: boolean; email: string; verifyFailed?: boolean; emailResent: boolean; }; refs?: { codeWrapperRef: any; }; }` | `undefined` | +| `invalidCodeText` | `invalid-code-text` | Error text displayed under verification input | `string` | `"Please check your code and try again. If you’re still having trouble, try resending your code."` | +| `networkErrorMessage` | `network-error-message` | Displayed when the email verification fails due to a network error. The participant can try refreshing the page. | `string` | `"An error occurred while verifying your email. Please refresh the page and try again."` | +| `onVerification` | -- | | `(token: string) => any` | `undefined` | +| `resendCodeLabel` | `resend-code-label` | The link that appears in the resend code link | `string` | `"Resend code"` | +| `resendCodeText` | `resend-code-text` | Text displayed under verify button | `string` | `"Didn't receive your code? {resendCodeLink}"` | +| `reverifyCodeHeaderText` | `reverify-code-header-text` | | `string` | `"Enter the code sent to {email} from our referral provider, impact.com."` | +| `verifyCodeHeaderText` | `verify-code-header-text` | | `string` | `"Enter the code sent to {email} from our referral provider, impact.com."` | +| `verifyText` | `verify-text` | | `string` | `"Verify"` | + + +## Dependencies + +### Used by + + - [sqm-banking-info-form](../../tax-and-cash/sqm-banking-info-form) + - [sqm-stencilbook](../../sqm-stencilbook) + - [sqm-widget-verification](..) + +### Depends on + +- [sqm-form-message](../../sqm-form-message) + +### Graph +```mermaid +graph TD; + sqm-code-verification --> sqm-form-message + sqm-banking-info-form --> sqm-code-verification + sqm-stencilbook --> sqm-code-verification + sqm-widget-verification --> sqm-code-verification + style sqm-code-verification fill:#f9f,stroke:#333,stroke-width:4px +``` + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/sqm-code-verification-view.tsx b/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/sqm-code-verification-view.tsx new file mode 100644 index 0000000000..8bfba0154e --- /dev/null +++ b/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/sqm-code-verification-view.tsx @@ -0,0 +1,209 @@ +import { h } from "@stencil/core"; +import { createStyleSheet } from "../../../styling/JSS"; +import { TextSpanView } from "../../sqm-text-span/sqm-text-span-view"; +import { ErrorStyles } from "../../../global/mixins"; +import { intl } from "../../../global/global"; + +export interface WidgetCodeVerificationViewProps { + states: { + initialiseLoading: boolean; + loading: boolean; + email: string; + verifyFailed?: boolean; + emailResent: boolean; + }; + refs: { + codeWrapperRef: any; + }; + callbacks: { + submitCode: () => Promise; + resendEmail: () => Promise; + }; + text: { + verifyCodeHeaderText: string; + reverifyCodeHeaderText: string; + resendCodeText: string; + verifyText: string; + invalidCodeText: string; + codeResentSuccessfullyText: string; + resendCodeLabel: string; + }; +} + +const style = { + Wrapper: { + display: "flex", + flexDirection: "column", + gap: "var(--sl-spacing-medium)", + maxWidth: "550px", + }, + HeaderContainer: { + display: "flex", + flexDirection: "column", + }, + InputsContainer: { + display: "flex", + gap: "var(--sl-spacing-medium)", + position: "relative", + flexDirection: "column", + }, + CodeInputContainer: { + display: "flex", + gap: "var(--sl-spacing-medium)", + }, + CodeInput: { + maxWidth: "40px", + "&::part(input)": { + margin: "0", + padding: "0 var(--sl-input-spacing-small)", + fontSize: "var(--sl-font-size-large)", + }, + }, + CodeInputError: { + ...ErrorStyles, + maxWidth: "40px", + "&::part(input)": { + margin: "0", + padding: "0 var(--sl-input-spacing-small)", + fontSize: "var(--sl-font-size-large)", + }, + }, + ErrorText: { + color: "var(--sl-color-danger-500)", + fontSize: "var(--sl-font-size-small)", + margin: "0", + }, + ContinueButton: { + width: "100%", + maxWidth: "100px", + }, + FooterContainer: { + display: "flex", + flexDirection: "column", + }, + SkeletonOne: { + width: "50%", + height: "16px", + }, + SkeletonTwo: { + width: "30%", + height: "34px", + }, + SkeletonThree: { + width: "15%", + height: "24px", + }, +}; + +const vanillaStyle = ` +:host { + display: block; +} +:host([hidden]): { + display: none; +} + + +`; + +const sheet = createStyleSheet(style); +const styleString = sheet.toString(); + +export function WidgetCodeVerificationView( + props: WidgetCodeVerificationViewProps +) { + const { states, refs, callbacks, text } = props; + + const resendCodeText = intl.formatMessage( + { + id: "resendCodeText", + defaultMessage: text.resendCodeText, + }, + { + resendCodeLink: ( + { + e.preventDefault(); + callbacks.resendEmail(); + }} + > + {text.resendCodeLabel} + + ), + } + ); + + const codeResentSuccessfully = intl.formatMessage( + { + id: "codeResentSuccessfully", + defaultMessage: text.codeResentSuccessfullyText, + }, + { + email: states.email, + } + ); + + const inputClass = states.verifyFailed + ? sheet.classes.CodeInputError + : sheet.classes.CodeInput; + + return ( +
+ +
+
+ + {intl.formatMessage( + { + id: `emailHeaderText`, + defaultMessage: states.verifyFailed + ? text.reverifyCodeHeaderText + : text.verifyCodeHeaderText, + }, + { email: states.email } + )} + +
+ {states.emailResent && ( + + {codeResentSuccessfully} + + )} +
+
+ + + + + + +
+ {states.verifyFailed && ( +

{text.invalidCodeText}

+ )} + + {text.verifyText} + +
+
+ {resendCodeText} +
+
+
+ ); +} diff --git a/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/sqm-code-verification.feature b/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/sqm-code-verification.feature new file mode 100644 index 0000000000..428cbd22e5 --- /dev/null +++ b/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/sqm-code-verification.feature @@ -0,0 +1,133 @@ +@author:andy @owner:andy +Feature: Cash payout code verification widget + + Background: User is on the Cash payouts setting page and sent a code to their email + Given they are on the Cash payouts setting page + + @minutia + Scenario Outline: Codes can be copy-pasted into the code input + Given is on the user's clipboard + When they paste into the first input + Then is auto-filled into all code inputs + And only one character/digit is in each input + + Examples: + | code | result | + | 123456 | 123456 | + | 1234567 | 123456 | + + @minutia + Scenario Outline: Focus shifts to the next code input after typing + Given a user is focused on input + When they type any character + Then the character is filled in the input + And the cursor focus changes to input + When they are focussed on input + And the input is clear + And they click "Backspace" + Then the cursor focusses on the input + + Examples: + | number | next | + | 1 | 2 | + | 2 | 3 | + | 3 | 4 | + | 4 | 5 | + | 5 | 6 | + + @minutia + Scenario: Clicking a code input highlights its contents + Given a code input with a character already entered + When a user clicks on the code input + Then the character is highlighted + + @minutia + Scenario: Successful code verification fires an event + Given a valid code is entered + And the mutation `verifyUserEmail` is successful + And an access key is recieved + Then a "sq:code-verified" event is dispatched + And it has detail + """ + { token: } + """ + + @motivating + Scenario: User enters verification code and proceed to setup + Given they are viewing the code verification widget + And they received a code in their email from the first step + When they enter the code provided in their email in to the code input + And press "Verify" + Then they will gain access to the Cash payout settings form + And the Cash payout settings form appears + + @minutia @ui + Scenario: Code verification header text changes based on user email + Given they are viewing the code verification widget + And they provided in the first step + Then the will change + + Examples: + | email | headerText | + | text@example.com | Enter the code sent to test@example.com from our referral provider, impact.com. | + | janedoe@gmail.com | Enter the code sent to janedoe@gmail.com from our referral provider, impact.com. | + + @minutia + Scenario: User re-sends verification code to email + Given they are viewing the code verification widget + And they see the link text + """ + Resend Code + """ + When they press the link text + Then a success alert appears with text + """ + Another code has been sent to email@example.com. + """ + And a new verification code is generated is sent to their email + + @minutia @ui + Scenario: User enters invalid verification code + Given they are viewing the code verification widget + When they enter an invalid verification code in to the code input + And press "Verify" + Then an error message will display: + """ + Please check your code and try again. If you’re still having trouble, try resending your code. + """ + And they will not gain access to the Cash payout settings form + + @motivating + Scenario: code-verification sends a 2FA email on first load if it's rendered by itself + Given `sqm-code-verification` is included in the page's html + But it is not a child element of `sqm-widget-verification` + When `sqm-code-verification` loads + Then it sends a 2FA email to the user's saved publisher email + + @motivating + Scenario Outline: Email verification prioritises sending to the connected Impact email + Given a user with participant email + And impactConnection + When the verification email is sent + Then is called + And the email is sent to + + Examples: + | participantEmail | impactConnection | mutation | finalEmail | + | null | null | N/A | N/A | + | participant@example.com | null | requestUserEmailVerification | participant@example.com | + | participantEmail | { user: { email: impact@example.com }} | requestImpactPublisherEmail2FA | impact@example.com | + + @motivating + Scenario Outline: Code verification uses different mutations depending on if the user has an impactConnection + Given a user with participant email + And impactConnection + And an email was sent with code "123456" + When the code is submitted + Then is called + + Examples: + | participantEmail | impactConnection | mutation | + | null | null | N/A | + | participant@example.com | null | verifyUserEmail | + | participantEmail | { user: { email: impact@example.com }} | submitImpactPublisherEmail2FACode | \ No newline at end of file diff --git a/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/sqm-code-verification.tsx b/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/sqm-code-verification.tsx new file mode 100644 index 0000000000..979a804d7d --- /dev/null +++ b/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/sqm-code-verification.tsx @@ -0,0 +1,120 @@ +import { isDemo, useSetParent } from "@saasquatch/component-boilerplate"; +import { useState, withHooks } from "@saasquatch/stencil-hooks"; +import { Component, h, Prop } from "@stencil/core"; +import deepmerge from "deepmerge"; +import { DemoData } from "../../../global/demo"; +import { getProps } from "../../../utils/utils"; +import { + WidgetCodeVerificationView, + WidgetCodeVerificationViewProps, +} from "./sqm-code-verification-view"; +import { useWidgetCodeVerification } from "./useCodeVerification"; +import { SHOW_CODE_NAMESPACE, VERIFICATION_PARENT_NAMESPACE } from "../keys"; + +@Component({ + tag: "sqm-code-verification", + shadow: true, +}) +export class WidgetCodeVerification { + /** + * @uiName Verify code widget header text + */ + @Prop() verifyCodeHeaderText: string = + "Enter the code sent to {email} from our referral provider, impact.com."; + /** + * @uiName Reverify code widget header text + */ + @Prop() reverifyCodeHeaderText: string = + "Enter the code sent to {email} from our referral provider, impact.com."; + /** + * Text displayed under verify button + * @uiName Resend code text + */ + @Prop() resendCodeText: string = "Didn't receive your code? {resendCodeLink}"; + /** + * The link that appears in the resend code link + * @uiName Resend code label + */ + @Prop() resendCodeLabel: string = "Resend code"; + /** + * Link text displayed under verify button + * @uiName Resend code text + */ + @Prop() codeResentSuccessfullyText: string = + "Another code has been sent to {email}"; + /** + * Error text displayed under verification input + * @uiName Invalid code text + */ + @Prop() invalidCodeText: string = + "Please check your code and try again. If you’re still having trouble, try resending your code."; + /** + * @uiName Verify code button text + */ + @Prop() + verifyText: string = "Verify"; + /** + * Displayed when the email verification fails due to a network error. The participant can try refreshing the page. + * @uiName Network error message + */ + @Prop() networkErrorMessage: string = + "An error occurred while verifying your email. Please refresh the page and try again."; + + /** + * @undocumented + * @uiType object + */ + @Prop() demoData?: DemoData; + + /** + * @undocumented + */ + @Prop() onVerification?: (token: string) => any; + + constructor() { + withHooks(this); + } + + disconnectedCallback() {} + + getTextProps() { + return getProps(this); + } + + render() { + const props = isDemo() + ? useDemoWidgetCodeVerification(this) + : useWidgetCodeVerification(this); + + return ; + } +} + +function useDemoWidgetCodeVerification( + props: WidgetCodeVerification +): WidgetCodeVerificationViewProps { + const [emailResent, setEmailResent] = useState(false); + const setVerifiedContext = useSetParent(VERIFICATION_PARENT_NAMESPACE); + + return deepmerge( + { + states: { + loading: false, + email: "test@example.com", + emailResent, + resendError: true, + verifyFailed: true, + }, + refs: { + codeWrapperRef: () => {}, + }, + callbacks: { + resendEmail: async () => setEmailResent(true), + submitCode: async () => setVerifiedContext(true), + }, + text: props.getTextProps(), + }, + props.demoData || {}, + { arrayMerge: (_, a) => a } + ); +} diff --git a/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/useCodeVerification.ts b/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/useCodeVerification.ts new file mode 100644 index 0000000000..2270065224 --- /dev/null +++ b/packages/mint-components/src/components/sqm-widget-verification/sqm-code-verification/useCodeVerification.ts @@ -0,0 +1,194 @@ +import { + useMutation, + useParent, + useParentValue, + useUserIdentity, +} from "@saasquatch/component-boilerplate"; +import { useEffect, useHost, useState } from "@saasquatch/stencil-hooks"; +import { gql } from "graphql-request"; +import { + SHOW_CODE_NAMESPACE, + VERIFICATION_EMAIL_NAMESPACE, + VERIFICATION_EVENT_KEY, +} from "../keys"; +import { WidgetCodeVerification } from "./sqm-code-verification"; +import { useVerificationEmail } from "../useVerificationEmail"; + +export const VerifyEmailWithCodeMutation = gql` + mutation submitImpactPublisherEmail2FACode( + $user: UserIdInput! + $code: String! + ) { + submitImpactPublisherEmail2FACode(user: $user, code: $code) { + verifiedEmail + accessKey + } + } +`; + +export function useCodeVerificationMutation() { + const user = useUserIdentity(); + const [request, { loading, data, errors }] = useMutation( + VerifyEmailWithCodeMutation + ); + + const verifyUserWithCodeMutation = async (code: string) => { + try { + const result = await request({ + user: { + id: user.id, + accountId: user.accountId, + }, + code, + }); + if (result instanceof Error || !result) throw new Error(); + + return result; + } catch (e) { + console.error("Failed to verify user", e); + return null; + } + }; + + return [verifyUserWithCodeMutation, { loading, data, errors }] as const; +} + +export function useWidgetCodeVerification(props: WidgetCodeVerification) { + const host = useHost(); + const [_, setShowCode] = useParent(SHOW_CODE_NAMESPACE); + const email = useParentValue( + VERIFICATION_EMAIL_NAMESPACE + ); + + const [emailSent, setEmailSent] = useState(false); + const [emailResent, setEmailResent] = useState(false); + const [codeRef, setCodeRef] = useState(null); + const [validationError, setValidationError] = useState(false); + const [emailError, setEmailError] = useState(false); + + const { + initialized, + verificationEmail, + send: [sendEmail, { loading: sendLoading, errors: sendErrors }], + verify: [verifyEmail, { loading: verifyLoading, errors: verifyErrors }], + } = useVerificationEmail(); + + useEffect(() => { + if (!codeRef) return; + + const slInputs = codeRef.querySelectorAll("sl-input"); + const codeElements = Array.from(slInputs).map( + (node) => + node.shadowRoot.querySelector(`input[name="code"]`) as HTMLInputElement + ); + + codeElements.forEach((element, idx) => { + element.addEventListener("focus", (e) => { + (e.target as HTMLInputElement).select(); + }); + element.addEventListener("keydown", (e: any) => { + if (e.key === "Backspace" && e.target.value === "") { + codeElements[Math.max(0, idx - 1)].focus(); + } + }); + element.addEventListener("input", (e: any) => { + const input = e.data; + if (!input) return; + if (idx === codeElements.length - 1) { + e.target.value = input.slice(0, 1); + return; + } + + if (input.length > 1) { + const rest = input.slice(1); + e.target.value = input.slice(0, 1); + codeElements[idx + 1].dispatchEvent( + new InputEvent("input", { + inputType: "insertFromPaste", + data: rest, + }) + ); + } + codeElements[idx + 1].focus(); + }); + }); + }, [codeRef]); + + const reset = () => { + setShowCode(false); + setValidationError(false); + }; + + const resendEmail = async () => { + // UI should only allow this to be called if initialized, but checking just in case + if (!initialized) return; + + const result = await sendEmail(); + if (!result) { + setEmailError(true); + return; + } + + if (emailSent) setEmailResent(true); + setEmailSent(true); + }; + + const submitCode = async () => { + const slInputs = codeRef.querySelectorAll("sl-input"); + const codeElements = Array.from(slInputs).map( + (node) => + node.shadowRoot.querySelector(`input[name="code"]`) as HTMLInputElement + ); + + if (codeElements.find((el) => !el.value)) { + setValidationError(true); + return; + } + + let code = ""; + codeElements.forEach((element) => { + code = `${code}${element.value}`; + }); + + // UI should only allow this to be called if initialized, but checking just in case + if (!initialized) return; + + setValidationError(false); + const res = await verifyEmail(code); + + if (res?.success) { + props.onVerification(res.accessKey); + reset(); + } else { + setValidationError(true); + } + }; + + useEffect(() => { + // Wait for mutations to be determined from user lookup + if (!initialized) return; + + // email should already exist if user has completed email-verification + if (!email) resendEmail(); + else setEmailSent(true); + }, [initialized]); + + return { + refs: { + codeWrapperRef: setCodeRef, + }, + states: { + email: verificationEmail, + emailResent, + resendError: sendErrors || verifyErrors, + initialiseLoading: !initialized, + loading: sendLoading || verifyLoading, + verifyFailed: !!validationError, + }, + callbacks: { + resendEmail, + submitCode, + }, + text: props.getTextProps(), + }; +} diff --git a/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/WidgetEmailVerification.stories.tsx b/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/WidgetEmailVerification.stories.tsx new file mode 100644 index 0000000000..3a61e38255 --- /dev/null +++ b/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/WidgetEmailVerification.stories.tsx @@ -0,0 +1,105 @@ +import { h } from "@stencil/core"; + +export default { + title: "Components/Widget Email Verification", +}; + +const defaultProps = { + email: "", + error: false, + loading: false, + initialLoading: false, + sendCodeError: false, +}; + +export const Default = () => ( + +); + +export const EmailIsPrefilled = () => ( +
+ +

Verify your email

+
+ + + To get your cash paid out directly to your bank account, please complete + your account setup + + + +
+); + +export const Loading = () => ( + +); + +export const SaveLoading = () => ( + +); + +export const WithHeader = () => ( +
+ +

Verify your email

+
+ + + To get your cash paid out directly to your bank account, please complete + your account setup + + + +
+); + +export const InvalidEmail = () => ( + +); + +export const SendCodeError = () => ( + +); diff --git a/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/readme.md b/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/readme.md new file mode 100644 index 0000000000..1a381ed6ee --- /dev/null +++ b/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/readme.md @@ -0,0 +1,39 @@ +# sqm-email-verification + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| -------------------------- | ----------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | +| `demoData` | -- | | `{ states?: { error: boolean; initialLoading: boolean; loading: boolean; email: string; sendCodeError: boolean; }; }` | `undefined` | +| `emailLabel` | `email-label` | | `string` | `"Email"` | +| `emailValidationErrorText` | `email-validation-error-text` | | `string` | `"Please enter a valid email"` | +| `sendCodeErrorDescription` | `send-code-error-description` | | `string` | `"Please try again. If this problem continues, contact our program {supportLink}."` | +| `sendCodeErrorHeader` | `send-code-error-header` | | `string` | `"There was an error sending your code"` | +| `sendCodeText` | `send-code-text` | | `string` | `"Send code"` | +| `supportLink` | `support-link` | | `string` | `"support team"` | +| `verifyEmailHeaderText` | `verify-email-header-text` | | `string` | `"Start by verifying your email. We’ll send you a code through our referral provider, impact.com."` | + + +## Dependencies + +### Used by + + - [sqm-stencilbook](../../sqm-stencilbook) + - [sqm-widget-verification](..) + +### Graph +```mermaid +graph TD; + sqm-stencilbook --> sqm-email-verification + sqm-widget-verification --> sqm-email-verification + style sqm-email-verification fill:#f9f,stroke:#333,stroke-width:4px +``` + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/sqm-email-verification-view.tsx b/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/sqm-email-verification-view.tsx new file mode 100644 index 0000000000..4cecc2e125 --- /dev/null +++ b/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/sqm-email-verification-view.tsx @@ -0,0 +1,172 @@ +import { h } from "@stencil/core"; +import { createStyleSheet } from "../../../styling/JSS"; +import { TextSpanView } from "../../sqm-text-span/sqm-text-span-view"; +import { intl } from "../../../global/global"; + +export interface WidgetEmailVerificationViewProps { + states: { + error: boolean; + initialLoading: boolean; + loading: boolean; + email: string; + sendCodeError: boolean; + }; + callbacks: { + submitEmail: (e: any) => Promise; + }; + text: { + verifyEmailHeaderText: string; + sendCodeText: string; + emailLabel: string; + sendCodeErrorHeader: string; + sendCodeErrorDescription: string; + emailValidationErrorText: string; + supportLink: string; + }; +} + +const style = { + Wrapper: { + display: "flex", + flexDirection: "column", + gap: "var(--sl-spacing-medium)", + marginTop: "var(--sl-spacing-medium)", + }, + InputsContainer: { + display: "flex", + gap: "var(--sl-spacing-medium)", + position: "relative", + flexDirection: "column", + maxWidth: "320px", + }, + ErrorAlertContainer: { + "&::part(base)": { + backgroundColor: "var(--sl-color-red-100)", + borderTop: "none", + }, + "&::part(message)": {}, + }, + ContinueButton: { + width: "100%", + maxWidth: "100px", + }, + SkeletonOne: { + width: "50%", + height: "16px", + }, + SkeletonTwo: { + width: "30%", + height: "34px", + }, + SkeletonThree: { + width: "15%", + height: "24px", + }, + ErrorInput: { + "&::part(base)": { + border: "1px solid var(--sl-color-danger-500)", + borderRadius: "var(--sl-input-border-radius-medium)", + }, + + "&::part(help-text)": { + color: "var(--sl-color-danger-500)", + }, + }, +}; + +const vanillaStyle = ` +:host { + display: block; +} +:host([hidden]): { + display: none; +} +`; + +const sheet = createStyleSheet(style); +const styleString = sheet.toString(); + +export function WidgetEmailVerificationView( + props: WidgetEmailVerificationViewProps +) { + const { states, callbacks, text } = props; + + const renderLoadingSkeleton = () => { + return ( +
+ + + +
+ ); + }; + + return ( +
+ + {states.sendCodeError && ( + + + {text.sendCodeErrorHeader} +
+ {intl.formatMessage( + { + id: "sendCodeErrorDescription", + defaultMessage: text.sendCodeErrorDescription, + }, + { + supportLink: ( + + {text.supportLink} + + ), + } + )} +
+ )} + {states.initialLoading ? ( + renderLoadingSkeleton() + ) : ( +
+ {text.verifyEmailHeaderText} + +
+ + + {text.sendCodeText} + +
+
+
+ )} +
+ ); +} diff --git a/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/sqm-email-verification.feature b/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/sqm-email-verification.feature new file mode 100644 index 0000000000..5c91f53bec --- /dev/null +++ b/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/sqm-email-verification.feature @@ -0,0 +1,61 @@ +@author:andy @owner:andy +Feature: Cash payout email verification widget + + Background: A user is on requires email verification to access the Cash payout form + Given they are on the Cash payouts setting page + + @minutia @ui + Scenario: User enters an invalid email + When they enter an invalid email in the input + And they press "Send Code" + Then the following error message is displayed under the input: + """ + Please enter a valid email + """ + + @minutia @ui + Scenario: Verification code fails to send + Given the user has a valid email in the input + And they press "Send Code" + And the code is unsuccessfully sent + Then the following error alert appears above the input + """ + There Was an error sending your code. + Please try again. If this problem continues, contact our program support team. + """ + + @minutia @ui + Scenario: Email verification widget is loading + When the widget is loading + Then skeleton loaders will display + + @minutia + Scenario: User's cannot edit their email + Given a user with email "test@example.com" + When they load `sqm-email-verification` + Then the email input is disabled + And the email input is prefilled with "test@example.com" + When they click the "Send Code" button + Then a 2FA email is sent to "test@example.com" + + @motivating + Scenario: Manually entered emails are set on the user if they don't have an email saved + Given a user with no email + Then the email input is not disabled + When they enter email "manual@example.com" + Then the upsertUser mutation is called before the verification email is sent + And the user has email "manual@example.com" saved as their participant email + + @motivating + Scenario Outline: Email verification prioritises sending to the connected Impact email + Given a user with participant email + And impactConnection + When they click the "Send Code" button + Then is called + And the email is sent to + + Examples: + | participantEmail | impactConnection | mutation | finalEmail | + | null | null | N/A | N/A | + | participant@example.com | null | requestUserEmailVerification | participant@example.com | + | participantEmail | { user: { email: impact@example.com }} | requestImpactPublisherEmail2FA | impact@example.com | \ No newline at end of file diff --git a/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/sqm-email-verification.tsx b/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/sqm-email-verification.tsx new file mode 100644 index 0000000000..fdde2abc15 --- /dev/null +++ b/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/sqm-email-verification.tsx @@ -0,0 +1,104 @@ +import { withHooks } from "@saasquatch/stencil-hooks"; +import { Component, h, Prop } from "@stencil/core"; +import { useWidgetEmailVerification } from "./useEmailVerification"; +import { DemoData } from "../../../global/demo"; +import { + WidgetEmailVerificationView, + WidgetEmailVerificationViewProps, +} from "./sqm-email-verification-view"; +import { getProps } from "../../../utils/utils"; +import { isDemo, useSetParent } from "@saasquatch/component-boilerplate"; +import deepmerge from "deepmerge"; +import { SHOW_CODE_NAMESPACE } from "../keys"; + +/** + * @uiName Widget Verification Gate + */ +@Component({ + tag: "sqm-email-verification", + shadow: true, +}) +export class WidgetEmailVerification { + /** + * @uiName Verify email widget header text + */ + @Prop() + verifyEmailHeaderText: string = + "Start by verifying your email. We’ll send you a code through our referral provider, impact.com."; + /** + * @uiName Send code to email alert header + */ + @Prop() + sendCodeErrorHeader: string = "There was an error sending your code"; + /** + * @uiName Support link text + */ + @Prop() + supportLink: string = "support team"; + /** + * @uiName Send code to email alert description + */ + @Prop() + sendCodeErrorDescription: string = + "Please try again. If this problem continues, contact our program {supportLink}."; + /** + * @uiName Email input label + */ + @Prop() + emailLabel: string = "Email"; + /** + * @uiName Send code button text + */ + @Prop() + sendCodeText: string = "Send code"; + /** + * @uiName Send code button text + */ + @Prop() + emailValidationErrorText: string = "Please enter a valid email"; + /** + * @undocumented + * @uiType object + */ + @Prop() demoData?: DemoData; + + constructor() { + withHooks(this); + } + disconnectedCallback() {} + getTextProps() { + return getProps(this); + } + + render() { + const props = isDemo() + ? useDemoWidgetEmailVerification(this) + : useWidgetEmailVerification(this); + + return ; + } +} + +function useDemoWidgetEmailVerification( + props: WidgetEmailVerification +): WidgetEmailVerificationViewProps { + const setShowCode = useSetParent(SHOW_CODE_NAMESPACE); + + return deepmerge( + { + states: { + error: true, + initialLoading: false, + loading: false, + email: "test@example.com", + sendCodeError: true, + }, + callbacks: { + submitEmail: async () => setShowCode(true), + }, + text: props.getTextProps(), + }, + props.demoData || {}, + { arrayMerge: (_, a) => a } + ); +} diff --git a/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/useEmailVerification.ts b/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/useEmailVerification.ts new file mode 100644 index 0000000000..3a9e9bbbbf --- /dev/null +++ b/packages/mint-components/src/components/sqm-widget-verification/sqm-email-verification/useEmailVerification.ts @@ -0,0 +1,165 @@ +import { + useMutation, + useParent, + useQuery, + useUserIdentity, +} from "@saasquatch/component-boilerplate"; +import { useEffect, useState } from "@saasquatch/universal-hooks"; +import { gql } from "graphql-request"; +import { SHOW_CODE_NAMESPACE, VERIFICATION_EMAIL_NAMESPACE } from "../keys"; +import { WidgetEmailVerification } from "./sqm-email-verification"; +import { WidgetEmailVerificationViewProps } from "./sqm-email-verification-view"; +import { useVerificationEmail } from "../useVerificationEmail"; + +export const UpsertUserEmailMutation = gql` + mutation upsertUser($userInput: UserInput!) { + upsertUser(userInput: $userInput) { + id + accountId + email + } + } +`; + +type User = { + id: string; + accountId: string; + email: string | null; + impactConnection: { + user: { + id: string; + email: string | null; + } | null; + } | null; +}; +export const UserLookupQuery = gql` + query user { + viewer { + ... on User { + id + accountId + email + impactConnection { + user { + id + email + } + } + } + } + } +`; + +export function useUpsertUserEmail() { + const user = useUserIdentity(); + const [request, { loading: loading, data, errors }] = useMutation( + UpsertUserEmailMutation + ); + + const upsertUserEmail = async (email: string) => { + try { + const result = await request({ + userInput: { + id: user.id, + accountId: user.accountId, + email, + }, + }); + if (result instanceof Error || !result) throw new Error(); + + return result; + } catch (e) { + console.error("Could not set email on user", e); + return undefined; + } + }; + + return [ + upsertUserEmail, + { + loading, + data, + errors, + }, + ] as const; +} + +export function useWidgetEmailVerification( + props: WidgetEmailVerification +): WidgetEmailVerificationViewProps { + const [_, setShowCode] = useParent(SHOW_CODE_NAMESPACE); + const [email, setEmail] = useParent(VERIFICATION_EMAIL_NAMESPACE); + const [emailExists, setEmailExists] = useState(false); + + const [error, setError] = useState(false); + const [mutationError, setMutationError] = useState(false); + const { + initialized, + send: [sendEmail, { loading: sendLoading, errors }], + } = useVerificationEmail(); + const [upsertUserEmail] = useUpsertUserEmail(); + const [loading, setLoading] = useState(true); + const { data, loading: initialLoading } = useQuery< + { viewer: User } | undefined + >(UserLookupQuery, {}); + + const emailRegex = + /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + + useEffect(() => { + if (!data?.viewer) return; + + setEmailExists(!!data.viewer.email); + }, [data]); + + useEffect(() => { + if (initialized) setLoading(false); + }, [initialized]); + + const submitEmail = async (e: any) => { + e.preventDefault(); + if (!data?.viewer) return; + + setLoading(true); + const toAddress = data.viewer.email; + if (!toAddress) { + // If no email on the user, set one + const formData = e.detail.formData; + const newEmail = formData.get("email").toString(); + + if (!emailRegex.test(newEmail)) { + setError(true); + return; + } + + const result = await upsertUserEmail(newEmail); + if (!result || !result.user.email) setError(true); + } + + // UI should not allow this call til initialisation is done + if (!initialized) return; + + const result = await sendEmail(); + if (!result || !result.success) setMutationError(true); + else { + // This is used to let the code verification widget know an email was already sent + setEmail(toAddress); + setShowCode(true); + } + setLoading(false); + }; + + return { + callbacks: { + submitEmail, + }, + states: { + loading, + initialLoading: initialLoading && !initialized, + error, + email: data?.viewer.email, + sendCodeError: mutationError, + }, + text: props.getTextProps(), + }; +} diff --git a/packages/mint-components/src/components/sqm-widget-verification/sqm-widget-verification-controller.tsx b/packages/mint-components/src/components/sqm-widget-verification/sqm-widget-verification-controller.tsx new file mode 100644 index 0000000000..0283e37163 --- /dev/null +++ b/packages/mint-components/src/components/sqm-widget-verification/sqm-widget-verification-controller.tsx @@ -0,0 +1,156 @@ +import { useParentState } from "@saasquatch/component-boilerplate"; +import { useHost, withHooks } from "@saasquatch/stencil-hooks"; +import { useCallback, useEffect, useState } from "@saasquatch/universal-hooks"; +import { Component, h, Host, Prop } from "@stencil/core"; +import debugFn from "debug"; +import { VERIFICATION_EVENT_KEY, VERIFICATION_PARENT_NAMESPACE } from "./keys"; +import { getProps } from "../../utils/utils"; +const debug = debugFn("sq:widget-verification"); + +function useTemplateChildren({ parent, callback }) { + const parentObserver = new MutationObserver(listenForTemplateChanges); + const childTemplateObserver = new MutationObserver(callback); + + parentObserver.observe(parent, { + childList: true, + // We only care about immediate children templates + subtree: false, + }); + listenForTemplateChanges({ addedNodes: parent.querySelectorAll("template") }); + + function listenForTemplateChanges(mutationList) { + // Be smart, only look at the mutation list + mutationList.addedNodes?.forEach((t) => { + childTemplateObserver.observe(t.content, { + childList: true, + attributes: true, + // Look deep into the templates for re-rendering + subtree: true, + }); + }); + } + + return () => { + parentObserver.disconnect(); + childTemplateObserver.disconnect(); + }; +} + +/** + * @uiName Widget Verification Controller + * @slots [{"name":"not-verified","title":"Not Verified template"},{"name":"verified","title":"Verified template"}] + * @canvasRenderer always-replace + * @exampleGroup Widget Verification + * @example Widget Verification Controller - + */ +@Component({ + tag: "sqm-widget-verification-controller", +}) +export class WidgetVerificationController { + constructor() { + withHooks(this); + } + disconnectedCallback() {} + + render() { + const [context, setContext] = useParentState({ + namespace: VERIFICATION_PARENT_NAMESPACE, + initialValue: false, + }); + + const [container, setContainer] = useState(undefined); + const [slot, setSlot] = useState(undefined); + + const updateTemplates = useCallback(() => { + const isAuth = context; + const templates = slot.querySelectorAll(`template`); + const template = Array.from(templates).find( + (t) => t.slot === (isAuth ? "verified" : "not-verified") + ); + + if (template) { + // use outerHTML if template's innerHTML is unset (only happens in Stencilbook) + const newContent = + template.innerHTML || template.firstElementChild.outerHTML; + + // if template contents are an exact match + if (newContent === container.innerHTML) { + debug("don't rerender"); + } else if (template) { + container.innerHTML = newContent; + } + } + + const plopTargets = Array.from(slot.children).filter( + (el) => el.tagName === "RAISINS-PLOP-TARGET" + ); + + // if editing in raisins + if (plopTargets.length) { + const loggedInPlopTargets = plopTargets.filter( + (el) => el.slot === "verified" + ); + + const loggedOutPlopTargets = plopTargets.filter( + (el) => el.slot === "not-verified" + ); + + loggedOutPlopTargets.forEach((target: HTMLElement, i) => { + if (isAuth) { + target.style.display = "none"; + return; + } + // Place last plop target at the bottom of the parent + if (i === loggedOutPlopTargets.length - 1) { + target.style.bottom = "0px"; + target.style.left = "0px"; + target.style.right = "0px"; + target.style.position = "absolute"; + } + + target.style.height = "25px"; + }); + + loggedInPlopTargets.forEach((target: HTMLElement, i) => { + if (!isAuth) { + target.style.display = "none"; + return; + } + // Place last plop target at the bottom of the parent + if (i === loggedInPlopTargets.length - 1) { + target.style.bottom = "0px"; + target.style.left = "0px"; + target.style.right = "0px"; + target.style.position = "absolute"; + } + + target.style.height = "25px"; + }); + } + }, [container, slot, context]); + + useEffect(() => { + if (!container || !slot) { + debug("DOM not ready:"); + return; + } + + // Run on first render + updateTemplates(); + + return useTemplateChildren({ parent: slot, callback: updateTemplates }); + }, [slot, container, context]); + + return ( + +
+ + +
+
+ +
+
+ ); + } +} diff --git a/packages/mint-components/src/components/sqm-widget-verification/sqm-widget-verification.feature b/packages/mint-components/src/components/sqm-widget-verification/sqm-widget-verification.feature new file mode 100644 index 0000000000..4679279759 --- /dev/null +++ b/packages/mint-components/src/components/sqm-widget-verification/sqm-widget-verification.feature @@ -0,0 +1,115 @@ +Feature: Widget verification flow + + @motivating + Scenario: sqm-widget-verification has a slot for a "verified" template + Given the following html + """ + + + + """ + When the html is loaded + Then the page displays the widget verification flow + When an email is successfully verified + Then the html inside the "verified" template is displayed + And the widget verification flow is hidden + + @motivating @ui + Scenario: Entering an email for verification + Given a user viewing the verification flow + Then they see the default sub-header text + """ + Start by verifying your email. We’ll send you a code through our referral provider, impact.com. + """ + And they see the default input label text "Email" + And they see the default button text "Send code" + + @minutia + Scenario: Invalid email error state + Given a user viewing the email verification step + When they enter an invalid email + And click the submit button + Then they see the default error text "Please enter a valid email" under the input + + @motivating + Scenario: Submitting a valid email sends a 2FA code to the email + Given enters a valid email + When they click the submit button + Then an email containing a 2FA is sent to the provided email address + And the component displays the code verification step + + @motivating @ui + Scenario: Code verification step + Given a user on the code verification step + Then they see the default sub-header text: + """ + Enter the code sent to {email} from our referral provider, impact.com. + """ + And the default button text: "Verify" + And the default help text: + """ + Didn't recieve your code? Resend Code + """ + + @motivating + Scenario Outline: Entering an invalid or expired 2FA code + Given a user has recieved the 2FA code "123456" via email + But they enter the code into the code input + And it has been
)} @@ -256,7 +270,22 @@ export const IndirectTaxFormView = (props: IndirectTaxFormViewProps) => { {text.error.generalTitle}
- {text.error.generalDescription} + {intl.formatMessage( + { + id: "generalDescription", + defaultMessage: text.error.generalDescription, + }, + { + supportLink: ( + + {text.supportLink} + + ), + } + )} )} {states.isPartner && ( @@ -269,7 +298,22 @@ export const IndirectTaxFormView = (props: IndirectTaxFormViewProps) => { {text.isPartnerAlertHeader}
- {text.isPartnerAlertDescription} + {intl.formatMessage( + { + id: "isPartnerAlertDescription", + defaultMessage: text.isPartnerAlertDescription, + }, + { + supportLink: ( + + {text.supportLink} + + ), + } + )} )}
@@ -328,7 +372,22 @@ export const IndirectTaxFormView = (props: IndirectTaxFormViewProps) => { slot="icon" name="exclamation-triangle" > - {text.cannotChangeInfoAlert} + {intl.formatMessage( + { + id: "cannotChangeInfoAlert", + defaultMessage: text.cannotChangeInfoAlert, + }, + { + supportLink: ( + + {text.supportLink} + + ), + } + )}
{ ); }; -export const Error = () => { - return ( - - ); -}; - export const NextPayout = () => { return ( - {states.error && ( - - -
- {text.error.errorTitleText} - {text.error.errorDescriptionText} -
-
- )} {states.loading ? ( renderLoadingSkeleton() ) : ( diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-payout-details-card/sqm-payout-details-card.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-payout-details-card/sqm-payout-details-card.tsx index 35bfcaa60c..fcdc17ed62 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-payout-details-card/sqm-payout-details-card.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-payout-details-card/sqm-payout-details-card.tsx @@ -34,30 +34,17 @@ export class PayoutDetailsCard { * @uiName Bank account field label */ @Prop() accountText: string = "Account"; - /** - * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. - * @uiName Payout error message title - */ - @Prop() errorTitleText: string = "Your payout is on hold "; - /** - * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. - * @uiName Payout error message description - */ - @Prop() - errorDescriptionText: string = - "If you’ve recently added your payout information, please wait while we verify your information. If it’s still on hold after a few days, please contact Support or check your inbox for an email from our referral program provider, impact.com."; /** * Text displayed for existing publishers that do not have saved banking information. * @uiName Payout missing information subtext */ @Prop() payoutMissingInformationText: string = "Missing banking information, go to Impact.com to resolve."; - /** * @undocumented */ @Prop() demoData?: DemoData; - + constructor() { withHooks(this); } @@ -68,10 +55,6 @@ export class PayoutDetailsCard { const props = getProps(this); return { ...props, - error: { - errorTitleText: this.errorTitleText, - errorDescriptionText: this.errorDescriptionText, - }, }; } diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-payout-status-alert/PayoutStatusAlert.stories.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-payout-status-alert/PayoutStatusAlert.stories.tsx new file mode 100644 index 0000000000..2241e139ca --- /dev/null +++ b/packages/mint-components/src/components/tax-and-cash/sqm-payout-status-alert/PayoutStatusAlert.stories.tsx @@ -0,0 +1,60 @@ +import { h } from "@stencil/core"; + +export default { + title: "Components/Payout Status Alert", +}; + +const defaultProps = { + error: false, + status: "INFORMATION_REQUIRED" as const, + loading: false, + veriffLoading: false, +}; + +export const InformationRequired = () => ( + +); + +export const VerifyIdentity = () => ( + +); + +export const VerifyIdentityRequiredInternal = () => ( + +); + +export const VerifyIdentityReviewInternal = () => ( + +); + +export const VerifyIdentityFailedInternal = () => ( + +); + +export const Hold = () => ( + +); + +export const Loading = () => ( + +); + +export const Error = () => ( + +); diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-payout-status-alert/readme.md b/packages/mint-components/src/components/tax-and-cash/sqm-payout-status-alert/readme.md new file mode 100644 index 0000000000..320d68430c --- /dev/null +++ b/packages/mint-components/src/components/tax-and-cash/sqm-payout-status-alert/readme.md @@ -0,0 +1,53 @@ +# sqm-payout-status-alert + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| ----------------------------------------- | -------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `cashPayoutsPageUrl` | `cash-payouts-page-url` | | `string` | `"/cash"` | +| `demoData` | -- | | `{ states?: { error: boolean; loading: boolean; status: PayoutStatus; veriffLoading: boolean; }; data?: { type: "SquatchJS2" \| "SquatchAndroid" \| "SquatchIOS" \| "SquatchPortal" \| "SquatchAdmin" \| "None"; }; }` | `undefined` | +| `errorDescription` | `error-description` | | `string` | `"There was an error with determining your payout status."` | +| `errorHeader` | `error-header` | | `string` | `"Could not determine payout status."` | +| `holdDescription` | `hold-description` | | `string` | `"Please check your inbox for an email from our referral provider, impact.com. It contains details on how to resolve this issue. If you need further assistance, feel free to reach out to our {supportLink}."` | +| `holdHeader` | `hold-header` | | `string` | `"Your payouts and account are on hold"` | +| `informationRequiredButtonText` | `information-required-button-text` | | `string` | `"Payouts & Tax Settings"` | +| `informationRequiredDescription` | `information-required-description` | | `string` | `"Submit your banking details and tax documents to receive your rewards."` | +| `informationRequiredHeader` | `information-required-header` | | `string` | `"Payout and tax information required"` | +| `supportLink` | `support-link` | | `string` | `"support team"` | +| `verificationFailedInternalDescription` | `verification-failed-internal-description` | | `string` | `"Identity verification has failed. Our team is reviewing the report and will contact you with further information. If you don't hear from us contact our {supportLink}."` | +| `verificationFailedInternalHeader` | `verification-failed-internal-header` | | `string` | `"Identity Verification Unsuccessful"` | +| `verificationRequiredButtonText` | `verification-required-button-text` | | `string` | `"Start Verification"` | +| `verificationRequiredDescription` | `verification-required-description` | | `string` | `"It should only take a few minutes verify. If you run in to an issue verifying your identity contact our {supportLink}."` | +| `verificationRequiredHeader` | `verification-required-header` | | `string` | `"Verify your identity"` | +| `verificationRequiredInternalDescription` | `verification-required-internal-description` | | `string` | `"Identity verification submission has been received. Our system is currently performing additional checks and analyzing the results. You will be updated shortley. If you don't hear from us contact our {supportLink}."` | +| `verificationRequiredInternalHeader` | `verification-required-internal-header` | | `string` | `"Identity Verification in Progress"` | +| `verificationReviewInternalDescription` | `verification-review-internal-description` | | `string` | `"Identity verification requires further review due to a potential error. Our team is reviewing the information and will update you shortly. If you don't hear from us contact our {supportLink}."` | +| `verificationReviewInternalHeader` | `verification-review-internal-header` | | `string` | `"Identity Verification Under Review"` | + + +## Dependencies + +### Used by + + - [sqm-stencilbook](../../sqm-stencilbook) + +### Depends on + +- [sqm-scroll](../../sqm-scroll) + +### Graph +```mermaid +graph TD; + sqm-payout-status-alert --> sqm-scroll + sqm-stencilbook --> sqm-payout-status-alert + style sqm-payout-status-alert fill:#f9f,stroke:#333,stroke-width:4px +``` + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-payout-status-alert/sqm-payout-status-alert-view.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-payout-status-alert/sqm-payout-status-alert-view.tsx new file mode 100644 index 0000000000..697b97b9f3 --- /dev/null +++ b/packages/mint-components/src/components/tax-and-cash/sqm-payout-status-alert/sqm-payout-status-alert-view.tsx @@ -0,0 +1,292 @@ +import { h } from "@stencil/core"; +import { createStyleSheet } from "../../../styling/JSS"; +import { PayoutStatus } from "./usePayoutStatus"; +import { intl } from "../../../global/global"; +export interface PayoutStatusAlertViewProps { + states: { + error: boolean; + loading: boolean; + status: PayoutStatus; + veriffLoading: boolean; + }; + data: { + type: + | "SquatchJS2" + | "SquatchAndroid" + | "SquatchIOS" + | "SquatchPortal" + | "SquatchAdmin" + | "None"; + }; + callbacks: { + onClick: () => void; + onTermsClick: () => void; + }; + text: { + informationRequiredHeader: string; + informationRequiredDescription: string; + informationRequiredButtonText: string; + verificationRequiredHeader: string; + verificationRequiredDescription: string; + verificationRequiredButtonText: string; + verificationRequiredInternalHeader: string; + verificationRequiredInternalDescription: string; + verificationReviewInternalHeader: string; + verificationReviewInternalDescription: string; + verificationFailedInternalHeader: string; + verificationFailedInternalDescription: string; + holdHeader: string; + holdDescription: string; + supportLink: string; + errorHeader: string; + errorDescription: string; + }; +} + +const style = { + SkeletonOne: { + width: "50%", + height: "16px", + }, + AlertDescriptionText: { + margin: "0", + marginBottom: "var(--sl-spacing-small)", + }, + ErrorAlertContainer: { + "&::part(base)": { + backgroundColor: "var(--sl-color-red-100)", + borderTop: "none", + }, + "& sl-icon::part(base)": { + color: "var(--sl-color-danger-500)", + }, + }, + WarningAlertContainer: { + "&::part(base)": { + backgroundColor: "var(--sl-color-yellow-100)", + borderTop: "none", + }, + "& sl-icon::part(base)": { + color: "var(--sl-color-danger-500)", + }, + }, + InfoAlertContainer: { + "&::part(base)": { + backgroundColor: "var(--sl-color-sky-100)", + borderTop: "none", + }, + "& sl-icon::part(base)": { + color: "var(--sl-color-blue-500)", + }, + }, + Dialog: { + "&::part(panel)": { + height: "600px", + }, + "&::part(close-button)": { + marginBottom: "var(--sl-spacing-xx-large)", + }, + "&::part(title)": { + display: "none", + }, + "&::part(header)": {}, + "&::part(body)": { padding: "0" }, + "&::part(footer)": {}, + }, +}; + +const sheet = createStyleSheet(style); +const styleString = sheet.toString(); + +export function PayoutStatusAlertView(props: PayoutStatusAlertViewProps) { + const { text, states, data, callbacks } = props; + + if (states.loading) { + return ; + } + + if (states.status === "DONE") { + return
; + } + + function getAlert(status: PayoutStatus) { + if (states.error) + return { + header: text.errorHeader, + description: text.errorDescription, + buttonText: null, + alertType: "critical", + icon: "exclamation-triangle", + class: sheet.classes.ErrorAlertContainer, + }; + + switch (status) { + case "INFORMATION_REQUIRED": + return { + header: text.informationRequiredHeader, + description: text.informationRequiredDescription, + buttonText: text.informationRequiredButtonText, + alertType: "info", + icon: "info-circle", + class: sheet.classes.InfoAlertContainer, + }; + case "VERIFICATION:REQUIRED": + return { + header: text.verificationRequiredHeader, + description: intl.formatMessage( + { + id: "verificationRequiredDescription", + defaultMessage: text.verificationRequiredDescription, + }, + { + supportLink: ( + + {text.supportLink} + + ), + } + ), + buttonText: text.verificationRequiredButtonText, + alertType: "warning", + icon: "exclamation-triangle", + class: sheet.classes.WarningAlertContainer, + }; + case "VERIFICATION:INTERNAL": + return { + header: text.verificationRequiredInternalHeader, + description: intl.formatMessage( + { + id: "verificationRequiredInternalDescription", + defaultMessage: text.verificationRequiredInternalDescription, + }, + { + supportLink: ( + + {text.supportLink} + + ), + } + ), + alertType: "warning", + icon: "exclamation-triangle", + class: sheet.classes.WarningAlertContainer, + }; + case "VERIFICATION:REVIEW": + return { + header: text.verificationReviewInternalHeader, + description: intl.formatMessage( + { + id: "verificationReviewInternalDescription", + defaultMessage: text.verificationReviewInternalDescription, + }, + { + supportLink: ( + + {text.supportLink} + + ), + } + ), + alertType: "warning", + icon: "exclamation-triangle", + class: sheet.classes.WarningAlertContainer, + }; + case "VERIFICATION:FAILED": + return { + header: text.verificationFailedInternalHeader, + description: intl.formatMessage( + { + id: "verificationFailedInternalDescription", + defaultMessage: text.verificationFailedInternalDescription, + }, + { + supportLink: ( + + {text.supportLink} + + ), + } + ), + alertType: "critical", + icon: "exclamation-octagon", + class: sheet.classes.ErrorAlertContainer, + }; + case "HOLD": + return { + header: text.holdHeader, + description: intl.formatMessage( + { + id: "holdDescription", + defaultMessage: text.holdDescription, + }, + { + supportLink: ( + + {text.supportLink} + + ), + } + ), + buttonText: null, + alertType: "warning", + icon: "exclamation-triangle", + class: sheet.classes.WarningAlertContainer, + }; + default: + return; + } + } + + function getButton(status: PayoutStatus) { + switch (status) { + case "INFORMATION_REQUIRED": + return data.type === "SquatchJS2" ? ( + + ) : data.type === "SquatchPortal" ? ( + + {text.informationRequiredButtonText} + + ) : ( + // Demo case + + {text.informationRequiredButtonText} + + ); + case "VERIFICATION:REQUIRED": + return ( + + {text.verificationRequiredButtonText} + + ); + default: + return; + } + } + + return ( +
+ + + + {getAlert(states.status)?.header} +

+ {getAlert(states.status)?.description} +

+ {getButton(states.status)} +
+
+ ); +} diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-payout-status-alert/sqm-payout-status-alert.feature b/packages/mint-components/src/components/tax-and-cash/sqm-payout-status-alert/sqm-payout-status-alert.feature new file mode 100644 index 0000000000..4bb532dd49 --- /dev/null +++ b/packages/mint-components/src/components/tax-and-cash/sqm-payout-status-alert/sqm-payout-status-alert.feature @@ -0,0 +1,106 @@ +@author:andy @owner:andy +Feature: Cash payout status widget alert + + Background: A user sees alert status banners in the widget related to their tax form status + Given they are viewing the widget + + @motivating + Scenario: User has not completed payout and tax form + Given they have impactConnection as one of the following + | impactConnection | + | null | + | { publisher: null } | + | { connected: false, publisher: null } | + | { connected: false, publisher: { ... } } | + | { publisher: { payoutsAccount: null }} | + Then a blue info banner appears with a header: + """ + Payout and tax information required + """ + And description + """ + Submit your banking details and tax documents to receive your rewards. + """ + And the alert contains a button with text + """ + Payouts & Tax Settings + """ + When they click the button + Then the page will scroll to the payouts and tax form + + @motivating + Scenario: User completes form and payout information required alert dissapears + Given they have impactConnection as one of the following + | impactConnection | + | { connected: true, publisher: { payoutsAccount: { hold: false } } } | + And they have completed the payout and tax form flow + Then the "Payout and tax information required" alert disappears + And a button appears in the big stat with text: + """ + Payout & Tax Settings + """ + + @motivating + Scenario: User has not verified their identity + Given they have impactConnection as one of the following + | impactConnection | + | { connected: true, publisher: { payoutsAccount: { hold: true, holdReasons: [IDV_CHECK_REQUIRED] } } } | + And they have completed the payout and tax form flow + Then a yellow warning banner appears with a header: + """ + Verify your identity + """ + And description + """ + Complete your verification to start receiving your cash rewards. It should only take a few minutes verify. + """ + And the alert contains a button with text + """ + Start Verification + """ + When they click the button + Then a modal opens with an iframe to verify their identity + + @minutia + Scenario Outline: Alert displays the current state of the verification process + Given a user has included in their holdReasons + And they complete the payout and tax form flow + Then a banner appears + And the alert has heading + And the alert has description + + Examples: + | holdReason | color | heading | description | + | IDV_CHECK_REQUIRED_INTERNAL | yellow | Verification In Progress | Verification submission has been received. Our system is currently performing additional checks and analyzing the results. You will be updated shortly. | + | IDV_CHECK_REVIEW_INTERNAL | yellow | Verification Under Review | Verification requires further review due to a potential error. Our team is reviewing the information and will update you shortly. | + | IDV_CHECK_FAILED_INTERNAL | red | Identity verification unsuccessful | Identity verification has failed. Our team is reviewing the report and will contact you with further information. | + + @motivating + Scenario: User has hold reasons + Given they have impactConnection as one of the following + | impactConnection | + | { connected: true, publisher: { payoutsAccount: { hold: true } } } | + And hold reasons don't include any of the following + | holdReason | + | IDV_CHECK_REQUIRED | + | IDV_CHECK_REQUIRED_INTERNAL | + | IDV_CHECK_REVIEW_INTERNAL | + | IDV_CHECK_FAILED_INTERNAL | + And they have completed the payout and tax form flow + Then a yellow warning banner appears with a header: + """ + Your payouts and account are on hold + """ + And description + """ + Please check your inbox for an email from our referral provider, impact.com. It contains details on how to resolve this issue. If you need further assistance, feel free to reach out to {support email}. + """ + + @minutia + Scenario: The banner refreshes it's state as the user goes through the tax form + Given a user filling out the tax form + When they fill in a form + And click to submit + Then a "sqm:tax-form-updated" custom event is fired + And the banner refreshes + diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-payout-status-alert/sqm-payout-status-alert.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-payout-status-alert/sqm-payout-status-alert.tsx new file mode 100644 index 0000000000..21ca7928c9 --- /dev/null +++ b/packages/mint-components/src/components/tax-and-cash/sqm-payout-status-alert/sqm-payout-status-alert.tsx @@ -0,0 +1,151 @@ +import { withHooks } from "@saasquatch/stencil-hooks"; +import { Component, h, Prop } from "@stencil/core"; +import deepmerge from "deepmerge"; +import { DemoData } from "../../../global/demo"; +import { getProps } from "../../../utils/utils"; +import { + PayoutStatusAlertView, + PayoutStatusAlertViewProps, +} from "./sqm-payout-status-alert-view"; +import { usePayoutStatus } from "./usePayoutStatus"; +import { isDemo } from "@saasquatch/component-boilerplate"; + +/** + * @uiName Payout Status Alert + * @exampleGroup Tax and Cash + * @example Payout Status Alert - + */ +@Component({ + tag: "sqm-payout-status-alert", + shadow: true, +}) +export class PayoutStatusAlert { + /** + * @uiName Info required alert header + */ + @Prop() informationRequiredHeader: string = + "Payout and tax information required"; + /** + * @uiName Info required alert description + */ + @Prop() informationRequiredDescription: string = + "Submit your banking details and tax documents to receive your rewards."; + /** + * @uiName Info required alert button text + */ + @Prop() informationRequiredButtonText: string = "Payouts & Tax Settings"; + /** + * @uiName Verification required alert header + */ + @Prop() verificationRequiredHeader: string = "Verify your identity"; + /** + * @uiName Verification required alert description + */ + @Prop() verificationRequiredDescription: string = + "It should only take a few minutes verify. If you run in to an issue verifying your identity contact our {supportLink}."; + /** + * @uiName Verification required alert button text + */ + @Prop() verificationRequiredButtonText: string = "Start Verification"; + /** + * @uiName Verification required internal alert header + */ + @Prop() verificationRequiredInternalHeader: string = + "Identity Verification in Progress"; + /** + * @uiName Verification required internal alert description + */ + @Prop() verificationRequiredInternalDescription: string = + "Identity verification submission has been received. Our system is currently performing additional checks and analyzing the results. You will be updated shortley. If you don't hear from us contact our {supportLink}."; + /** + * @uiName Verification review internal alert header + */ + @Prop() verificationReviewInternalHeader: string = + "Identity Verification Under Review"; + /** + * @uiName Verification review internal alert description + */ + @Prop() verificationReviewInternalDescription: string = + "Identity verification requires further review due to a potential error. Our team is reviewing the information and will update you shortly. If you don't hear from us contact our {supportLink}."; + /** + * @uiName Verification failed internal alert header + */ + @Prop() verificationFailedInternalHeader: string = + "Identity Verification Unsuccessful"; + /** + * @uiName Verification failed internal alert description + */ + @Prop() verificationFailedInternalDescription: string = + "Identity verification has failed. Our team is reviewing the report and will contact you with further information. If you don't hear from us contact our {supportLink}."; + /** + * @uiName Payout on hold alert header + */ + @Prop() holdHeader: string = "Your payouts and account are on hold"; + /** + * @uiName Payout on hold alert description + */ + @Prop() holdDescription: string = + "Please check your inbox for an email from our referral provider, impact.com. It contains details on how to resolve this issue. If you need further assistance, feel free to reach out to our {supportLink}."; + /** + * @uiName Cash & Payouts Microsite Page (only set if alert is used in a microsite) + */ + @Prop() cashPayoutsPageUrl: string = "/cash"; + /** + * @uiName Support link text + */ + @Prop() supportLink: string = "support team"; + /** + * @uiName Error header + */ + @Prop() errorHeader: string = "Could not determine payout status."; + /** + * @uiName Error description + */ + @Prop() errorDescription: string = + "There was an error with determining your payout status."; + + /** + * @undocumented + * @uiType object + */ + @Prop() demoData?: DemoData; + + constructor() { + withHooks(this); + } + disconnectedCallback() {} + getTextProps() { + return getProps(this); + } + + render() { + const props = isDemo() + ? useDemoPayoutStatusAlert(this) + : usePayoutStatus(this); + + return ; + } +} + +function useDemoPayoutStatusAlert( + props: PayoutStatusAlert +): PayoutStatusAlertViewProps { + return deepmerge( + { + states: { + error: false, + status: "INFORMATION_REQUIRED", + loading: false, + showVerifyIdentity: false, + }, + data: { type: "SquatchAdmin" }, + text: props.getTextProps(), + callbacks: { + onTermsClick: () => {}, + onClick: () => console.log("show"), + }, + }, + props.demoData || {}, + { arrayMerge: (_, a) => a } + ); +} diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-payout-status-alert/usePayoutStatus.ts b/packages/mint-components/src/components/tax-and-cash/sqm-payout-status-alert/usePayoutStatus.ts new file mode 100644 index 0000000000..141557ad0d --- /dev/null +++ b/packages/mint-components/src/components/tax-and-cash/sqm-payout-status-alert/usePayoutStatus.ts @@ -0,0 +1,100 @@ +import { getEnvironmentSDK, useQuery } from "@saasquatch/component-boilerplate"; +import { useEffect, useState } from "@saasquatch/stencil-hooks"; +import { gql } from "graphql-request"; +import { TAX_FORM_UPDATED_EVENT_KEY } from "../eventKeys"; +import { UserQuery } from "../sqm-tax-and-cash/data"; +import { useVeriffApp, VERIFF_COMPLETE_EVENT_KEY } from "../useVeriffApp"; +import { PayoutStatusAlert } from "./sqm-payout-status-alert"; + +export type PayoutStatus = + | "INFORMATION_REQUIRED" + | "VERIFICATION:REQUIRED" + | "VERIFICATION:INTERNAL" + | "VERIFICATION:REVIEW" + | "VERIFICATION:FAILED" + | "HOLD" + | "DONE"; + +const GET_USER_STATUS = gql` + query getUserStatus { + user: viewer { + ... on User { + id + impactConnection { + connected + publisher { + id + payoutsAccount { + hold + holdReasons + } + } + } + } + } + } +`; + +export function getStatus(data: UserQuery): PayoutStatus { + const account = data.user.impactConnection?.publisher?.payoutsAccount; + + if (!data.user?.impactConnection?.connected || !account) + return "INFORMATION_REQUIRED"; + if (account.holdReasons?.includes("IDV_CHECK_REQUIRED")) + return "VERIFICATION:REQUIRED"; + if (account.holdReasons?.includes("IDV_CHECK_REQUIRED_INTERNAL")) + return "VERIFICATION:INTERNAL"; + if (account.holdReasons?.includes("IDV_CHECK_REVIEW_INTERNAL")) + return "VERIFICATION:REVIEW"; + if (account.holdReasons?.includes("IDV_CHECK_FAILED_INTERNAL")) + return "VERIFICATION:FAILED"; + if (account.hold) return "HOLD"; + return "DONE"; +} + +export function usePayoutStatus(props: PayoutStatusAlert) { + const { type } = getEnvironmentSDK(); + const { loading, data, errors, refetch } = useQuery( + GET_USER_STATUS, + {} + ); + const { + render, + loading: veriffLoading, + errors: veriffErrors, + } = useVeriffApp(); + const [status, setStatus] = useState(undefined); + + useEffect(() => { + if (!data) return; + + const s = getStatus(data); + setStatus(s); + }, [data]); + + useEffect(() => { + const cb = () => refetch(); + window.addEventListener(TAX_FORM_UPDATED_EVENT_KEY, cb); + window.addEventListener(VERIFF_COMPLETE_EVENT_KEY, cb); + return () => { + window.removeEventListener(TAX_FORM_UPDATED_EVENT_KEY, cb); + window.removeEventListener(VERIFF_COMPLETE_EVENT_KEY, cb); + }; + }, []); + + return { + states: { + loading, + veriffLoading, + status, + error: !!errors, + }, + data: { type }, + text: props.getTextProps(), + callbacks: { + onTermsClick: () => + window.open(props.cashPayoutsPageUrl, "_blank").focus(), + onClick: render, + }, + }; +} diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/readme.md b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/readme.md index c3a581c95b..e7eda0ac0e 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/readme.md +++ b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/readme.md @@ -7,61 +7,71 @@ ## Properties -| Property | Attribute | Description | Type | Default | -| --------------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `accountText` | `account-text` | Shown before the participant’s bank account information. | `string` | `"Account"` | -| `badgeTextAwaitingReview` | `badge-text-awaiting-review` | Additional text displayed next to the tax form's status badge. | `string` | `"Awaiting review. Submitted on {dateSubmitted}."` | -| `badgeTextSubmittedOn` | `badge-text-submitted-on` | Additional text displayed next to the tax form's status badge | `string` | `"Submitted on {dateSubmitted}."` | -| `badgeTextSubmittedOnW8` | `badge-text-submitted-on-w-8` | Additional text displayed next to the tax form's status badge. | `string` | `"Submitted on {dateSubmitted}. Valid for three years after submission."` | -| `bankingInformationSectionHeader` | `banking-information-section-header` | | `string` | `"Payout information"` | -| `cancelButton` | `cancel-button` | | `string` | `"Cancel"` | -| `dateColumnTitle` | `date-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Date"` | -| `demoData` | -- | | `{ states?: { status?: string; documentType: TaxDocumentType; documentTypeString: string; canEditPayoutInfo: boolean; disabled?: boolean; dateSubmitted?: string; noFormNeeded?: boolean; indirectTaxType?: string; qstNumber?: string; subRegionTaxNumber?: string; subRegion?: string; indirectTaxNumber?: string; province?: string; country?: string; notRegistered?: boolean; isBusinessEntity?: boolean; loading?: boolean; loadingError?: boolean; showNewFormDialog: boolean; errors?: { general?: boolean; }; }; }` | `undefined` | -| `earningsAfterTaxColumnTitle` | `earnings-after-tax-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Earnings after tax"` | -| `earningsColumnTitle` | `earnings-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Earnings"` | -| `editPaymentInformationButton` | `edit-payment-information-button` | | `string` | `"Edit Payout Information"` | -| `errorDescriptionText` | `error-description-text` | Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. | `string` | `"If you’ve recently added your payout information, please wait while we verify your information. If it’s still on hold after a few days, please contact Support or check your inbox for an email from our referral program provider, impact.com."` | -| `errorTitleText` | `error-title-text` | Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. | `string` | `"Your payout is on hold "` | -| `generalErrorDescription` | `general-error-description` | Part of the alert displayed at the top of the page. | `string` | `"Please review your information and try again. If this problem continues, contact Support."` | -| `generalErrorTitle` | `general-error-title` | Part of the alert displayed at the top of the page. | `string` | `"There was a problem submitting your information"` | -| `indirectTaxColumnTitle` | `indirect-tax-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Indirect tax"` | -| `indirectTaxDetails` | `indirect-tax-details` | Displayed to participants who have submitted their indirect tax information. | `string` | `"{indirectTaxType} number: {indirectTaxNumber}"` | -| `indirectTaxInfoCanada` | `indirect-tax-info-canada` | If the participant is registered for indirect tax in Canada, display the province they’re registered in. | `string` | `"Registered in {province}, {country}"` | -| `indirectTaxInfoOtherCountry` | `indirect-tax-info-other-country` | If the participant is registered for indirect tax, display the country they’re registered in. | `string` | `"Registered in {country}"` | -| `indirectTaxInfoSectionHeader` | `indirect-tax-info-section-header` | | `string` | `"Indirect tax"` | -| `indirectTaxInfoSpain` | `indirect-tax-info-spain` | If the participant is registered for indirect tax in Spain, display the region they’re registered in. | `string` | `"Registered in {country}, {subRegion}"` | -| `indirectTaxTooltipSupport` | `indirect-tax-tooltip-support` | | `string` | `"To make changes to your indirect tax information, please contact Support."` | -| `invalidForm` | `invalid-form` | Additional text displayed next to the tax form's status badge. | `string` | `"Make sure your information is correct and submit new form."` | -| `invoiceColumnTitle` | `invoice-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Invoice"` | -| `invoiceDescription` | `invoice-description` | | `string` | `"View and download your invoices to report your earnings and stay tax compliant."` | -| `invoiceEmptyStateHeader` | `invoice-empty-state-header` | | `string` | `"View your invoice details"` | -| `invoiceEmptyStateText` | `invoice-empty-state-text` | | `string` | `"Refer a friend to view the status of your invoices and rewards earned"` | -| `invoiceHeader` | `invoice-header` | | `string` | `"Invoices"` | -| `invoiceMoreLabel` | `invoice-more-label` | | `string` | `"Next"` | -| `invoicePrevLabel` | `invoice-prev-label` | | `string` | `"Prev"` | -| `loadingErrorAlertDescription` | `loading-error-alert-description` | Part of the alert displayed at the top of the page. | `string` | `"Please refresh the page and try again. If this problem continues, contact Support."` | -| `loadingErrorAlertHeader` | `loading-error-alert-header` | Part of the alert displayed at the top of the page. | `string` | `"There was a problem loading your form"` | -| `newFormButton` | `new-form-button` | | `string` | `"Submit new form"` | -| `noFormNeededSubtext` | `no-form-needed-subtext` | No other statuses or badges will be displayed in the tax form section in this case. | `string` | `"Tax documents are only required if you are based in the US or joining the referral program of a US based brand."` | -| `notRegisteredForTax` | `not-registered-for-tax` | | `string` | `"Not registered for indirect tax. If you’ve previously registered with your tax authority, contact Support to add your information to stay tax compliant."` | -| `payoutFromImpact` | `payout-from-impact` | Displayed under the payout details card. | `string` | `"Your balance may take up to 24 hours to update. Payouts will be sent from our referral program provider, impact.com."` | -| `payoutMissingInformationText` | `payout-missing-information-text` | Text displayed for existing publishers that do not have saved banking information. | `string` | `"Missing banking information, go to Impact.com to resolve."` | -| `qstNumber` | `qst-number` | Displayed to participants registered in Quebec, Canada. | `string` | `"QST number: {qstNumber}"` | -| `replaceTaxFormModalBodyText` | `replace-tax-form-modal-body-text` | | `string` | `"Submitting a new tax form will remove your existing form. Make sure to sign and complete your new tax form to prevent any issues with your next payout."` | -| `replaceTaxFormModalHeader` | `replace-tax-form-modal-header` | | `string` | `"Replace existing tax form"` | -| `statusBadgeText` | `status-badge-text` | | `string` | `"{badgeText, select, payoutToday {Payout Today} nextPayout {Next Payout} }"` | -| `statusTextActive` | `status-text-active` | | `string` | `"Active"` | -| `statusTextNotActive` | `status-text-not-active` | | `string` | `"Invalid Tax Form"` | -| `statusTextNotVerified` | `status-text-not-verified` | Displayed when the participant submitted their form but it is awaiting review. | `string` | `"Not Verified"` | -| `subRegionTaxNumber` | `sub-region-tax-number` | | `string` | `"Income tax number: {subRegionTaxNumber}"` | -| `taxAlertHeaderNotActiveW8` | `tax-alert-header-not-active-w-8` | Part of the alert displayed at the top of the page. | `string` | `"{documentType} tax form is invalid"` | -| `taxAlertHeaderNotActiveW9` | `tax-alert-header-not-active-w-9` | Part of the alert displayed at the top of the page. | `string` | `"Your W9 tax form has personal information that doesn’t match your profile"` | -| `taxAlertNotActiveMessageW8` | `tax-alert-not-active-message-w-8` | Part of the alert displayed at the top of the page. | `string` | `"Your tax form may have expired or has personal information that doesn’t match your profile. Please submit a new {documentType} form."` | -| `taxAlertNotActiveMessageW9` | `tax-alert-not-active-message-w-9` | Part of the alert displayed at the top of the page. | `string` | `"Please resubmit a new {documentType} form."` | -| `taxAndPayoutsDescription` | `tax-and-payouts-description` | Displayed at the top of the page on all set up steps and on the dashboard. | `string` | `"Submit your tax documents and add your banking information to receive your rewards."` | -| `taxDocumentSectionHeader` | `tax-document-section-header` | | `string` | `"Tax documents"` | -| `taxDocumentSectionSubHeader` | `tax-document-section-sub-header` | Displayed under the tax document section header. | `string` | `"{documentType} tax form"` | -| `thresholdPayoutText` | `threshold-payout-text` | Display participants' payout preference on the payout information card, indicating the balance at which they want to get paid. | `string` | `"Next payout occurs when balance is {thresholdBalance}"` | +| Property | Attribute | Description | Type | Default | +| ----------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `accountText` | `account-text` | Shown before the participant’s bank account information. | `string` | `"Account"` | +| `badgeTextAwaitingReview` | `badge-text-awaiting-review` | Additional text displayed next to the tax form's status badge. | `string` | `"Awaiting review. Submitted on {dateSubmitted}."` | +| `badgeTextSubmittedOn` | `badge-text-submitted-on` | Additional text displayed next to the tax form's status badge | `string` | `"Submitted on {dateSubmitted}."` | +| `badgeTextSubmittedOnW8` | `badge-text-submitted-on-w-8` | Additional text displayed next to the tax form's status badge. | `string` | `"Submitted on {dateSubmitted}. Valid for three years after submission."` | +| `bankingInformationSectionHeader` | `banking-information-section-header` | | `string` | `"Payout information"` | +| `cancelButton` | `cancel-button` | | `string` | `"Cancel"` | +| `dateColumnTitle` | `date-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Date"` | +| `demoData` | -- | | `{ states?: { status?: string; documentType: TaxDocumentType; documentTypeString: string; canEditPayoutInfo: boolean; disabled?: boolean; dateSubmitted?: string; noFormNeeded?: boolean; indirectTaxType?: string; qstNumber?: string; subRegionTaxNumber?: string; subRegion?: string; indirectTaxNumber?: string; province?: string; country?: string; notRegistered?: boolean; isBusinessEntity?: boolean; loading?: boolean; loadingError?: boolean; showNewFormDialog: boolean; hasHold: boolean; payoutStatus: PayoutStatus; veriffLoading: boolean; errors?: { general?: boolean; }; }; }` | `undefined` | +| `earningsAfterTaxColumnTitle` | `earnings-after-tax-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Earnings after tax"` | +| `earningsColumnTitle` | `earnings-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Earnings"` | +| `editPaymentInformationButton` | `edit-payment-information-button` | | `string` | `"Edit Payout Information"` | +| `generalErrorDescription` | `general-error-description` | Part of the alert displayed at the top of the page. | `string` | `"Please review your information and try again. If this problem continues, contact our {supportLink}."` | +| `generalErrorTitle` | `general-error-title` | Part of the alert displayed at the top of the page. | `string` | `"There was a problem submitting your information"` | +| `indirectTaxColumnTitle` | `indirect-tax-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Indirect tax"` | +| `indirectTaxDetails` | `indirect-tax-details` | Displayed to participants who have submitted their indirect tax information. | `string` | `"{indirectTaxType} number: {indirectTaxNumber}"` | +| `indirectTaxInfoCanada` | `indirect-tax-info-canada` | If the participant is registered for indirect tax in Canada, display the province they’re registered in. | `string` | `"Registered in {province}, {country}"` | +| `indirectTaxInfoOtherCountry` | `indirect-tax-info-other-country` | If the participant is registered for indirect tax, display the country they’re registered in. | `string` | `"Registered in {country}"` | +| `indirectTaxInfoSectionHeader` | `indirect-tax-info-section-header` | | `string` | `"Indirect tax"` | +| `indirectTaxInfoSpain` | `indirect-tax-info-spain` | If the participant is registered for indirect tax in Spain, display the region they’re registered in. | `string` | `"Registered in {country}, {subRegion}"` | +| `indirectTaxTooltipSupport` | `indirect-tax-tooltip-support` | | `string` | `"To make changes to your indirect tax information, please contact our Support team."` | +| `invalidForm` | `invalid-form` | Additional text displayed next to the tax form's status badge. | `string` | `"Make sure your information is correct and submit new form."` | +| `invoiceColumnTitle` | `invoice-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Invoice"` | +| `invoiceDescription` | `invoice-description` | | `string` | `"View and download your invoices to report your earnings and stay tax compliant."` | +| `invoiceEmptyStateHeader` | `invoice-empty-state-header` | | `string` | `"View your invoice details"` | +| `invoiceEmptyStateText` | `invoice-empty-state-text` | | `string` | `"Refer a friend to view the status of your invoices and rewards earned"` | +| `invoiceHeader` | `invoice-header` | | `string` | `"Invoices"` | +| `invoiceMoreLabel` | `invoice-more-label` | | `string` | `"Next"` | +| `invoicePrevLabel` | `invoice-prev-label` | | `string` | `"Prev"` | +| `loadingErrorAlertDescription` | `loading-error-alert-description` | Part of the alert displayed at the top of the page. | `string` | `"Please refresh the page and try again. If this problem continues, contact our {supportLink}."` | +| `loadingErrorAlertHeader` | `loading-error-alert-header` | Part of the alert displayed at the top of the page. | `string` | `"There was a problem loading your form"` | +| `newFormButton` | `new-form-button` | | `string` | `"Submit new form"` | +| `noFormNeededSubtext` | `no-form-needed-subtext` | No other statuses or badges will be displayed in the tax form section in this case. | `string` | `"Tax documents are only required if you are based in the US or joining the referral program of a US based brand."` | +| `notRegisteredForTax` | `not-registered-for-tax` | | `string` | `"Not registered for indirect tax. If you’ve previously registered with your tax authority, contact our {supportLink} to add your information to stay tax compliant."` | +| `payoutFromImpact` | `payout-from-impact` | Displayed under the payout details card. | `string` | `"Your balance may take up to 24 hours to update. Payouts will be sent from our referral program provider, impact.com."` | +| `payoutHoldAlertDescription` | `payout-hold-alert-description` | Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. | `string` | `"Please contact our {supportLink} or check your inbox for an email from our referral program provider, impact.com."` | +| `payoutHoldAlertHeader` | `payout-hold-alert-header` | Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. | `string` | `"Your payout is on hold"` | +| `payoutMissingInformationText` | `payout-missing-information-text` | Text displayed for existing publishers that do not have saved banking information. | `string` | `"Missing banking information, go to Impact.com to resolve."` | +| `qstNumber` | `qst-number` | Displayed to participants registered in Quebec, Canada. | `string` | `"QST number: {qstNumber}"` | +| `replaceTaxFormModalBodyText` | `replace-tax-form-modal-body-text` | | `string` | `"Submitting a new tax form will remove your existing form. Make sure to sign and complete your new tax form to prevent any issues with your next payout."` | +| `replaceTaxFormModalHeader` | `replace-tax-form-modal-header` | | `string` | `"Replace existing tax form"` | +| `statusBadgeText` | `status-badge-text` | | `string` | `"{badgeText, select, payoutToday {Payout Today} nextPayout {Next Payout} }"` | +| `statusTextActive` | `status-text-active` | | `string` | `"Active"` | +| `statusTextNotActive` | `status-text-not-active` | | `string` | `"Invalid Tax Form"` | +| `statusTextNotVerified` | `status-text-not-verified` | Displayed when the participant submitted their form but it is awaiting review. | `string` | `"Not Verified"` | +| `subRegionTaxNumber` | `sub-region-tax-number` | | `string` | `"Income tax number: {subRegionTaxNumber}"` | +| `supportLink` | `support-link` | | `string` | `"support team"` | +| `taxAlertHeaderNotActiveW8` | `tax-alert-header-not-active-w-8` | Part of the alert displayed at the top of the page. | `string` | `"{documentType} tax form is invalid"` | +| `taxAlertHeaderNotActiveW9` | `tax-alert-header-not-active-w-9` | Part of the alert displayed at the top of the page. | `string` | `"Your W9 tax form has personal information that doesn’t match your profile"` | +| `taxAlertNotActiveMessageW8` | `tax-alert-not-active-message-w-8` | Part of the alert displayed at the top of the page. | `string` | `"Your tax form may have expired or has personal information that doesn’t match your profile. Please submit a new {documentType} form."` | +| `taxAlertNotActiveMessageW9` | `tax-alert-not-active-message-w-9` | Part of the alert displayed at the top of the page. | `string` | `"Please resubmit a new {documentType} form."` | +| `taxAndPayoutsDescription` | `tax-and-payouts-description` | Displayed at the top of the page on all set up steps and on the dashboard. | `string` | `"Submit your tax documents and add your banking information to receive your rewards."` | +| `taxDocumentSectionHeader` | `tax-document-section-header` | | `string` | `"Tax documents"` | +| `taxDocumentSectionSubHeader` | `tax-document-section-sub-header` | Displayed under the tax document section header. | `string` | `"{documentType} tax form"` | +| `thresholdPayoutText` | `threshold-payout-text` | Display participants' payout preference on the payout information card, indicating the balance at which they want to get paid. | `string` | `"Next payout occurs when balance is {thresholdBalance}"` | +| `verificationFailedInternalDescription` | `verification-failed-internal-description` | | `string` | `"Identity verification has failed. Our team is reviewing the report and will contact you with further information. If you don't hear from us contact our {supportLink}."` | +| `verificationFailedInternalHeader` | `verification-failed-internal-header` | | `string` | `"Identity Verification Unsuccessful"` | +| `verificationRequiredButtonText` | `verification-required-button-text` | Part of the alert displayed at the top of the page when the user needs to verify their identity. | `string` | `"Start Verification"` | +| `verificationRequiredDescription` | `verification-required-description` | Part of the alert displayed at the top of the page when the user needs to verify their identity | `string` | `"Complete your verification to start receiving your cash rewards. It should only take a few minutes verify. If you run in to an issue verifying your identity contact our {supportLink}."` | +| `verificationRequiredHeader` | `verification-required-header` | Part of the alert displayed at the top of the page when the user needs to verify their identity. | `string` | `"Verify your identity"` | +| `verificationRequiredInternalDescription` | `verification-required-internal-description` | | `string` | `"Identity verification submission has been received. Our system is currently performing additional checks and analyzing the results. You will be updated shortly. If you don't hear from us contact our {supportLink}."` | +| `verificationRequiredInternalHeader` | `verification-required-internal-header` | | `string` | `"Identity Verification in Progress"` | +| `verificationReviewInternalDescription` | `verification-review-internal-description` | | `string` | `"Identity verification requires further review due to a potential error. Our team is reviewing the information and will update you shortly. If you don't hear from us contact our {supportLink}."` | +| `verificationReviewInternalHeader` | `verification-review-internal-header` | | `string` | `"Identity Verification Under Review"` | ## Dependencies diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/sqm-tax-and-cash-dashboard-view.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/sqm-tax-and-cash-dashboard-view.tsx index 25cfaeab1d..07ac5d79f8 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/sqm-tax-and-cash-dashboard-view.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/sqm-tax-and-cash-dashboard-view.tsx @@ -3,6 +3,7 @@ import { intl } from "../../../global/global"; import { createStyleSheet } from "../../../styling/JSS"; import { TaxDocumentType } from "../sqm-tax-and-cash/data"; import { P } from "../../../global/mixins"; +import { PayoutStatus } from "../sqm-payout-status-alert/usePayoutStatus"; export interface TaxAndCashDashboardProps { states: { @@ -25,6 +26,9 @@ export interface TaxAndCashDashboardProps { loading?: boolean; loadingError?: boolean; showNewFormDialog: boolean; + hasHold: boolean; + payoutStatus: PayoutStatus; + veriffLoading: boolean; errors?: { general?: boolean; }; @@ -34,6 +38,7 @@ export interface TaxAndCashDashboardProps { }; callbacks: { onClick: (props: any) => void; + onVerifyClick: () => void; onEditPayoutInfo: () => void; onNewFormCancel: () => void; onNewFormClick: () => void; @@ -83,7 +88,19 @@ export interface TaxAndCashDashboardProps { invoiceEmptyStateText: string; replaceTaxFormModalHeader: string; replaceTaxFormModalBodyText: string; + payoutHoldAlertHeader: string; + payoutHoldAlertDescription: string; + verificationRequiredHeader: string; + verificationRequiredDescription: string; + verificationRequiredButtonText: string; + verificationRequiredInternalHeader: string; + verificationRequiredInternalDescription: string; + verificationReviewInternalHeader: string; + verificationReviewInternalDescription: string; + verificationFailedInternalHeader: string; + verificationFailedInternalDescription: string; cancelButton: string; + supportLink: string; error: { generalTitle: string; generalDescription: string; @@ -94,11 +111,46 @@ export interface TaxAndCashDashboardProps { } const style = { - WarningAlertContainer: { + ErrorAlertContainer: { "&::part(base)": { backgroundColor: "var(--sl-color-red-100)", borderTop: "none", }, + "& sl-icon::part(base)": { + color: "var(--sl-color-danger-500)", + }, + }, + WarningAlertContainer: { + "&::part(base)": { + backgroundColor: "var(--sl-color-yellow-100)", + borderTop: "none", + maxWidth: "600px", + }, + "& sl-icon::part(base)": { + color: "var(--sl-color-warning-500)", + }, + }, + WarningHoldAlertContainer: { + marginLeft: "-20px", + "&::part(base)": { + maxWidth: "850px", + border: "none", + backgroundColor: "transparent", + }, + "& sl-icon::part(base)": { + color: "var(--sl-color-warning-500)", + }, + }, + ErrorHoldAlertContainer: { + marginLeft: "-20px", + "&::part(base)": { + maxWidth: "850px", + border: "none", + backgroundColor: "transparent", + }, + "& sl-icon::part(base)": { + color: "var(--sl-color-danger-500)", + }, }, ExpiringSoonAlertContainer: { "&::part(base)": { @@ -219,6 +271,7 @@ const style = { PageDescriptionText: { color: "var(--sl-color-neutral-500)", fontSize: "var(--sl-font-size-medium)", + marginTop: "0", }, Dialog: { "&::part(panel)": { @@ -256,6 +309,115 @@ const styleString = sheet.toString(); export const TaxAndCashDashboardView = (props: TaxAndCashDashboardProps) => { const { states, text, callbacks, slots } = props; + function getAlert(status: PayoutStatus) { + switch (status) { + case "VERIFICATION:REQUIRED": + return { + header: text.verificationRequiredHeader, + description: intl.formatMessage( + { + id: "verificationRequiredDescription", + defaultMessage: text.verificationRequiredDescription, + }, + { + supportLink: ( + + {text.supportLink} + + ), + } + ), + buttonText: text.verificationRequiredButtonText, + alertType: "warning", + icon: "exclamation-triangle", + class: sheet.classes.WarningHoldAlertContainer, + }; + case "VERIFICATION:INTERNAL": + return { + header: text.verificationRequiredInternalHeader, + description: intl.formatMessage( + { + id: "verificationRequiredInternalDescription", + defaultMessage: text.verificationRequiredInternalDescription, + }, + { + supportLink: ( + + {text.supportLink} + + ), + } + ), + alertType: "warning", + icon: "exclamation-triangle", + class: sheet.classes.WarningHoldAlertContainer, + }; + case "VERIFICATION:REVIEW": + return { + header: text.verificationReviewInternalHeader, + description: intl.formatMessage( + { + id: "verificationReviewInternalDescription", + defaultMessage: text.verificationReviewInternalDescription, + }, + { + supportLink: ( + + {text.supportLink} + + ), + } + ), + alertType: "warning", + icon: "exclamation-triangle", + class: sheet.classes.WarningHoldAlertContainer, + }; + case "VERIFICATION:FAILED": + return { + header: text.verificationFailedInternalHeader, + description: intl.formatMessage( + { + id: "verificationFailedInternalDescription", + defaultMessage: text.verificationFailedInternalDescription, + }, + { + supportLink: ( + + {text.supportLink} + + ), + } + ), + alertType: "critical", + icon: "exclamation-octagon", + class: sheet.classes.ErrorHoldAlertContainer, + }; + case "HOLD": + return { + header: text.payoutHoldAlertHeader, + description: intl.formatMessage( + { + id: "payoutHoldAlertDescription", + defaultMessage: text.payoutHoldAlertDescription, + }, + { + supportLink: ( + + {text.supportLink} + + ), + } + ), + buttonText: null, + alertType: "warning", + icon: "exclamation-triangle", + class: sheet.classes.WarningHoldAlertContainer, + }; + default: + return; + } + } + const statusMap = { NOT_VERIFIED: (
@@ -312,7 +474,7 @@ export const TaxAndCashDashboardView = (props: TaxAndCashDashboardProps) => { exportparts="base: alert-base, icon:alert-icon" type="danger" open - class={sheet.classes.WarningAlertContainer} + class={sheet.classes.ErrorAlertContainer} > @@ -392,12 +554,27 @@ export const TaxAndCashDashboardView = (props: TaxAndCashDashboardProps) => { exportparts="base: alert-base, icon:alert-icon" type="danger" open - class={sheet.classes.WarningAlertContainer} + class={sheet.classes.ErrorAlertContainer} > {text.error.loadingErrorAlertHeader}
- {text.error.loadingErrorAlertDescription} + {intl.formatMessage( + { + id: "loadingErrorAlertDescription", + defaultMessage: text.error.loadingErrorAlertDescription, + }, + { + supportLink: ( + + {text.supportLink} + + ), + } + )}
)} @@ -406,12 +583,54 @@ export const TaxAndCashDashboardView = (props: TaxAndCashDashboardProps) => { exportparts="base: alert-base, icon:alert-icon" type="danger" open - class={sheet.classes.WarningAlertContainer} + class={sheet.classes.ErrorAlertContainer} > {text.error.generalTitle}
- {text.error.generalDescription} + {intl.formatMessage( + { + id: "generalDescription", + defaultMessage: text.error.generalDescription, + }, + { + supportLink: ( + + {text.supportLink} + + ), + } + )} + + )} + {getAlert(states.payoutStatus) && ( + + + {getAlert(states.payoutStatus).header} +

+ {getAlert(states.payoutStatus).description} +

+ {getAlert(states.payoutStatus).buttonText && ( + callbacks.onVerifyClick()} + > + {getAlert(states.payoutStatus).buttonText} + + )}
)} { {states.status === "INACTIVE" && alertMap[states.status]}
-

{text.bankingInformationSectionHeader}

+

+ {text.bankingInformationSectionHeader} +

{text.taxAndPayoutsDescription}

@@ -544,7 +765,22 @@ export const TaxAndCashDashboardView = (props: TaxAndCashDashboardProps) => { {states.notRegistered ? ( - {text.notRegisteredForTax} + {intl.formatMessage( + { + id: "notRegisteredForTax", + defaultMessage: text.notRegisteredForTax, + }, + { + supportLink: ( + + {text.supportLink} + + ), + } + )} ) : ( getIndirectTaxRegisteredIn() diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/sqm-tax-and-cash-dashboard.feature b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/sqm-tax-and-cash-dashboard.feature index d532c12cf1..63b7974f6f 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/sqm-tax-and-cash-dashboard.feature +++ b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/sqm-tax-and-cash-dashboard.feature @@ -56,13 +56,13 @@ Feature: Tax And Cash Dashboard Examples: | isRegistered | country | region | registeredDetails | indirectTaxType | indirectTaxNumber | - | true | Australia | n/a | Registered in Australia. | GST | 123456 | - | true | Canada | Ontario | Registered in Ontario, Canada. | GST | 345213 | - | true | Canada | British Columbia | Registered in British Columbia, Canada. | HST | 345213 | - | true | Canada | Quebec | Registered in Quebec, Canada. | GST, QST | 345213, 12312 | - | true | United Kingdom | n/a | Registered in United Kingdom. | VAT | 321413 | - | true | Spain | Spain Proper | Registered in Spain, Spain Proper. | VAT, Income tax number | 345213, 12345 | - | true | Spain | Canary Islands | Registered in Spain, Canary Islands. | VAT, Income tax number | 345213, 12345 | + | true | Australia | n/a | Registered in Australia. | GST | 123456 | + | true | Canada | Ontario | Registered in Ontario, Canada. | GST | 345213 | + | true | Canada | British Columbia | Registered in British Columbia, Canada. | HST | 345213 | + | true | Canada | Quebec | Registered in Quebec, Canada. | GST, QST | 345213, 12312 | + | true | United Kingdom | n/a | Registered in United Kingdom. | VAT | 321413 | + | true | Spain | Spain Proper | Registered in Spain, Spain Proper. | VAT, Income tax number | 345213, 12345 | + | true | Spain | Canary Islands | Registered in Spain, Canary Islands. | VAT, Income tax number | 345213, 12345 | | false | United States | n/a | Not registered. Only participants representing a company in countries that enforce indirect tax (e.g. GST, HST, VAT) must add their indirect tax information. | | N/A | | false | United States | n/a | Not registered. Only participants representing a company in countries that enforce indirect tax (e.g. GST, HST, VAT) must add their indirect tax information. | | N/A | | false | Papua New Guinea | n/a | Not registered. Only participants representing a company in countries that enforce indirect tax (e.g. GST, HST, VAT) must add their indirect tax information. | | N/A | @@ -90,6 +90,60 @@ Feature: Tax And Cash Dashboard | NOT_ACTIVE | W8-BEN | W-8 tax form is invalid. | Your tax form may have expired or has personal information that doesn’t match your profile. Please submit a new W-8 form. | | NOT_ACTIVE | W8-BEN-E | W-8 tax form is invalid. | Your tax form may have expired or has personal information that doesn’t match your profile. Please submit a new W-8 form. | + @minutia @ui + Scenario: A Warning Alert is displayed if the user has a payout on hold + Given the user has a hold reason + Then a warning alert indicating appears with description text: + """ + Please contact Support or check your inbox for an email from our referral program provider, impact.com. + """ + + @minutia @ui + Scenario: A Warning Alert is displayed if the user must verify their identity + Given the user has "IDV_CHECK_REQUIRED" included in their hold reasons + Then a warning alert indicating appears with description text: + """ + Complete your verification to start receiving your cash rewards. It should only take a few minutes verify. + """ + And a Start Verification button is present + When they press the button + Then a modal will appear with steps to verify their identity + + @minutia + Scenario Outline: Alert displays the current state of the verification process + Given a user has included in their holdReasons + And they complete the payout and tax form flow + Then a banner appears + And the alert has heading + And the alert has description + + Examples: + | holdReason | color | heading | description | + | IDV_CHECK_REQUIRED_INTERNAL | yellow | Verification In Progress | Verification submission has been received. Our system is currently performing additional checks and analyzing the results. You will be updated shortly. | + | IDV_CHECK_REVIEW_INTERNAL | yellow | Verification Under Review | Verification requires further review due to a potential error. Our team is reviewing the information and will update you shortly. | + | IDV_CHECK_FAILED_INTERNAL | red | Identity verification unsuccessful | Identity verification has failed. Our team is reviewing the report and will contact you with further information. | + + @motivating + Scenario: User has hold reasons + Given they have impactConnection as one of the following + | impactConnection | + | { connected: true, publisher: { payoutsAccount: { hold: true } } } | + And hold reasons don't include any of the following + | holdReason | + | IDV_CHECK_REQUIRED | + | IDV_CHECK_REQUIRED_INTERNAL | + | IDV_CHECK_REVIEW_INTERNAL | + | IDV_CHECK_FAILED_INTERNAL | + And they have completed the payout and tax form flow + Then a yellow warning banner appears with a header: + """ + Your payouts and account are on hold + """ + And description + """ + Please check your inbox for an email from our referral provider, impact.com. It contains details on how to resolve this issue. If you need further assistance, feel free to reach out to {support email}. + """ + @minutia @ui Scenario: Invoices table is available for participants regsistered for Indirect Tax Given a participant is registered for Indirect Tax diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/sqm-tax-and-cash-dashboard.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/sqm-tax-and-cash-dashboard.tsx index e78305afca..8b736c83f3 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/sqm-tax-and-cash-dashboard.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/sqm-tax-and-cash-dashboard.tsx @@ -143,7 +143,7 @@ export class TaxAndCashDashboard { * @uiWidget textArea */ @Prop() indirectTaxTooltipSupport: string = - "To make changes to your indirect tax information, please contact Support."; + "To make changes to your indirect tax information, please contact our Support team."; /** * Displayed to participants who have submitted their indirect tax information. * @@ -165,7 +165,7 @@ export class TaxAndCashDashboard { * @uiWidget textArea */ @Prop() notRegisteredForTax: string = - "Not registered for indirect tax. If you’ve previously registered with your tax authority, contact Support to add your information to stay tax compliant."; + "Not registered for indirect tax. If you’ve previously registered with your tax authority, contact our {supportLink} to add your information to stay tax compliant."; /** * Displayed to participants registered in Quebec, Canada. * @uiName QST indirect tax details @@ -200,22 +200,68 @@ export class TaxAndCashDashboard { * @uiName Payout error message title * @uiWidget textArea */ - @Prop() errorTitleText: string = "Your payout is on hold "; + @Prop() payoutHoldAlertHeader: string = "Your payout is on hold"; /** * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. * @uiName Payout error message description * @uiWidget textArea */ - @Prop() - errorDescriptionText: string = - "If you’ve recently added your payout information, please wait while we verify your information. If it’s still on hold after a few days, please contact Support or check your inbox for an email from our referral program provider, impact.com."; + @Prop() payoutHoldAlertDescription: string = + "Please contact our {supportLink} or check your inbox for an email from our referral program provider, impact.com."; /** * Text displayed for existing publishers that do not have saved banking information. * @uiName Payout missing information subtext */ @Prop() payoutMissingInformationText: string = "Missing banking information, go to Impact.com to resolve."; - + /** + * Part of the alert displayed at the top of the page when the user needs to verify their identity. + * @uiName Verification required alert message title + * @uiWidget textArea + */ + @Prop() verificationRequiredHeader: string = "Verify your identity"; + /** + * Part of the alert displayed at the top of the page when the user needs to verify their identity + * @uiName Verification required alert message description + * @uiWidget textArea + */ + @Prop() verificationRequiredDescription: string = + "Complete your verification to start receiving your cash rewards. It should only take a few minutes verify. If you run in to an issue verifying your identity contact our {supportLink}."; + /** + * @uiName Verification required internal alert header + */ + @Prop() verificationRequiredInternalHeader: string = + "Identity Verification in Progress"; + /** + * @uiName Verification required internal alert description + */ + @Prop() verificationRequiredInternalDescription: string = + "Identity verification submission has been received. Our system is currently performing additional checks and analyzing the results. You will be updated shortly. If you don't hear from us contact our {supportLink}."; + /** + * @uiName Verification review internal alert header + */ + @Prop() verificationReviewInternalHeader: string = + "Identity Verification Under Review"; + /** + * @uiName Verification review internal alert description + */ + @Prop() verificationReviewInternalDescription: string = + "Identity verification requires further review due to a potential error. Our team is reviewing the information and will update you shortly. If you don't hear from us contact our {supportLink}."; + /** + * @uiName Verification failed internal alert header + */ + @Prop() verificationFailedInternalHeader: string = + "Identity Verification Unsuccessful"; + /** + * @uiName Verification failed internal alert description + */ + @Prop() verificationFailedInternalDescription: string = + "Identity verification has failed. Our team is reviewing the report and will contact you with further information. If you don't hear from us contact our {supportLink}."; + /** + * Part of the alert displayed at the top of the page when the user needs to verify their identity. + * @uiName Verification required alert button text + */ + @Prop() verificationRequiredButtonText: string = "Start Verification"; /** * Part of the alert displayed at the top of the page. * @uiName Form submission error message title @@ -229,8 +275,7 @@ export class TaxAndCashDashboard { * @uiWidget textArea */ @Prop() generalErrorDescription: string = - "Please review your information and try again. If this problem continues, contact Support."; - + "Please review your information and try again. If this problem continues, contact our {supportLink}."; /** * Displayed under the payout details card. * @uiName Payout from impact text @@ -251,7 +296,7 @@ export class TaxAndCashDashboard { * @uiWidget textArea */ @Prop() loadingErrorAlertDescription: string = - "Please refresh the page and try again. If this problem continues, contact Support."; + "Please refresh the page and try again. If this problem continues, contact our {supportLink}."; /** * Part of the Invoice table displayed at the bottom of the page. @@ -314,6 +359,10 @@ export class TaxAndCashDashboard { */ @Prop() replaceTaxFormModalBodyText: string = "Submitting a new tax form will remove your existing form. Make sure to sign and complete your new tax form to prevent any issues with your next payout."; + /** + * @uiName Support link text + */ + @Prop() supportLink: string = "support team"; /** * @uiName Cancel button label */ @@ -384,12 +433,17 @@ function useDemoTaxAndCashDashboard( disabled: false, loading: false, showNewFormDialog: false, + hasHold: false, + showVerifyIdentity: false, + payoutStatus: "DONE", + veriffLoading: false, }, callbacks: { onClick: () => console.debug("check step"), onEditPayoutInfo: () => console.debug("payout info"), onNewFormCancel: () => console.log("hide"), onNewFormClick: () => console.log("show"), + onVerifyClick: () => console.log("verify"), }, text: props.getTextProps(), }, diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/useTaxAndCashDashboard.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/useTaxAndCashDashboard.tsx index 5f4770141e..13f5e5afdd 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/useTaxAndCashDashboard.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash-dashboard/useTaxAndCashDashboard.tsx @@ -21,6 +21,8 @@ import { import { taxTypeToName } from "../utils"; import { TaxAndCashDashboard } from "./sqm-tax-and-cash-dashboard"; import { TaxAndCashDashboardProps } from "./sqm-tax-and-cash-dashboard-view"; +import { getStatus } from "../sqm-payout-status-alert/usePayoutStatus"; +import { useVeriffApp, VERIFF_COMPLETE_EVENT_KEY } from "../useVeriffApp"; function getCountryName(countryCode: string, locale: string) { if (!countryCode) return undefined; @@ -68,6 +70,11 @@ export const useTaxAndCashDashboard = ( const setStep = useSetParent(TAX_CONTEXT_NAMESPACE); const setContext = useSetParent(TAX_FORM_CONTEXT_NAMESPACE); const [showDialog, setShowDialog] = useState(false); + const { + render, + loading: veriffLoading, + errors: veriffErrors, + } = useVeriffApp(); const locale = useLocale(); @@ -80,6 +87,7 @@ export const useTaxAndCashDashboard = ( data, loading, errors: userError, + refetch, } = useParentQueryValue(USER_QUERY_NAMESPACE); const publisher = data?.user?.impactConnection?.publisher; @@ -115,6 +123,16 @@ export const useTaxAndCashDashboard = ( (p) => p.regionCode === publisher?.taxInformation?.indirectTaxRegion )?.displayName; + const payoutStatus = data ? getStatus(data) : null; + + useEffect(() => { + const cb = () => refetch(); + window.addEventListener(VERIFF_COMPLETE_EVENT_KEY, cb); + return () => { + window.removeEventListener(VERIFF_COMPLETE_EVENT_KEY, cb); + }; + }, []); + return { states: { dateSubmitted, @@ -139,9 +157,13 @@ export const useTaxAndCashDashboard = ( loading, loadingError: !!userError?.message, showNewFormDialog: showDialog, + hasHold: !!publisher?.payoutsAccount?.hold, + payoutStatus, + veriffLoading, }, callbacks: { onClick: () => setShowDialog(true), + onVerifyClick: () => render(), onEditPayoutInfo, onNewFormCancel: () => setShowDialog(false), onNewFormClick, diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/data.ts b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/data.ts index 9a567cac48..6a87ac75e0 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/data.ts +++ b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/data.ts @@ -46,11 +46,17 @@ export const GET_USER = gql` query getUserTaxInfo { user: viewer { ... on User { + id firstName lastName email countryCode customFields + managedIdentity { + uid + email + emailVerified + } impactConnection { connected user { @@ -94,6 +100,7 @@ export const GET_USER = gql` } payoutsAccount { hold + holdReasons balance } } @@ -141,6 +148,7 @@ export type ImpactPublisher = { }; payoutsAccount: { hold: boolean; + holdReasons: string[]; balance: string; }; }; @@ -153,6 +161,11 @@ export type UserQuery = { customFields?: { [key: string]: any; }; + managedIdentity?: { + uid: string; + email: string; + emailVerified: boolean; + } | null; impactConnection: null | { connected: boolean; user: { diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/extractProps.ts b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/extractProps.ts index 678cb899da..6ab576ae39 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/extractProps.ts +++ b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/extractProps.ts @@ -4,7 +4,10 @@ * @param prefix Prefix denoting which group the text prop belongs to * @returns A new object with all keys with the prefix provided. The prefix is removed from each key. */ -export function extractProps(props: object, prefix: string) { +export function extractProps( + props: X, + prefix: T +): PickPrefix { const keys = Object.keys(props).filter((k) => k.startsWith(prefix)); const formattedProps = keys.reduce((prev, k) => { @@ -15,5 +18,11 @@ export function extractProps(props: object, prefix: string) { }; }, {}); - return formattedProps; + return formattedProps as PickPrefix; } + +type PickPrefix = { + [K in keyof Object as K extends `${Prefix}${infer Suffix}` + ? `${Suffix}` + : never]: Object[K]; +}; diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/readme.md b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/readme.md index dab1099814..8b9bca6c18 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/readme.md +++ b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/readme.md @@ -5,158 +5,159 @@ ## Properties -| Property | Attribute | Description | Type | Default | -| ----------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `backButton` | `back-button` | | `string` | `"Back"` | -| `cancelButton` | `cancel-button` | | `string` | `"Cancel"` | -| `continueButton` | `continue-button` | | `string` | `"Continue"` | -| `dashboard_accountText` | `dashboard_account-text` | Shown before the participant’s bank account information. | `string` | `"Account"` | -| `dashboard_badgeTextAwaitingReview` | `dashboard_badge-text-awaiting-review` | Additional text displayed next to the tax form's status badge. | `string` | `"Awaiting review. Submitted on {dateSubmitted}."` | -| `dashboard_badgeTextSubmittedOn` | `dashboard_badge-text-submitted-on` | Additional text displayed next to the tax form's status badge | `string` | `"Submitted on {dateSubmitted}."` | -| `dashboard_badgeTextSubmittedOnW8` | `dashboard_badge-text-submitted-on-w-8` | Additional text displayed next to the tax form's status badge. | `string` | `"Submitted on {dateSubmitted}. Valid for three years after submission."` | -| `dashboard_bankingInformationSectionHeader` | `dashboard_banking-information-section-header` | | `string` | `"Payout Information"` | -| `dashboard_dateColumnTitle` | `dashboard_date-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Date"` | -| `dashboard_earningsAfterTaxColumnTitle` | `dashboard_earnings-after-tax-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Earnings after tax"` | -| `dashboard_earningsColumnTitle` | `dashboard_earnings-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Earnings"` | -| `dashboard_editPaymentInformationButton` | `dashboard_edit-payment-information-button` | | `string` | `"Edit Payout Information"` | -| `dashboard_errorDescriptionText` | `dashboard_error-description-text` | Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. | `string` | `"If you’ve recently added your payout information, please wait while we verify your information. If it’s still on hold after a few days, please contact Support or check your inbox for an email from our referral program provider, impact.com."` | -| `dashboard_errorTitleText` | `dashboard_error-title-text` | Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. | `string` | `"Your payout is on hold "` | -| `dashboard_indirectTaxColumnTitle` | `dashboard_indirect-tax-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Indirect tax"` | -| `dashboard_indirectTaxDetails` | `dashboard_indirect-tax-details` | Displayed to participants who have submitted their indirect tax information. | `string` | `"{indirectTaxType} number: {indirectTaxNumber}"` | -| `dashboard_indirectTaxInfoCanada` | `dashboard_indirect-tax-info-canada` | If the participant is registered for indirect tax in Canada, display the province they’re registered in. | `string` | `"Registered in {province}, {country}"` | -| `dashboard_indirectTaxInfoOtherCountry` | `dashboard_indirect-tax-info-other-country` | If the participant is registered for indirect tax, display the country they’re registered in. | `string` | `"Registered in {country}"` | -| `dashboard_indirectTaxInfoSectionHeader` | `dashboard_indirect-tax-info-section-header` | | `string` | `"Indirect tax"` | -| `dashboard_indirectTaxInfoSpain` | `dashboard_indirect-tax-info-spain` | If the participant is registered for indirect tax in Spain, display the region they’re registered in. | `string` | `"Registered in {country}, {subRegion}"` | -| `dashboard_indirectTaxTooltipSupport` | `dashboard_indirect-tax-tooltip-support` | | `string` | `"To make changes to your indirect tax information, please contact Support."` | -| `dashboard_invalidForm` | `dashboard_invalid-form` | Additional text displayed next to the tax form's status badge. | `string` | `"Make sure your information is correct and submit new form."` | -| `dashboard_invoiceColumnTitle` | `dashboard_invoice-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Invoice"` | -| `dashboard_invoiceDescription` | `dashboard_invoice-description` | | `string` | `"View and download your invoices to report your earnings and stay tax compliant."` | -| `dashboard_invoiceEmptyStateHeader` | `dashboard_invoice-empty-state-header` | | `string` | `"View your invoice details"` | -| `dashboard_invoiceEmptyStateText` | `dashboard_invoice-empty-state-text` | | `string` | `"Refer a friend to view the status of your invoices and rewards earned"` | -| `dashboard_invoiceHeader` | `dashboard_invoice-header` | | `string` | `"Invoices"` | -| `dashboard_invoiceMoreLabel` | `dashboard_invoice-more-label` | | `string` | `"Next"` | -| `dashboard_invoicePrevLabel` | `dashboard_invoice-prev-label` | | `string` | `"Prev"` | -| `dashboard_newFormButton` | `dashboard_new-form-button` | | `string` | `"Submit new form"` | -| `dashboard_noFormNeededSubtext` | `dashboard_no-form-needed-subtext` | No other statuses or badges will be displayed in the tax form section in this case. | `string` | `"Tax documents are only required if you are based in the US or joining the referral program of a US based brand."` | -| `dashboard_notRegisteredForTax` | `dashboard_not-registered-for-tax` | | `string` | `"Not registered for indirect tax. If you’ve previously registered with your tax authority, contact Support to add your information to stay tax compliant."` | -| `dashboard_payoutFromImpact` | `dashboard_payout-from-impact` | Displayed under the payout details card. | `string` | `"Your balance may take up to 24 hours to update. Payouts will be sent from our referral program provider, impact.com."` | -| `dashboard_payoutMissingInformationText` | `dashboard_payout-missing-information-text` | Text displayed for existing publishers that do not have saved banking information. | `string` | `"Missing banking information, go to Impact.com to resolve."` | -| `dashboard_qstNumber` | `dashboard_qst-number` | Displayed to participants registered in Quebec, Canada. | `string` | `"QST number: {qstNumber}"` | -| `dashboard_replaceTaxFormModalBodyText` | `dashboard_replace-tax-form-modal-body-text` | | `string` | `"Submitting a new tax form will remove your existing form. Make sure to sign and complete your new tax form to prevent any issues with your next payout."` | -| `dashboard_replaceTaxFormModalHeader` | `dashboard_replace-tax-form-modal-header` | | `string` | `"Replace existing tax form"` | -| `dashboard_statusBadgeText` | `dashboard_status-badge-text` | | `string` | `"{badgeText, select, payoutToday {Payout Today} nextPayout {Next Payout} }"` | -| `dashboard_statusTextActive` | `dashboard_status-text-active` | | `string` | `"Active"` | -| `dashboard_statusTextNotActive` | `dashboard_status-text-not-active` | | `string` | `"Invalid Tax Form"` | -| `dashboard_statusTextNotVerified` | `dashboard_status-text-not-verified` | Displayed when the participant submitted their form but it is awaiting review. | `string` | `"Not Verified"` | -| `dashboard_subRegionTaxNumber` | `dashboard_sub-region-tax-number` | | `string` | `"Income tax number: {subRegionTaxNumber}"` | -| `dashboard_taxAlertHeaderNotActiveW8` | `dashboard_tax-alert-header-not-active-w-8` | Part of the alert displayed at the top of the page. | `string` | `"{documentType} tax form is invalid"` | -| `dashboard_taxAlertHeaderNotActiveW9` | `dashboard_tax-alert-header-not-active-w-9` | Part of the alert displayed at the top of the page. | `string` | `"Your W9 tax form has personal information that doesn’t match your profile"` | -| `dashboard_taxAlertNotActiveMessageW8` | `dashboard_tax-alert-not-active-message-w-8` | Part of the alert displayed at the top of the page. | `string` | `"Your tax form may have expired or has personal information that doesn’t match your profile. Please submit a new {documentType} form."` | -| `dashboard_taxAlertNotActiveMessageW9` | `dashboard_tax-alert-not-active-message-w-9` | Part of the alert displayed at the top of the page. | `string` | `"Please resubmit a new {documentType} form."` | -| `dashboard_taxDocumentSectionHeader` | `dashboard_tax-document-section-header` | | `string` | `"Tax documents"` | -| `dashboard_taxDocumentSectionSubHeader` | `dashboard_tax-document-section-sub-header` | Displayed under the tax document section header. | `string` | `"{documentType} tax form"` | -| `dashboard_thresholdPayoutText` | `dashboard_threshold-payout-text` | Display participants' payout preference on the payout information card, indicating the balance at which they want to get paid. | `string` | `"Next payout occurs when balance is {thresholdBalance}"` | -| `demoData` | -- | | `{ loading?: boolean; setStep?: (value: string) => void; step?: string; context?: TaxContext; namespace?: string; }` | `undefined` | -| `fieldInvalidError` | `field-invalid-error` | Displayed under a field when it has an invalid entry. | `string` | `"{fieldName} is invalid"` | -| `fieldRequiredError` | `field-required-error` | Displayed under a field that is missing required information. | `string` | `"{fieldName} is required"` | -| `formStep` | `form-step` | | `string` | `"Step {step} of {count}"` | -| `generalErrorDescription` | `general-error-description` | Part of the alert displayed at the top of the page. | `string` | `"Please review your information and try again. If this problem continues, contact Support."` | -| `generalErrorTitle` | `general-error-title` | Part of the alert displayed at the top of the page. | `string` | `"There was a problem submitting your information"` | -| `invalidCharacterError` | `invalid-character-error` | Displayed under a field that includes invalid characters (non-ASCII). | `string` | `"{fieldName} includes characters that aren't supported."` | -| `isPartnerAlertDescription` | `is-partner-alert-description` | Part of the alert displayed at the top of the page if the participant is already a registered partner on impact.com. | `string` | `"If you don’t recognize this referral program provider or believe this is a mistake, please contact Support or sign up for this referral program with a different email."` | -| `isPartnerAlertHeader` | `is-partner-alert-header` | Part of the alert displayed at the top of the page if the participant is already a registered partner on impact.com. | `string` | `"An account with this email already exists with our referral program provider, impact.com"` | -| `loadingErrorAlertDescription` | `loading-error-alert-description` | Part of the alert displayed at the top of the page. | `string` | `"Please refresh the page and try again. If this problem continues, contact Support."` | -| `loadingErrorAlertHeader` | `loading-error-alert-header` | Part of the alert displayed at the top of the page. | `string` | `"There was a problem loading your form"` | -| `searchForCountryText` | `search-for-country-text` | Placeholder text displayed in the country search dropdown | `string` | `"Search for country.."` | -| `step1_address` | `step-1_address` | | `string` | `"Address"` | -| `step1_allowBankingCollection` | `step-1_allow-banking-collection` | Edit the property called terms and conditions text to change what's displayed for {termsAndConditionsLink}. | `string` | `"I have read the {termsAndConditionsLink} and allow impact.com to collect my tax and banking information"` | -| `step1_city` | `step-1_city` | | `string` | `"City"` | -| `step1_country` | `step-1_country` | | `string` | `"Country"` | -| `step1_currency` | `step-1_currency` | | `string` | `"Currency"` | -| `step1_currencyHelpText` | `step-1_currency-help-text` | | `string` | `"Choose your preferred payout currency"` | -| `step1_email` | `step-1_email` | | `string` | `"Email"` | -| `step1_firstName` | `step-1_first-name` | | `string` | `"First name"` | -| `step1_lastName` | `step-1_last-name` | | `string` | `"Last name"` | -| `step1_personalInformation` | `step-1_personal-information` | | `string` | `"Personal Information"` | -| `step1_phoneNumber` | `step-1_phone-number` | | `string` | `"Phone number"` | -| `step1_postalCode` | `step-1_postal-code` | | `string` | `"Postal code"` | -| `step1_province` | `step-1_province` | | `string` | `"Province"` | -| `step1_region` | `step-1_region` | | `string` | `"Region"` | -| `step1_searchForCurrencyText` | `step-1_search-for-currency-text` | Placeholder text displayed in the currency search dropdown | `string` | `"Search for currency.."` | -| `step1_state` | `step-1_state` | | `string` | `"State"` | -| `step1_termsAndConditionsLabel` | `step-1_terms-and-conditions-label` | The link text that appears in the terms and conditions checkbox | `string` | `"terms and conditions"` | -| `step1_termsAndConditionsLink` | `step-1_terms-and-conditions-link` | The link that appears in the terms and conditions checkbox | `string` | `"/payout-terms-and-conditions"` | -| `step2_cannotChangeInfoAlert` | `step-2_cannot-change-info-alert` | Communicate that after this step, only Support can change personal and indirect tax information. | `string` | `"Changes to your personal and indirect tax information can only be made through our Support team after you complete this step. Make sure these are correct before continuing."` | -| `step2_indirectTax` | `step-2_indirect-tax` | | `string` | `"Indirect Tax"` | -| `step2_indirectTaxDescription` | `step-2_indirect-tax-description` | Displayed under the title of this step. | `string` | `"Indirect taxes (e.g. VAT, HST, GST) are transaction based taxes often applied to goods and services. Service providers are typically required to register with their tax authority and collect these taxes on behalf governments."` | -| `step2_indirectTaxDetails` | `step-2_indirect-tax-details` | Displayed with indirect tax registration options. | `string` | `"Indirect tax details"` | -| `step2_indirectTaxNumber` | `step-2_indirect-tax-number` | | `string` | `"{taxType, select, GST {GST number} HST {HST number} VAT {VAT number} CT {CT number} SST {SST number} GENERAL {Indirect tax number}}"` | -| `step2_indirectTaxNumberError` | `step-2_indirect-tax-number-error` | | `string` | `"{taxType, select, GST {GST number} HST {HST number} VAT {VAT number} CT {CT number} SST {SST number} GENERAL {Indirect tax number}} is required"` | -| `step2_isRegisteredQST` | `step-2_is-registered-q-s-t` | Displayed to participants registered for indirect tax in Quebec, Canada. | `string` | `"I am registered for QST Tax"` | -| `step2_isRegisteredSubRegionIncomeTax` | `step-2_is-registered-sub-region-income-tax` | Displayed to participants registered for indirect tax in Spain. | `string` | `"I am an individual registered for Income Tax purposes in Spain, and withholding tax will apply to any payments made to me."` | -| `step2_notRegistered` | `step-2_not-registered` | | `string` | `"Not registered for indirect tax"` | -| `step2_notRegisteredSubtext` | `step-2_not-registered-subtext` | Participants based in the US are considered not registered. | `string` | `"If you’ve never set up indirect tax with your tax authority, then you’re likely not considered registered."` | -| `step2_otherRegion` | `step-2_other-region` | | `string` | `"Registered for indirect tax"` | -| `step2_otherRegionSubtext` | `step-2_other-region-subtext` | Selecting this option will display fields to enter indirect tax details. | `string` | `"If you’ve registered with your tax authority, add your information to stay tax compliant."` | -| `step2_province` | `step-2_province` | | `string` | `"Province"` | -| `step2_qstNumber` | `step-2_qst-number` | Displayed to participants registered for QST. | `string` | `"QST number"` | -| `step2_selectedRegion` | `step-2_selected-region` | | `string` | `"Country / region of indirect tax"` | -| `step2_subRegion` | `step-2_sub-region` | Displayed to participants registered in Spain. | `string` | `"Sub-region"` | -| `step2_subRegionTaxNumberLabel` | `step-2_sub-region-tax-number-label` | | `string` | `"Income Tax Number"` | -| `step3_businessEntity` | `step-3_business-entity` | An option for the participant type field. Used to determine which W-8 form is required. | `string` | `"I represent a business"` | -| `step3_docusignError` | `step-3_docusign-error` | This appears inside the Docusign frame. | `string` | `"There was a problem displaying this form. Please refresh the page. If this problem continues, contact Support."` | -| `step3_docusignExpired` | `step-3_docusign-expired` | This appears inside the Docusign frame. | `string` | `"For your security and privacy, we automatically end your session after 20 minutes of inactivity. Please refresh and re-enter your tax information to continue."` | -| `step3_docusignSessionWarning` | `step-3_docusign-session-warning` | Remind participants their session will time out after 20 minutes of inactivity. | `string` | `"For your security, we automatically end your session when you have not interacted with the form after 20 minutes."` | -| `step3_exitButton` | `step-3_exit-button` | | `string` | `"Exit"` | -| `step3_individualParticipant` | `step-3_individual-participant` | An option for the participant type field. Used to determine which W-8 form is required. | `string` | `"I am an individual participant"` | -| `step3_participantType` | `step-3_participant-type` | | `string` | `"Participant type"` | -| `step3_refreshButton` | `step-3_refresh-button` | | `string` | `"Refresh Page"` | -| `step3_taxForm` | `step-3_tax-form` | | `string` | `"Tax form"` | -| `step3_taxFormDescription` | `step-3_tax-form-description` | Displayed at the top of the page to participants based in the US. | `string` | `"Participants based in the US need to submit a {documentType} form."` | -| `step3_taxFormDescriptionBusinessEntity` | `step-3_tax-form-description-business-entity` | Displayed at the top of the page to participants representing a business. | `string` | `"Participants residing outside of the US who represent a business entity need to submit a {documentType} form."` | -| `step3_taxFormDescriptionIndividualParticipant` | `step-3_tax-form-description-individual-participant` | Displayed at the top of the page to individuals joining a US program who reside outside the country. | `string` | `"Participants residing outside of the US, joining the referral program of a US-based company, need to submit a {documentType} form."` | -| `step3_taxFormLabel` | `step-3_tax-form-label` | Display the type of tax form that the participant must submit. | `string` | `"{documentType} Tax Form"` | -| `step4_agencyCodeLabel` | `step-4_agency-code-label` | | `string` | `"Agency code"` | -| `step4_bankAccountNumberLabel` | `step-4_bank-account-number-label` | | `string` | `"Bank account number"` | -| `step4_bankAccountTypeLabel` | `step-4_bank-account-type-label` | | `string` | `"Bank account type"` | -| `step4_bankAddressLabel` | `step-4_bank-address-label` | | `string` | `"Bank address"` | -| `step4_bankCityLabel` | `step-4_bank-city-label` | | `string` | `"Bank city"` | -| `step4_bankLocationLabel` | `step-4_bank-location-label` | | `string` | `"Bank country location"` | -| `step4_bankNameLabel` | `step-4_bank-name-label` | | `string` | `"Bank name"` | -| `step4_bankPostalCodeLabel` | `step-4_bank-postal-code-label` | | `string` | `"Bank postal code"` | -| `step4_bankStateLabel` | `step-4_bank-state-label` | | `string` | `"Bank Province / State"` | -| `step4_beneficiaryAccountNameLabel` | `step-4_beneficiary-account-name-label` | | `string` | `"Beneficiary account name"` | -| `step4_branchCodeLabel` | `step-4_branch-code-label` | | `string` | `"Branch code"` | -| `step4_businessSelectItemLabel` | `step-4_business-select-item-label` | One of three options listed for the classification field | `string` | `"Business"` | -| `step4_checkingSelectItemLabel` | `step-4_checking-select-item-label` | | `string` | `"Checking"` | -| `step4_classificationCPFLabel` | `step-4_classification-c-p-f-label` | | `string` | `"Classification CPF"` | -| `step4_classificationEntityLabel` | `step-4_classification-entity-label` | | `string` | `"Classification entity"` | -| `step4_classificationLabel` | `step-4_classification-label` | Label text for the classification input field | `string` | `"Classification"` | -| `step4_directlyToBankAccount` | `step-4_directly-to-bank-account` | | `string` | `"Directly to my bank account"` | -| `step4_eftWithdrawalLabel` | `step-4_eft-withdrawal-label` | Default payment method to the participants’ bank account. | `string` | `"EFT Withdrawal (free)"` | -| `step4_foreignSelectItemLabel` | `step-4_foreign-select-item-label` | One of three options listed for the classification field | `string` | `"Foreign"` | -| `step4_fxWireProcessingFeeLabel` | `step-4_fx-wire-processing-fee-label` | | `string` | `"FX Wire (Processing Fee {currency}{defaultFxFee}.00)"` | -| `step4_ibanLabel` | `step-4_iban-label` | | `string` | `"IBAN"` | -| `step4_individualSelectItemLabel` | `step-4_individual-select-item-label` | One of three options listed for the classification field | `string` | `"Individual"` | -| `step4_patronymicNameLabel` | `step-4_patronymic-name-label` | | `string` | `"Patronymic name"` | -| `step4_payPalInputLabel` | `step-4_pay-pal-input-label` | Displayed to participants who choose PayPal as their payout method | `string` | `"PayPal email"` | -| `step4_paymentDayFifteenthOfMonthLabelText` | `step-4_payment-day-fifteenth-of-month-label-text` | One of two payment day options | `string` | `"15th of the month"` | -| `step4_paymentDayFirstOfMonthLabelText` | `step-4_payment-day-first-of-month-label-text` | One of two payment day options | `string` | `"1st of the month"` | -| `step4_paymentDaySelectLabel` | `step-4_payment-day-select-label` | Let the participant choose what day of the month they’ll get paid | `string` | `"Payment Day"` | -| `step4_paymentMethod` | `step-4_payment-method` | | `string` | `"Payment method"` | -| `step4_paymentMethodSubtext` | `step-4_payment-method-subtext` | | `string` | `"Payouts will be sent from our referral program provider, impact.com."` | -| `step4_paymentSchedule` | `step-4_payment-schedule` | | `string` | `"Payment schedule"` | -| `step4_paymentScheduleBalanceThreshold` | `step-4_payment-schedule-balance-threshold` | | `string` | `"Pay me when my balance reaches a threshold"` | -| `step4_paymentScheduleFixedDay` | `step-4_payment-schedule-fixed-day` | | `string` | `"Pay me on a fixed day of the month"` | -| `step4_paymentThresholdSelectLabel` | `step-4_payment-threshold-select-label` | Participant use this field to select the balance at which they want to be paid | `string` | `"Payment threshold"` | -| `step4_routingCodeLabel` | `step-4_routing-code-label` | | `string` | `"{bankCountry, select, AU {BSB number} CA {Routing number} CZ {Bank code} HK {Clearing code} SG {Clearing code} US {ABA routing number} NZ {BSB number} ZA {Bank/Branch number} IN {IFSC} CNY {CNAPS} other {Routing code} }"` | -| `step4_savingsSelectItemLabel` | `step-4_savings-select-item-label` | Label text for the savings account type select item | `string` | `"Savings"` | -| `step4_swiftCodeLabel` | `step-4_swift-code-label` | | `string` | `"SWIFT code"` | -| `step4_taxAndPayouts` | `step-4_tax-and-payouts` | | `string` | `"Payouts"` | -| `step4_taxPayerIdLabel` | `step-4_tax-payer-id-label` | | `string` | `"Beneficiary INN"` | -| `step4_toPayPalAccount` | `step-4_to-pay-pal-account` | | `string` | `"PayPal (2% processing fee capped to {feeCap})"` | -| `step4_voCodeLabel` | `step-4_vo-code-label` | | `string` | `"VO code"` | -| `taxAndPayoutsDescription` | `tax-and-payouts-description` | Displayed at the top of the page on all set up steps. | `string` | `"Submit your tax documents and add your banking information to receive your rewards."` | +| Property | Attribute | Description | Type | Default | +| ----------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `backButton` | `back-button` | | `string` | `"Back"` | +| `cancelButton` | `cancel-button` | | `string` | `"Cancel"` | +| `continueButton` | `continue-button` | | `string` | `"Continue"` | +| `dashboard_accountText` | `dashboard_account-text` | Shown before the participant’s bank account information. | `string` | `"Account"` | +| `dashboard_badgeTextAwaitingReview` | `dashboard_badge-text-awaiting-review` | Additional text displayed next to the tax form's status badge. | `string` | `"Awaiting review. Submitted on {dateSubmitted}."` | +| `dashboard_badgeTextSubmittedOn` | `dashboard_badge-text-submitted-on` | Additional text displayed next to the tax form's status badge | `string` | `"Submitted on {dateSubmitted}."` | +| `dashboard_badgeTextSubmittedOnW8` | `dashboard_badge-text-submitted-on-w-8` | Additional text displayed next to the tax form's status badge. | `string` | `"Submitted on {dateSubmitted}. Valid for three years after submission."` | +| `dashboard_bankingInformationSectionHeader` | `dashboard_banking-information-section-header` | | `string` | `"Payout Information"` | +| `dashboard_dateColumnTitle` | `dashboard_date-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Date"` | +| `dashboard_earningsAfterTaxColumnTitle` | `dashboard_earnings-after-tax-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Earnings after tax"` | +| `dashboard_earningsColumnTitle` | `dashboard_earnings-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Earnings"` | +| `dashboard_editPaymentInformationButton` | `dashboard_edit-payment-information-button` | | `string` | `"Edit Payout Information"` | +| `dashboard_indirectTaxColumnTitle` | `dashboard_indirect-tax-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Indirect tax"` | +| `dashboard_indirectTaxDetails` | `dashboard_indirect-tax-details` | Displayed to participants who have submitted their indirect tax information. | `string` | `"{indirectTaxType} number: {indirectTaxNumber}"` | +| `dashboard_indirectTaxInfoCanada` | `dashboard_indirect-tax-info-canada` | If the participant is registered for indirect tax in Canada, display the province they’re registered in. | `string` | `"Registered in {province}, {country}"` | +| `dashboard_indirectTaxInfoOtherCountry` | `dashboard_indirect-tax-info-other-country` | If the participant is registered for indirect tax, display the country they’re registered in. | `string` | `"Registered in {country}"` | +| `dashboard_indirectTaxInfoSectionHeader` | `dashboard_indirect-tax-info-section-header` | | `string` | `"Indirect tax"` | +| `dashboard_indirectTaxInfoSpain` | `dashboard_indirect-tax-info-spain` | If the participant is registered for indirect tax in Spain, display the region they’re registered in. | `string` | `"Registered in {country}, {subRegion}"` | +| `dashboard_indirectTaxTooltipSupport` | `dashboard_indirect-tax-tooltip-support` | | `string` | `"To make changes to your indirect tax information, please contact our {supportLink}."` | +| `dashboard_invalidForm` | `dashboard_invalid-form` | Additional text displayed next to the tax form's status badge. | `string` | `"Make sure your information is correct and submit new form."` | +| `dashboard_invoiceColumnTitle` | `dashboard_invoice-column-title` | Part of the Invoice table displayed at the bottom of the page. | `string` | `"Invoice"` | +| `dashboard_invoiceDescription` | `dashboard_invoice-description` | | `string` | `"View and download your invoices to report your earnings and stay tax compliant."` | +| `dashboard_invoiceEmptyStateHeader` | `dashboard_invoice-empty-state-header` | | `string` | `"View your invoice details"` | +| `dashboard_invoiceEmptyStateText` | `dashboard_invoice-empty-state-text` | | `string` | `"Refer a friend to view the status of your invoices and rewards earned"` | +| `dashboard_invoiceHeader` | `dashboard_invoice-header` | | `string` | `"Invoices"` | +| `dashboard_invoiceMoreLabel` | `dashboard_invoice-more-label` | | `string` | `"Next"` | +| `dashboard_invoicePrevLabel` | `dashboard_invoice-prev-label` | | `string` | `"Prev"` | +| `dashboard_newFormButton` | `dashboard_new-form-button` | | `string` | `"Submit new form"` | +| `dashboard_noFormNeededSubtext` | `dashboard_no-form-needed-subtext` | No other statuses or badges will be displayed in the tax form section in this case. | `string` | `"Tax documents are only required if you are based in the US or joining the referral program of a US based brand."` | +| `dashboard_notRegisteredForTax` | `dashboard_not-registered-for-tax` | | `string` | `"Not registered for indirect tax. If you’ve previously registered with your tax authority, contact our {supportLink} to add your information to stay tax compliant."` | +| `dashboard_payoutFromImpact` | `dashboard_payout-from-impact` | Displayed under the payout details card. | `string` | `"Your balance may take up to 24 hours to update. Payouts will be sent from our referral program provider, impact.com."` | +| `dashboard_payoutMissingInformationText` | `dashboard_payout-missing-information-text` | Text displayed for existing publishers that do not have saved banking information. | `string` | `"Missing banking information, go to Impact.com to resolve."` | +| `dashboard_qstNumber` | `dashboard_qst-number` | Displayed to participants registered in Quebec, Canada. | `string` | `"QST number: {qstNumber}"` | +| `dashboard_replaceTaxFormModalBodyText` | `dashboard_replace-tax-form-modal-body-text` | | `string` | `"Submitting a new tax form will remove your existing form. Make sure to sign and complete your new tax form to prevent any issues with your next payout."` | +| `dashboard_replaceTaxFormModalHeader` | `dashboard_replace-tax-form-modal-header` | | `string` | `"Replace existing tax form"` | +| `dashboard_statusBadgeText` | `dashboard_status-badge-text` | | `string` | `"{badgeText, select, payoutToday {Payout Today} nextPayout {Next Payout} }"` | +| `dashboard_statusTextActive` | `dashboard_status-text-active` | | `string` | `"Active"` | +| `dashboard_statusTextNotActive` | `dashboard_status-text-not-active` | | `string` | `"Invalid Tax Form"` | +| `dashboard_statusTextNotVerified` | `dashboard_status-text-not-verified` | Displayed when the participant submitted their form but it is awaiting review. | `string` | `"Not Verified"` | +| `dashboard_subRegionTaxNumber` | `dashboard_sub-region-tax-number` | | `string` | `"Income tax number: {subRegionTaxNumber}"` | +| `dashboard_taxAlertHeaderNotActiveW8` | `dashboard_tax-alert-header-not-active-w-8` | Part of the alert displayed at the top of the page. | `string` | `"{documentType} tax form is invalid"` | +| `dashboard_taxAlertHeaderNotActiveW9` | `dashboard_tax-alert-header-not-active-w-9` | Part of the alert displayed at the top of the page. | `string` | `"Your W9 tax form has personal information that doesn’t match your profile"` | +| `dashboard_taxAlertNotActiveMessageW8` | `dashboard_tax-alert-not-active-message-w-8` | Part of the alert displayed at the top of the page. | `string` | `"Your tax form may have expired or has personal information that doesn’t match your profile. Please submit a new {documentType} form."` | +| `dashboard_taxAlertNotActiveMessageW9` | `dashboard_tax-alert-not-active-message-w-9` | Part of the alert displayed at the top of the page. | `string` | `"Please resubmit a new {documentType} form."` | +| `dashboard_taxDocumentSectionHeader` | `dashboard_tax-document-section-header` | | `string` | `"Tax documents"` | +| `dashboard_taxDocumentSectionSubHeader` | `dashboard_tax-document-section-sub-header` | Displayed under the tax document section header. | `string` | `"{documentType} tax form"` | +| `dashboard_thresholdPayoutText` | `dashboard_threshold-payout-text` | Display participants' payout preference on the payout information card, indicating the balance at which they want to get paid. | `string` | `"Next payout occurs when balance is {thresholdBalance}"` | +| `demoData` | -- | | `{ loading?: boolean; setStep?: (value: string) => void; step?: string; context?: TaxContext; namespace?: string; }` | `undefined` | +| `fieldInvalidError` | `field-invalid-error` | Displayed under a field when it has an invalid entry. | `string` | `"{fieldName} is invalid"` | +| `fieldRequiredError` | `field-required-error` | Displayed under a field that is missing required information. | `string` | `"{fieldName} is required"` | +| `formStep` | `form-step` | | `string` | `"Step {step} of {count}"` | +| `generalErrorDescription` | `general-error-description` | Part of the alert displayed at the top of the page. | `string` | `"Please review your information and try again. If this problem continues, contact our {supportLink}."` | +| `generalErrorTitle` | `general-error-title` | Part of the alert displayed at the top of the page. | `string` | `"There was a problem submitting your information"` | +| `invalidCharacterError` | `invalid-character-error` | Displayed under a field that includes invalid characters (non-ASCII). | `string` | `"{fieldName} includes characters that aren't supported."` | +| `isPartnerAlertDescription` | `is-partner-alert-description` | Part of the alert displayed at the top of the page if the participant is already a registered partner on impact.com. | `string` | `"If you don’t recognize this referral program provider or believe this is a mistake, please contact our {supportLink} or sign up for this referral program with a different email."` | +| `isPartnerAlertHeader` | `is-partner-alert-header` | Part of the alert displayed at the top of the page if the participant is already a registered partner on impact.com. | `string` | `"An account with this email already exists with our referral program provider, impact.com"` | +| `loadingErrorAlertDescription` | `loading-error-alert-description` | Part of the alert displayed at the top of the page. | `string` | `"Please refresh the page and try again. If this problem continues, contact our {supportLink}."` | +| `loadingErrorAlertHeader` | `loading-error-alert-header` | Part of the alert displayed at the top of the page. | `string` | `"There was a problem loading your form"` | +| `payoutHoldAlertDescription` | `payout-hold-alert-description` | Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. | `string` | `"Please contact our {supportLink} or check your inbox for an email from our referral program provider, impact.com."` | +| `payoutHoldAlertHeader` | `payout-hold-alert-header` | Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. | `string` | `"Your payout is on hold"` | +| `searchForCountryText` | `search-for-country-text` | Placeholder text displayed in the country search dropdown | `string` | `"Search for country.."` | +| `step1_address` | `step-1_address` | | `string` | `"Address"` | +| `step1_allowBankingCollection` | `step-1_allow-banking-collection` | Edit the property called terms and conditions text to change what's displayed for {termsAndConditionsLink}. | `string` | `"I have read the {termsAndConditionsLink} and allow impact.com to collect my tax and banking information"` | +| `step1_city` | `step-1_city` | | `string` | `"City"` | +| `step1_country` | `step-1_country` | | `string` | `"Country"` | +| `step1_currency` | `step-1_currency` | | `string` | `"Currency"` | +| `step1_currencyHelpText` | `step-1_currency-help-text` | | `string` | `"Choose your preferred payout currency"` | +| `step1_email` | `step-1_email` | | `string` | `"Email"` | +| `step1_firstName` | `step-1_first-name` | | `string` | `"First name"` | +| `step1_lastName` | `step-1_last-name` | | `string` | `"Last name"` | +| `step1_personalInformation` | `step-1_personal-information` | | `string` | `"Personal Information"` | +| `step1_phoneNumber` | `step-1_phone-number` | | `string` | `"Phone number"` | +| `step1_postalCode` | `step-1_postal-code` | | `string` | `"Postal code"` | +| `step1_province` | `step-1_province` | | `string` | `"Province"` | +| `step1_region` | `step-1_region` | | `string` | `"Region"` | +| `step1_searchForCurrencyText` | `step-1_search-for-currency-text` | Placeholder text displayed in the currency search dropdown | `string` | `"Search for currency.."` | +| `step1_state` | `step-1_state` | | `string` | `"State"` | +| `step1_termsAndConditionsLabel` | `step-1_terms-and-conditions-label` | The link text that appears in the terms and conditions checkbox | `string` | `"terms and conditions"` | +| `step1_termsAndConditionsLink` | `step-1_terms-and-conditions-link` | The link that appears in the terms and conditions checkbox | `string` | `"/payout-terms-and-conditions"` | +| `step2_cannotChangeInfoAlert` | `step-2_cannot-change-info-alert` | Communicate that after this step, only Support can change personal and indirect tax information. | `string` | `"Changes to your personal and indirect tax information can only be made through our {supportLink} after you complete this step. Make sure these are correct before continuing."` | +| `step2_indirectTax` | `step-2_indirect-tax` | | `string` | `"Indirect Tax"` | +| `step2_indirectTaxDescription` | `step-2_indirect-tax-description` | Displayed under the title of this step. | `string` | `"Indirect taxes (e.g. VAT, HST, GST) are transaction based taxes often applied to goods and services. Service providers are typically required to register with their tax authority and collect these taxes on behalf governments."` | +| `step2_indirectTaxDetails` | `step-2_indirect-tax-details` | Displayed with indirect tax registration options. | `string` | `"Indirect tax details"` | +| `step2_indirectTaxNumber` | `step-2_indirect-tax-number` | | `string` | `"{taxType, select, GST {GST number} HST {HST number} VAT {VAT number} CT {CT number} SST {SST number} GENERAL {Indirect tax number}}"` | +| `step2_indirectTaxNumberError` | `step-2_indirect-tax-number-error` | | `string` | `"{taxType, select, GST {GST number} HST {HST number} VAT {VAT number} CT {CT number} SST {SST number} GENERAL {Indirect tax number}} is required"` | +| `step2_isRegisteredQST` | `step-2_is-registered-q-s-t` | Displayed to participants registered for indirect tax in Quebec, Canada. | `string` | `"I am registered for QST Tax"` | +| `step2_isRegisteredSubRegionIncomeTax` | `step-2_is-registered-sub-region-income-tax` | Displayed to participants registered for indirect tax in Spain. | `string` | `"I am an individual registered for Income Tax purposes in Spain, and withholding tax will apply to any payments made to me."` | +| `step2_notRegistered` | `step-2_not-registered` | | `string` | `"Not registered for indirect tax"` | +| `step2_notRegisteredSubtext` | `step-2_not-registered-subtext` | Participants based in the US are considered not registered. | `string` | `"If you’ve never set up indirect tax with your tax authority, then you’re likely not considered registered."` | +| `step2_otherRegion` | `step-2_other-region` | | `string` | `"Registered for indirect tax"` | +| `step2_otherRegionSubtext` | `step-2_other-region-subtext` | Selecting this option will display fields to enter indirect tax details. | `string` | `"If you’ve registered with your tax authority, add your information to stay tax compliant."` | +| `step2_province` | `step-2_province` | | `string` | `"Province"` | +| `step2_qstNumber` | `step-2_qst-number` | Displayed to participants registered for QST. | `string` | `"QST number"` | +| `step2_selectedRegion` | `step-2_selected-region` | | `string` | `"Country / region of indirect tax"` | +| `step2_subRegion` | `step-2_sub-region` | Displayed to participants registered in Spain. | `string` | `"Sub-region"` | +| `step2_subRegionTaxNumberLabel` | `step-2_sub-region-tax-number-label` | | `string` | `"Income Tax Number"` | +| `step3_businessEntity` | `step-3_business-entity` | An option for the participant type field. Used to determine which W-8 form is required. | `string` | `"I represent a business"` | +| `step3_docusignError` | `step-3_docusign-error` | This appears inside the Docusign frame. | `string` | `"There was a problem displaying this form. Please refresh the page. If this problem continues, contact our {supportLink}."` | +| `step3_docusignExpired` | `step-3_docusign-expired` | This appears inside the Docusign frame. | `string` | `"For your security and privacy, we automatically end your session after 20 minutes of inactivity. Please refresh and re-enter your tax information to continue."` | +| `step3_docusignSessionWarning` | `step-3_docusign-session-warning` | Remind participants their session will time out after 20 minutes of inactivity. | `string` | `"For your security, we automatically end your session when you have not interacted with the form after 20 minutes."` | +| `step3_exitButton` | `step-3_exit-button` | | `string` | `"Exit"` | +| `step3_individualParticipant` | `step-3_individual-participant` | An option for the participant type field. Used to determine which W-8 form is required. | `string` | `"I am an individual participant"` | +| `step3_participantType` | `step-3_participant-type` | | `string` | `"Participant type"` | +| `step3_refreshButton` | `step-3_refresh-button` | | `string` | `"Refresh Page"` | +| `step3_taxForm` | `step-3_tax-form` | | `string` | `"Tax form"` | +| `step3_taxFormDescription` | `step-3_tax-form-description` | Displayed at the top of the page to participants based in the US. | `string` | `"Participants based in the US need to submit a {documentType} form."` | +| `step3_taxFormDescriptionBusinessEntity` | `step-3_tax-form-description-business-entity` | Displayed at the top of the page to participants representing a business. | `string` | `"Participants residing outside of the US who represent a business entity need to submit a {documentType} form."` | +| `step3_taxFormDescriptionIndividualParticipant` | `step-3_tax-form-description-individual-participant` | Displayed at the top of the page to individuals joining a US program who reside outside the country. | `string` | `"Participants residing outside of the US, joining the referral program of a US-based company, need to submit a {documentType} form."` | +| `step3_taxFormLabel` | `step-3_tax-form-label` | Display the type of tax form that the participant must submit. | `string` | `"{documentType} Tax Form"` | +| `step4_agencyCodeLabel` | `step-4_agency-code-label` | | `string` | `"Agency code"` | +| `step4_bankAccountNumberLabel` | `step-4_bank-account-number-label` | | `string` | `"Bank account number"` | +| `step4_bankAccountTypeLabel` | `step-4_bank-account-type-label` | | `string` | `"Bank account type"` | +| `step4_bankAddressLabel` | `step-4_bank-address-label` | | `string` | `"Bank address"` | +| `step4_bankCityLabel` | `step-4_bank-city-label` | | `string` | `"Bank city"` | +| `step4_bankLocationLabel` | `step-4_bank-location-label` | | `string` | `"Bank country location"` | +| `step4_bankNameLabel` | `step-4_bank-name-label` | | `string` | `"Bank name"` | +| `step4_bankPostalCodeLabel` | `step-4_bank-postal-code-label` | | `string` | `"Bank postal code"` | +| `step4_bankStateLabel` | `step-4_bank-state-label` | | `string` | `"Bank Province / State"` | +| `step4_beneficiaryAccountNameLabel` | `step-4_beneficiary-account-name-label` | | `string` | `"Beneficiary account name"` | +| `step4_branchCodeLabel` | `step-4_branch-code-label` | | `string` | `"Branch code"` | +| `step4_businessSelectItemLabel` | `step-4_business-select-item-label` | One of three options listed for the classification field | `string` | `"Business"` | +| `step4_checkingSelectItemLabel` | `step-4_checking-select-item-label` | | `string` | `"Checking"` | +| `step4_classificationCPFLabel` | `step-4_classification-c-p-f-label` | | `string` | `"Classification CPF"` | +| `step4_classificationEntityLabel` | `step-4_classification-entity-label` | | `string` | `"Classification entity"` | +| `step4_classificationLabel` | `step-4_classification-label` | Label text for the classification input field | `string` | `"Classification"` | +| `step4_directlyToBankAccount` | `step-4_directly-to-bank-account` | | `string` | `"Directly to my bank account"` | +| `step4_eftWithdrawalLabel` | `step-4_eft-withdrawal-label` | Default payment method to the participants’ bank account. | `string` | `"EFT Withdrawal (free)"` | +| `step4_foreignSelectItemLabel` | `step-4_foreign-select-item-label` | One of three options listed for the classification field | `string` | `"Foreign"` | +| `step4_fxWireProcessingFeeLabel` | `step-4_fx-wire-processing-fee-label` | | `string` | `"FX Wire (Processing Fee {currency}{defaultFxFee}.00)"` | +| `step4_ibanLabel` | `step-4_iban-label` | | `string` | `"IBAN"` | +| `step4_individualSelectItemLabel` | `step-4_individual-select-item-label` | One of three options listed for the classification field | `string` | `"Individual"` | +| `step4_patronymicNameLabel` | `step-4_patronymic-name-label` | | `string` | `"Patronymic name"` | +| `step4_payPalInputLabel` | `step-4_pay-pal-input-label` | Displayed to participants who choose PayPal as their payout method | `string` | `"PayPal email"` | +| `step4_paymentDayFifteenthOfMonthLabelText` | `step-4_payment-day-fifteenth-of-month-label-text` | One of two payment day options | `string` | `"15th of the month"` | +| `step4_paymentDayFirstOfMonthLabelText` | `step-4_payment-day-first-of-month-label-text` | One of two payment day options | `string` | `"1st of the month"` | +| `step4_paymentDaySelectLabel` | `step-4_payment-day-select-label` | Let the participant choose what day of the month they’ll get paid | `string` | `"Payment Day"` | +| `step4_paymentMethod` | `step-4_payment-method` | | `string` | `"Payment method"` | +| `step4_paymentMethodSubtext` | `step-4_payment-method-subtext` | | `string` | `"Payouts will be sent from our referral program provider, impact.com."` | +| `step4_paymentSchedule` | `step-4_payment-schedule` | | `string` | `"Payment schedule"` | +| `step4_paymentScheduleBalanceThreshold` | `step-4_payment-schedule-balance-threshold` | | `string` | `"Pay me when my balance reaches a threshold"` | +| `step4_paymentScheduleFixedDay` | `step-4_payment-schedule-fixed-day` | | `string` | `"Pay me on a fixed day of the month"` | +| `step4_paymentThresholdSelectLabel` | `step-4_payment-threshold-select-label` | Participant use this field to select the balance at which they want to be paid | `string` | `"Payment threshold"` | +| `step4_routingCodeLabel` | `step-4_routing-code-label` | | `string` | `"{bankCountry, select, AU {BSB number} CA {Routing number} CZ {Bank code} HK {Clearing code} SG {Clearing code} US {ABA routing number} NZ {BSB number} ZA {Bank/Branch number} IN {IFSC} CNY {CNAPS} other {Routing code} }"` | +| `step4_savingsSelectItemLabel` | `step-4_savings-select-item-label` | Label text for the savings account type select item | `string` | `"Savings"` | +| `step4_swiftCodeLabel` | `step-4_swift-code-label` | | `string` | `"SWIFT code"` | +| `step4_taxAndPayouts` | `step-4_tax-and-payouts` | | `string` | `"Payouts"` | +| `step4_taxPayerIdLabel` | `step-4_tax-payer-id-label` | | `string` | `"Beneficiary INN"` | +| `step4_toPayPalAccount` | `step-4_to-pay-pal-account` | | `string` | `"PayPal (2% processing fee capped to {feeCap})"` | +| `step4_voCodeLabel` | `step-4_vo-code-label` | | `string` | `"VO code"` | +| `supportLink` | `support-link` | Link text for contacting support team | `string` | `"support team"` | +| `taxAndPayoutsDescription` | `tax-and-payouts-description` | Displayed at the top of the page on all set up steps. | `string` | `"Submit your tax documents and add your banking information to receive your rewards."` | ## Dependencies @@ -183,6 +184,8 @@ graph TD; sqm-tax-and-cash --> sqm-banking-info-form sqm-tax-and-cash --> sqm-tax-and-cash-dashboard sqm-user-info-form --> sqm-tax-and-cash + sqm-banking-info-form --> sqm-code-verification + sqm-code-verification --> sqm-form-message sqm-tax-and-cash-dashboard --> sqm-payout-details-card sqm-tax-and-cash-dashboard --> sqm-invoice-table sqm-tax-and-cash-dashboard --> sqm-invoice-table-download-column diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/sqm-tax-and-cash.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/sqm-tax-and-cash.tsx index 1913cdc68c..fdacdad15b 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/sqm-tax-and-cash.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/sqm-tax-and-cash.tsx @@ -118,13 +118,15 @@ export class TaxAndCashMonolith { * @uiName Terms and conditions link * @uiGroup Step 1 Properties */ - @Prop() step1_termsAndConditionsLink: string = "/payout-terms-and-conditions"; + @Prop() step1_termsAndConditionsLink: string = + "https://terms.advocate.impact.com/PayoutTermsAndConditions.html"; /** * Placeholder text displayed in the currency search dropdown * @uiName Currency field placeholder text * @uiGroup Step 1 Properties */ @Prop() step1_searchForCurrencyText: string = "Search for currency.."; + /** /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ STEP 2 PROPS: @@ -237,7 +239,7 @@ export class TaxAndCashMonolith { * @uiWidget textArea */ @Prop() step2_cannotChangeInfoAlert: string = - "Changes to your personal and indirect tax information can only be made through our Support team after you complete this step. Make sure these are correct before continuing."; + "Changes to your personal and indirect tax information can only be made through our {supportLink} after you complete this step. Make sure these are correct before continuing."; /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ STEP 3 PROPS: @@ -318,7 +320,7 @@ export class TaxAndCashMonolith { * @uiWidget textArea */ @Prop() step3_docusignError: string = - "There was a problem displaying this form. Please refresh the page. If this problem continues, contact Support."; + "There was a problem displaying this form. Please refresh the page. If this problem continues, contact our {supportLink}."; /** * @uiName Refresh page button label * @uiGroup Step 3 Properties @@ -733,7 +735,7 @@ export class TaxAndCashMonolith { * @uiWidget textArea */ @Prop() dashboard_indirectTaxTooltipSupport: string = - "To make changes to your indirect tax information, please contact Support."; + "To make changes to your indirect tax information, please contact our {supportLink}."; /** * Displayed to participants who have submitted their indirect tax information. * @@ -759,7 +761,7 @@ export class TaxAndCashMonolith { * @uiWidget textArea */ @Prop() dashboard_notRegisteredForTax: string = - "Not registered for indirect tax. If you’ve previously registered with your tax authority, contact Support to add your information to stay tax compliant."; + "Not registered for indirect tax. If you’ve previously registered with your tax authority, contact our {supportLink} to add your information to stay tax compliant."; /** * Displayed to participants registered in Quebec, Canada. * @uiName QST indirect tax details @@ -836,17 +838,15 @@ export class TaxAndCashMonolith { * @uiGroup Dashboard Properties * @uiWidget textArea */ - @Prop() - dashboard_errorTitleText: string = "Your payout is on hold "; + @Prop() payoutHoldAlertHeader: string = "Your payout is on hold"; /** * Part of the alert displayed at the top of the page when there’s been an issue preventing payouts. * @uiName Payout error message description * @uiGroup Dashboard Properties * @uiWidget textArea */ - @Prop() - dashboard_errorDescriptionText: string = - "If you’ve recently added your payout information, please wait while we verify your information. If it’s still on hold after a few days, please contact Support or check your inbox for an email from our referral program provider, impact.com."; + @Prop() payoutHoldAlertDescription: string = + "Please contact our {supportLink} or check your inbox for an email from our referral program provider, impact.com."; /** * Text displayed for existing publishers that do not have saved banking information. * @uiName Payout missing information subtext @@ -932,7 +932,7 @@ export class TaxAndCashMonolith { * @uiWidget textArea */ @Prop() generalErrorDescription: string = - "Please review your information and try again. If this problem continues, contact Support."; + "Please review your information and try again. If this problem continues, contact our {supportLink}."; /** * Displayed under a field that is missing required information. * @uiName Empty form field error message @@ -982,7 +982,7 @@ export class TaxAndCashMonolith { * @uiWidget textArea */ @Prop() isPartnerAlertDescription: string = - "If you don’t recognize this referral program provider or believe this is a mistake, please contact Support or sign up for this referral program with a different email."; + "If you don’t recognize this referral program provider or believe this is a mistake, please contact our {supportLink} or sign up for this referral program with a different email."; /** * Placeholder text displayed in the country search dropdown * @uiName Country field placeholder text @@ -1004,7 +1004,7 @@ export class TaxAndCashMonolith { * @uiWidget textArea */ @Prop() loadingErrorAlertDescription: string = - "Please refresh the page and try again. If this problem continues, contact Support."; + "Please refresh the page and try again. If this problem continues, contact our {supportLink}."; /** * Displayed at the top of the page on all set up steps. * @uiName Page description @@ -1012,6 +1012,12 @@ export class TaxAndCashMonolith { */ @Prop() taxAndPayoutsDescription: string = "Submit your tax documents and add your banking information to receive your rewards."; + /** + * Link text for contacting support team + * @uiName Suport link text + * @uiGroup General Form Properties + */ + @Prop() supportLink: string = "support team"; /** * @@ -1045,6 +1051,7 @@ export class TaxAndCashMonolith { taxAndPayoutsDescription: props.taxAndPayoutsDescription, searchForCountryText: props.searchForCountryText, formStep: props.formStep, + supportLink: props.supportLink, }; } diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/useTaxAndCash.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/useTaxAndCash.tsx index b5ac716722..46f5f199f5 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/useTaxAndCash.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-tax-and-cash/useTaxAndCash.tsx @@ -34,7 +34,7 @@ import { } from "./data"; function getCurrentStep(user: UserQuery["user"]) { - if (!user.impactConnection?.connected) { + if (!user.impactConnection?.connected || !user.impactConnection?.publisher) { return "/1"; } diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-tax-form.feature b/packages/mint-components/src/components/tax-and-cash/sqm-tax-form.feature index 32c9e4f514..6277345115 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-tax-form.feature +++ b/packages/mint-components/src/components/tax-and-cash/sqm-tax-form.feature @@ -3,6 +3,16 @@ Feature: Tax Form Flow Background: A user submits their Tax information + @motivating + Scenario: Users must complete a 2FA flow to view the tax form + Given the tax form has just loaded + When a user views the tax form + Then they see a 2FA flow + And they are required to verify their email + When they verify their email + Then the `sqm-tax-and-cash` component is loaded + And they can now go through the tax form + @minutia Scenario Outline: Participants can register as branded partners, provide indirect tax information and submit their tax forms Given they are on step 1 @@ -85,18 +95,19 @@ Feature: Tax Form Flow | Withholding tax Region | | QST Number | When they press "Continue" + And the registered partner Then they proceed to depending on their and participants Examples: - | countryCode | brandCountry | stepX | - | CA | US | 3 | - | CA | MX | 4 | - | US | CA | 3 | - | US | MX | 3 | - | GB | US | 3 | - | GB | US | 3 | - | ES | US | 3 | - | EG | MX | 4 | + | countryCode | brandCountry | hasExistingTaxDoc | stepX | + | CA | US | true | dashboard | + | CA | MX | n/a | dashboard | + | US | CA | false | 3 | + | US | MX | true | dashboard | + | GB | US | false | 3 | + | GB | US | true | dashboard | + | ES | US | false | 3 | + | EG | MX | n/a | dashboard | @minutia Scenario Outline: Participants based in another country working with non-US brands do not have to fillout Comply Exchange forms diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/readme.md b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/readme.md index 27509f08ff..8cebaee90c 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/readme.md +++ b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/readme.md @@ -7,40 +7,41 @@ ## Properties -| Property | Attribute | Description | Type | Default | -| ------------------------------ | --------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `address` | `address` | | `string` | `"Address"` | -| `allowBankingCollection` | `allow-banking-collection` | Edit the property called terms and conditions text to change what's displayed for {termsAndConditionsLink}. | `string` | `"I have read the {termsAndConditionsLink} and allow impact.com to collect my tax and banking information"` | -| `city` | `city` | | `string` | `"City"` | -| `continueButton` | `continue-button` | | `string` | `"Continue"` | -| `country` | `country` | | `string` | `"Country"` | -| `currency` | `currency` | | `string` | `"Currency"` | -| `currencyHelpText` | `currency-help-text` | | `string` | `"Choose your preferred payout currency"` | -| `demoData` | -- | | `{ states?: { step: string; hideState: boolean; hideSteps: boolean; disabled: boolean; loadingError: boolean; loading: boolean; isPartner: boolean; isUser: boolean; formState: { errors: {}; firstName: string; lastName: string; email: string; phoneNumberCountryCode: string; phoneNumber: string; countryCode: string; currency: string; address: string; city: string; state: string; postalCode: string; }; }; refs?: { formRef: Ref; currencyRef: Ref; phoneCountryRef: Ref; }; data?: { currencies: { displayName: string; currencyCode: string; }[]; countries: TaxCountry[]; phoneCountries: TaxCountry[]; allCurrencies: { displayName: string; currencyCode: string; }[]; allCountries: TaxCountry[]; regionLabelEnum: "STATE" \| "PROVINCE" \| "REGION"; regions: { label: string; value: string; }[]; }; setStep?: (value: string) => void; onSubmit?: (event: any) => Promise; }` | `undefined` | -| `email` | `email` | | `string` | `"Email"` | -| `fieldInvalidError` | `field-invalid-error` | Displayed under a field when it has an invalid entry. | `string` | `"{fieldName} is invalid"` | -| `fieldRequiredError` | `field-required-error` | Displayed under a field that is missing required information. | `string` | `"{fieldName} is required"` | -| `firstName` | `first-name` | | `string` | `"First name"` | -| `formStep` | `form-step` | | `string` | `"Step {step} of {count}"` | -| `generalErrorDescription` | `general-error-description` | Part of the alert displayed at the top of the page. | `string` | `"Please review your information and try again. If this problem continues, contact Support."` | -| `generalErrorTitle` | `general-error-title` | Part of the alert displayed at the top of the page. | `string` | `"There was a problem submitting your information"` | -| `invalidCharacterError` | `invalid-character-error` | Displayed under Address or City fields that includes invalid characters (non-ASCII). | `string` | `"{fieldName} includes characters that aren't supported."` | -| `isPartnerAlertDescription` | `is-partner-alert-description` | Part of the alert displayed at the top of the page if the participant is already a registered partner on impact.com. | `string` | `"If you don’t recognize this referral program provider or believe this is a mistake, please contact Support or sign up for this referral program with a different email."` | -| `isPartnerAlertHeader` | `is-partner-alert-header` | Part of the alert displayed at the top of the page if the participant is already a registered partner on impact.com. | `string` | `"An account with this email already exists with our referral program provider, impact.com"` | -| `lastName` | `last-name` | | `string` | `"Last name"` | -| `loadingErrorAlertDescription` | `loading-error-alert-description` | Part of the alert displayed at the top of the page. | `string` | `"Please refresh the page and try again. If this problem continues, contact Support."` | -| `loadingErrorAlertHeader` | `loading-error-alert-header` | Part of the alert displayed at the top of the page. | `string` | `"There was a problem loading your form"` | -| `personalInformation` | `personal-information` | | `string` | `"Personal Information"` | -| `phoneNumber` | `phone-number` | | `string` | `"Phone number"` | -| `postalCode` | `postal-code` | | `string` | `"Postal code"` | -| `province` | `province` | | `string` | `"Province"` | -| `region` | `region` | | `string` | `"Region"` | -| `searchForCountryText` | `search-for-country-text` | Placeholder text displayed in the country search dropdown | `string` | `"Search for country.."` | -| `searchForCurrencyText` | `search-for-currency-text` | Placeholder text displayed in the currency search dropdown | `string` | `"Search for currency.."` | -| `state` | `state` | | `string` | `"State"` | -| `taxAndPayoutsDescription` | `tax-and-payouts-description` | Displayed at the top of the page on all set up steps. | `string` | `"Submit your tax documents and add your banking information to receive your rewards."` | -| `termsAndConditionsLabel` | `terms-and-conditions-label` | The link text that appears in the terms and conditions checkbox | `string` | `"terms and conditions"` | -| `termsAndConditionsLink` | `terms-and-conditions-link` | The link that appears in the terms and conditions checkbox | `string` | `"/payout-terms-and-conditions"` | +| Property | Attribute | Description | Type | Default | +| ------------------------------ | --------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `address` | `address` | | `string` | `"Address"` | +| `allowBankingCollection` | `allow-banking-collection` | Edit the property called terms and conditions text to change what's displayed for {termsAndConditionsLink}. | `string` | `"I have read the {termsAndConditionsLink} and allow impact.com to collect my tax and banking information"` | +| `city` | `city` | | `string` | `"City"` | +| `continueButton` | `continue-button` | | `string` | `"Continue"` | +| `country` | `country` | | `string` | `"Country"` | +| `currency` | `currency` | | `string` | `"Currency"` | +| `currencyHelpText` | `currency-help-text` | | `string` | `"Choose your preferred payout currency"` | +| `demoData` | -- | | `{ states?: { step: string; hideState: boolean; hideSteps: boolean; disabled: boolean; loadingError: boolean; loading: boolean; isPartner: boolean; isUser: boolean; formState: { errors: {}; firstName: string; lastName: string; email: string; phoneNumberCountryCode: string; phoneNumber: string; countryCode: string; currency: string; address: string; city: string; state: string; postalCode: string; }; }; refs?: { formRef: Ref; currencyRef: Ref; phoneCountryRef: Ref; }; data?: { currencies: { displayName: string; currencyCode: string; }[]; countries: TaxCountry[]; phoneCountries: TaxCountry[]; allCurrencies: { displayName: string; currencyCode: string; }[]; allCountries: TaxCountry[]; regionLabelEnum: "STATE" \| "PROVINCE" \| "REGION"; regions: { label: string; value: string; }[]; }; setStep?: (value: string) => void; onSubmit?: (event: any) => Promise; }` | `undefined` | +| `email` | `email` | | `string` | `"Email"` | +| `fieldInvalidError` | `field-invalid-error` | Displayed under a field when it has an invalid entry. | `string` | `"{fieldName} is invalid"` | +| `fieldRequiredError` | `field-required-error` | Displayed under a field that is missing required information. | `string` | `"{fieldName} is required"` | +| `firstName` | `first-name` | | `string` | `"First name"` | +| `formStep` | `form-step` | | `string` | `"Step {step} of {count}"` | +| `generalErrorDescription` | `general-error-description` | Part of the alert displayed at the top of the page. | `string` | `"Please review your information and try again. If this problem continues, contact our {supportLink}."` | +| `generalErrorTitle` | `general-error-title` | Part of the alert displayed at the top of the page. | `string` | `"There was a problem submitting your information"` | +| `invalidCharacterError` | `invalid-character-error` | Displayed under Address or City fields that includes invalid characters (non-ASCII). | `string` | `"{fieldName} includes characters that aren't supported."` | +| `isPartnerAlertDescription` | `is-partner-alert-description` | Part of the alert displayed at the top of the page if the participant is already a registered partner on impact.com. | `string` | `"If you don’t recognize this referral program provider or believe this is a mistake, please contact our {supportLink} or sign up for this referral program with a different email."` | +| `isPartnerAlertHeader` | `is-partner-alert-header` | Part of the alert displayed at the top of the page if the participant is already a registered partner on impact.com. | `string` | `"An account with this email already exists with our referral program provider, impact.com"` | +| `lastName` | `last-name` | | `string` | `"Last name"` | +| `loadingErrorAlertDescription` | `loading-error-alert-description` | Part of the alert displayed at the top of the page. | `string` | `"Please refresh the page and try again. If this problem continues, contact our {supportLink}."` | +| `loadingErrorAlertHeader` | `loading-error-alert-header` | Part of the alert displayed at the top of the page. | `string` | `"There was a problem loading your form"` | +| `personalInformation` | `personal-information` | | `string` | `"Personal Information"` | +| `phoneNumber` | `phone-number` | | `string` | `"Phone number"` | +| `postalCode` | `postal-code` | | `string` | `"Postal code"` | +| `province` | `province` | | `string` | `"Province"` | +| `region` | `region` | | `string` | `"Region"` | +| `searchForCountryText` | `search-for-country-text` | Placeholder text displayed in the country search dropdown | `string` | `"Search for country.."` | +| `searchForCurrencyText` | `search-for-currency-text` | Placeholder text displayed in the currency search dropdown | `string` | `"Search for currency.."` | +| `state` | `state` | | `string` | `"State"` | +| `supportLink` | `support-link` | | `string` | `"support team"` | +| `taxAndPayoutsDescription` | `tax-and-payouts-description` | Displayed at the top of the page on all set up steps. | `string` | `"Submit your tax documents and add your banking information to receive your rewards."` | +| `termsAndConditionsLabel` | `terms-and-conditions-label` | The link text that appears in the terms and conditions checkbox | `string` | `"terms and conditions"` | +| `termsAndConditionsLink` | `terms-and-conditions-link` | The link that appears in the terms and conditions checkbox | `string` | `"/payout-terms-and-conditions"` | ## Dependencies @@ -65,6 +66,8 @@ ```mermaid graph TD; sqm-user-info-form --> sqm-user-info-form + sqm-banking-info-form --> sqm-code-verification + sqm-code-verification --> sqm-form-message sqm-tax-and-cash-dashboard --> sqm-payout-details-card sqm-tax-and-cash-dashboard --> sqm-invoice-table sqm-tax-and-cash-dashboard --> sqm-invoice-table-download-column diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form-view.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form-view.tsx index bf241be908..494dac80cf 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form-view.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form-view.tsx @@ -100,6 +100,7 @@ export interface UserInfoFormViewProps { termsAndConditionsLabel: string; termsAndConditionsLink: string; taxAndPayoutsDescription: string; + supportLink: string; error: { generalTitle: string; generalDescription: string; @@ -204,10 +205,8 @@ const style = { PhoneInputsSection: { display: "flex", - alignItems: "flex-start", flexDirection: "column", gap: "4px", - "& p": { fontSize: "var(--sl-font-size-small)", color: "var(--sl-input-label-color)", @@ -217,7 +216,6 @@ const style = { PhoneInputsContainer: { display: "flex", - alignItems: "flex-start", gap: "4px", width: "100%", @@ -319,7 +317,22 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { {text.error.loadingErrorAlertHeader}
- {text.error.loadingErrorAlertDescription} + {intl.formatMessage( + { + id: "loadingErrorAlertDescription", + defaultMessage: text.error.loadingErrorAlertDescription, + }, + { + supportLink: ( + + {text.supportLink} + + ), + } + )}
@@ -357,7 +370,22 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { {text.error.generalTitle}
- {text.error.generalDescription} + {intl.formatMessage( + { + id: "generalDescription", + defaultMessage: text.error.generalDescription, + }, + { + supportLink: ( + + {text.supportLink} + + ), + } + )} )} {(states.isPartner || states.isUser) && ( @@ -370,7 +398,22 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { {text.isPartnerAlertHeader}
- {text.isPartnerAlertDescription} + {intl.formatMessage( + { + id: "isPartnerAlertDescription", + defaultMessage: text.isPartnerAlertDescription, + }, + { + supportLink: ( + + {text.supportLink} + + ), + } + )} )} @@ -525,14 +568,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { id="phoneNumber" name="/phoneNumber" value={formState.phoneNumber} - validationError={({ value }) => - // Naive phone number validation - validateBillingField(/[a-zA-Z]+/, value) && - formatErrorMessage( - text.phoneNumber, - text.error.fieldInvalidError - ) - } + validationError={true} disabled={states.disabled || states.isPartner} {...(formState.errors?.phoneNumber ? { diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.feature b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.feature index 936b51ed31..621e0d6549 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.feature +++ b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.feature @@ -263,3 +263,15 @@ Feature: Tax Form Step One | United States | is not hidden | | Spain | is not hidden | + @minutia + Scenario Outline: Prefilled user email prioritises their managed identity email if it has been verified + Given a user with participant email + And managed identity email + And their managed identity is + Then the prefilled email in the user info form is + + Examples: + | participantEmail | miEmail | verified | email | + | p@example.com | null | N/A | p@example.com | + | p@example.com | mi@example.com | not verified | p@example.com | + | p@example.com | mi@example.com | verified | mi@example.com | diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.tsx index 1f6f08f9d2..95358c2e1e 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.tsx @@ -97,7 +97,7 @@ export class TaxForm { * @uiWidget textArea */ @Prop() isPartnerAlertDescription: string = - "If you don’t recognize this referral program provider or believe this is a mistake, please contact Support or sign up for this referral program with a different email."; + "If you don’t recognize this referral program provider or believe this is a mistake, please contact our {supportLink} or sign up for this referral program with a different email."; /** * Part of the alert displayed at the top of the page. * @uiName Form submission error message title @@ -111,7 +111,7 @@ export class TaxForm { * @uiWidget textArea */ @Prop() generalErrorDescription: string = - "Please review your information and try again. If this problem continues, contact Support."; + "Please review your information and try again. If this problem continues, contact our {supportLink}."; /** * Edit the property called terms and conditions text to change what's displayed for {termsAndConditionsLink}. * @uiName Terms and conditions checkbox @@ -128,7 +128,8 @@ export class TaxForm { * The link that appears in the terms and conditions checkbox * @uiName Terms and conditions link */ - @Prop() termsAndConditionsLink: string = "/payout-terms-and-conditions"; + @Prop() termsAndConditionsLink: string = + "https://terms.advocate.impact.com/PayoutTermsAndConditions.html"; /** * Placeholder text displayed in the country search dropdown * @uiName Country field placeholder text @@ -168,13 +169,17 @@ export class TaxForm { * @uiWidget textArea */ @Prop() loadingErrorAlertDescription: string = - "Please refresh the page and try again. If this problem continues, contact Support."; + "Please refresh the page and try again. If this problem continues, contact our {supportLink}."; /** * Displayed at the top of the page on all set up steps. * @uiName Page description */ @Prop() taxAndPayoutsDescription: string = "Submit your tax documents and add your banking information to receive your rewards."; + /** + * @uiName Support link text + */ + @Prop() supportLink: string = "support team"; /** * @undocumented diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx index 5475aa3d20..bf9d396662 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx @@ -102,10 +102,15 @@ export function useUserInfoForm(props: TaxForm) { // If form already filled out, skip initialising it if (objectIsFull(userFormContext)) return; + // Prefer MI email if it was verified before this + const email = user.managedIdentity?.emailVerified + ? user.managedIdentity.email + : user.email; + if (user.impactConnection?.publisher && user.impactConnection?.user) { // Initialise with partner information setUserFormContext({ - email: user.email, + email, firstName: user.impactConnection.user.firstName, lastName: user.impactConnection.user.lastName, countryCode: user.impactConnection.publisher.countryCode, @@ -121,7 +126,7 @@ export function useUserInfoForm(props: TaxForm) { } else if (!userFormContext?.email) { // Initialise with user information setUserFormContext({ - email: user.email, + email, firstName: user.firstName, lastName: user.lastName, countryCode: user.countryCode || "US", @@ -228,6 +233,8 @@ export function useUserInfoForm(props: TaxForm) { setUserFormContext({ ...userFormContext, + firstName: userData.firstName, + lastName: userData.lastName, phoneNumberCountryCode: userData.phoneNumberCountryCode, phoneNumber: userData.phoneNumber, countryCode: userData.countryCode, diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-veriff-flow.feature b/packages/mint-components/src/components/tax-and-cash/sqm-veriff-flow.feature new file mode 100644 index 0000000000..d85dd379fc --- /dev/null +++ b/packages/mint-components/src/components/tax-and-cash/sqm-veriff-flow.feature @@ -0,0 +1,34 @@ +@owner:coleton @author:coleton + +Feature: Veriff Identity Verification + + Scenario: User with IDV_CHECK_REQUIRED hold reason can start flow + Given a user has successfully completed the tax and cash form + And 'IDV_CHECK_REQUIRED' is included in their holdReasons + Then a verification button is displayed in the following components + | components | + | sqm-payouts-status-alert | + | sqm-tax-and-cash-dashboard | + When they click the button + Then a Veriff modal will open + + Scenario: Exiting the Veriff flow will refresh components + Given a user is in the Veriff flow + But they close the modal before completion + Then `refreshImpactPublisherFinanceStatus` mutation is called + And the `sqm:veriff-updated` custom event is fired on the window + And the following components will refresh + | components | + | sqm-payouts-status-alert | + | sqm-tax-and-cash-dashboard | + + Scenario: Finishing the Veriff flow will refresh components + Given a user is in the Veriff flow + When they complete the verification process + And the modal closes + Then `refreshImpactPublisherFinanceStatus` mutation is called + And the `sqm:veriff-updated` custom event is fired on the window + And the following components will refresh + | components | + | sqm-payouts-status-alert | + | sqm-tax-and-cash-dashboard | diff --git a/packages/mint-components/src/components/tax-and-cash/useVeriffApp.ts b/packages/mint-components/src/components/tax-and-cash/useVeriffApp.ts new file mode 100644 index 0000000000..a829fa6448 --- /dev/null +++ b/packages/mint-components/src/components/tax-and-cash/useVeriffApp.ts @@ -0,0 +1,78 @@ +import { + useMutation, + useUserIdentity, +} from "@saasquatch/component-boilerplate"; +import { createVeriffFrame, MESSAGES } from "@veriff/incontext-sdk"; +import { gql } from "graphql-request"; + +const CREATE_IMPACT_VERIFICATION_SESSION = gql` + mutation createImpactVerificationSession($user: UserIdInput!) { + createImpactPublisherIdentityVerificationSession(user: $user) { + sessionUrl + } + } +`; + +const REFRESH_USER_STATUS = gql` + mutation refreshImpactPublisherFinanceStatus($user: UserIdInput!) { + refreshImpactPublisherFinanceStatus(user: $user) { + id + } + } +`; + +export const VERIFF_COMPLETE_EVENT_KEY = "sqm:veriff-updated"; + +export function useVeriffApp() { + const { id, accountId } = useUserIdentity(); + const [createImpactVerificationSessionMutation, { loading, errors }] = + useMutation(CREATE_IMPACT_VERIFICATION_SESSION); + const [ + refreshImpactPublisherFinanceStatus, + { loading: refreshLoading, errors: refreshErrors }, + ] = useMutation(REFRESH_USER_STATUS); + + const refresh = async () => { + try { + const res = await refreshImpactPublisherFinanceStatus({ + user: { id, accountId }, + }); + if (!res || (res as Error).message) throw new Error(); + + window.dispatchEvent(new Event(VERIFF_COMPLETE_EVENT_KEY)); + } catch (e) { + console.error("Failed to initialise Veriff:", e); + } + }; + + const render = async () => { + try { + const res = await createImpactVerificationSessionMutation({ + user: { id, accountId }, + }); + if (!res || (res as Error).message) throw new Error(); + + const sessionUrl = + res.createImpactPublisherIdentityVerificationSession.sessionUrl; + + createVeriffFrame({ + url: sessionUrl, + onEvent: (msg: MESSAGES) => { + switch (msg) { + case MESSAGES.FINISHED: + case MESSAGES.CANCELED: + refresh(); + } + }, + }); + } catch (e) { + console.error("Failed to initialise Veriff:", e); + } + }; + + return { + render, + loading: loading || refreshLoading, + errors: errors || refreshErrors, + }; +} diff --git a/packages/mint-components/src/index.html b/packages/mint-components/src/index.html index 168b34e519..8734e5add5 100644 --- a/packages/mint-components/src/index.html +++ b/packages/mint-components/src/index.html @@ -9,10 +9,22 @@ Mint Components Stencilbook - + + diff --git a/packages/mint-components/src/templates/CashPayoutsReferralWidget.html b/packages/mint-components/src/templates/CashPayoutsReferralWidget.html new file mode 100644 index 0000000000..7c19f5c499 --- /dev/null +++ b/packages/mint-components/src/templates/CashPayoutsReferralWidget.html @@ -0,0 +1,106 @@ + + + + + + + + + + + +

Referrals

+
+ +

Cash Balance

+
+ +
+ + +

+ Get rewarded when your friend uses our product +

+
+ +

+ They’ll get a $50 credit towards a new account and + you’ll get: +

+ + + + + + + + +
+ +

Choose how you want to share

+

Your unique referral link:

+ + + + + Share via email + + + Share on LinkedIn + + + Post about us + + +
+ + + + + + + + + + + + + +
+ + + +
+
\ No newline at end of file diff --git a/packages/mint-components/src/templates/TermsAndConditions.html b/packages/mint-components/src/templates/TermsAndConditions.html new file mode 100644 index 0000000000..3c316b520f --- /dev/null +++ b/packages/mint-components/src/templates/TermsAndConditions.html @@ -0,0 +1,1657 @@ + + + + + + + + Payout Terms and Conditions + + + + + + +
+

User Agreement

+

This user agreement is effective as of July 1, 2024.

+ +

Advocate by impact.com

+

+ This user agreement (“Agreement”) is a contract between you and Impact Tech, Inc. + (“impact.com”) governing your participation in rewards or cashback programs that are + serviced by impact.com. In order to participate, you must create an impact.com account + (“Account”).To use the impact.com services, you must be a resident of a place, and have a + means to receive payment in such place, that is supported by impact.com technology + (supported jurisdictions and payment methods available upon sign-up or as may be updated by + posting a notice from time to time). +

+

+ You agree to comply with all of the terms and conditions in this Agreement. The terms + include the following additional policies (accessible at impact.com/legal), in addition to + any other agreement that you may enter into from time to time with impact.com, or applicable + policies including but not limited to our + Privacy Policy. +

+

+ We may revise this Agreement and any of the policies listed above at any time by posting the + new version in its place. The revised version will be effective upon the date of posting, + unless otherwise noted and subject to applicable laws. If our changes reduce your rights or + increase your responsibilities, we will provide at least 30 days’ notice to you. By + continuing to use our services after any changes to this Agreement become effective, you + agree to abide by and be bound by those changes. If you do not agree with any changes to + this Agreement, cease using your Account and you may notify us to close your impact.com + Account. For jurisdictions that require you to opt-in to changes, you will be required to + opt-in during a notice period specified in the notice, and if you do not do so the Agreement + is terminated. +

+

Creating an Account

+

+ To create an impact.com Account, you must be a resident of a supported jurisdiction, at + least 18 years old or the age of majority in your place of residence, and have a bank + account or other approved method of receiving funds. Your Account is personal to you, and + may not be shared with anyone else. Through your Account, you will be able to (depending + upon the reward or cashback program terms): +

+
    +
  • Be paid out for amounts earned as a participant in rewards or cashback programs.
  • +
  • Receive goods through participating in brands’ rewards programs.
  • +
  • Send participant amounts earned as donations to charitable organizations.
  • +
+

+ It is your responsibility to keep confidential any credentials used to access your + impact.com Account and to keep your phone number, email address, banking or payout method, + and other contact information current in your impact.com Account profile. Failure to do so + may result in a loss of amounts earned as a participant, delay in receiving your reward or + cashback, forfeiture of your Account, or impact.com closing your Account to protect the + integrity of our services and technology. +

+

+ Before you may be paid out amounts earned as a participant, you must submit full, complete, + and accurate tax information, as applicable to your relevant jurisdiction. Our team will + conduct due diligence based on the information that you provide. If we are unable to verify + your identity or if your tax information is incomplete or incorrect, you will have a + reasonable opportunity to complete or correct such information, but if we are still unable + to verify you and/or your tax ID your Account will be closed and any earned and pending + amounts will be forfeited. +

+

Closing Your Account

+

+ You may close your impact.com Account and terminate this Agreement at any time. To close + your impact.com Account, reach out to us via the Help Center. Any pending amounts recorded + on your Account but not yet earned as a participant will be canceled and removed from your + account. Closure of your Account does not stop the following: +

+
    +
  • An investigation into amounts earned by you as a participant.
  • +
  • + Resolution of an open dispute or claim concerning amounts not pending at the time of + closure. +
  • +
  • Repayment of amounts required to be repaid pursuant to this User Agreement.
  • +
+

Earnings Unpayable in Your impact.com Account

+

+ Unless otherwise expressly stated, all references to “funds” in these terms and conditions + mean money denominated in sovereign currency and not cryptocurrency or any other form of + asset. +

+

+ Any earned funds in your impact.com Account represent unsecured claims against impact.com. + Your impact.com Account does not accrue interest and you have no ownership interest or + investment in impact.com. +

+

Payment Reviews

+

+ Each earned reward or cashback may be reviewed prior to payment. We reserve the right to + review your Account and reward and cashback activity at any time and for any reason. Amounts + earned may be placed on hold pending receipt of additional information and/or documentation + that we ask you to provide in the review process, such as documentation to verify your + identity. We may withhold payment until verification is completed, and we may reverse or + cancel rewards or cashback amounts or even terminate your Account as a result of the + verification process. Other consequences of the review and/or verification process include + money or payments being applied to amounts you owe to impact.com or reversed for having been + determined to have been unearned or as a consequence of non-compliance with program terms or + this User Agreement. +

+

Account Statements

+

+ As long as you have access to your active Account, you will be able to view and download + information regarding your participation in rewards or cashback programs. +

+

Donations

+

+ If available, you may donate your earnings as a participant in rewards or cashback programs + to charitable organizations via impact.com. impact.com does not provide a tax receipt for + such donations. Charitable organizations are responsible for sending tax receipts directly + to you. impact.com will share your email address with any charitable organization profile to + which you send a donation for tax receipt purposes. +

+

Minimum Payout Thresholds and Fees

+

+ We may, at our discretion, impose minimum thresholds depending upon the payout method + elected by you. Additionally, there may be fees associated with some payout methods. Minimum + thresholds and fees will be disclosed when you are electing your payout method or if you + change your payout method. We may in our discretion limit or change the payout methods + available to you. Fees and minimum thresholds may change from time to time in our sole + discretion. +

+

Taxes and Information Reporting

+

+ Amounts that you earn as a participant in a rewards or cashback program do not include any + taxes, levies, duties or similar governmental assessments of any nature, including, for + example, value-added, sales, use or withholding taxes, assessable by any jurisdiction + (collectively, “taxes”). It is your responsibility to determine what, if any, taxes apply to + the payouts you receive, and it is solely your responsibility to assess, collect, report and + remit the correct taxes to the appropriate authority. Impact.com is not responsible for + determining whether any taxes apply to an action, or for calculating, collecting, reporting + or remitting taxes arising from any action. You acknowledge that we may make certain reports + to state and federal tax authorities regarding actions (payouts) that you receive pursuant + to your participation in rewards and cashback programs. For example, impact.com is required + to report to the U.S. Internal Revenue Service the total amount of payments that you receive + each calendar year via impact.com and that are associated with the same tax identification + number once you receive at least $600 in payments. We may also have reporting requirements + to applicable state and local governments. We will send you any necessary forms related to + your payouts and also transmit them to the relevant tax authority. +

+

Taxpayer Identification Number and Withholding Tax

+

+ We may request that you provide your tax identification number and/or a US tax form such as + W-9 or W-8. If you do not provide us the requested information and documentation, you + understand and agree that you may be subject to account limitations and federal and state + withholding tax at the applicable rates on all US source income payments received. We will + send all withholding taxes to the appropriate taxing authorities and cannot refund those + amounts. +

+

Charitable Donations

+

+ impact.com may enable you the ability to elect charitable organizations which can receive + donations of amounts earned by you as a participant in a rewards or cashback program that + enables charity donations. You agree that we may provide your information to your elected + charitable organizations and provide them with amounts donated by you from your impact.com + Account. impact.com disclaims all liability for charitable organizations, their compliance + with laws, rules and regulations, and how they use the funds that you donate to them. +

+

Customer Service & Disputes

+

+ If you have a dispute or complaint about your impact.com Account, please visit our Help Center. You may deactivate your impact.com Account + through + the + Help Center. +

+

Modifications, Suspensions, and Cancellation

+

+ We reserve the right to modify and/or discontinue the impact.com services, in whole or in + part, at any time, or to place conditions on your use of your impact.com Account. Except as + prohibited by applicable law, we may block, restrict, suspend, or terminate your use of your + impact.com Account at any time upon notice. impact.com reserves the right to do so for any + breach or violation of this Agreement or any other terms and agreements between you and + impact.com. You agree that we will not be liable to you or any third-party for any + modifications or discontinuance to the impact.com services, and/or blocking, suspending, + canceling, or terminating your use of your impact.com Account. +

+

Restricted Activities

+

+ In connection with your use of the impact.com services, impact.com Account, an impact.com + website, you must not (and you are responsible for anyone that you allow to access or use + any of the preceding): +

+
    +
  • Breach this Agreement or any other agreement between you and us;
  • +
  • Violate any law, statute, ordinance, or regulation;
  • +
  • + Infringe our or any third party's copyright, patent, trademark, trade secret or other + intellectual property rights, or rights of publicity or privacy; +
  • +
  • + Create or control more than one impact.com Account for yourself without our express + authorization, through, among other methods, using a name that is not yours, using a + temporary email address or phone number, or providing any other falsified personal + information; +
  • +
  • + Act in a manner that is defamatory, trade libelous, threatening or harassing our + employees, agents, or other users; +
  • +
  • Provide false, inaccurate or misleading information;
  • +
  • + Refuse to cooperate in an investigation or provide confirmation of your identity or any + information you provide to us; +
  • +
  • + Attempt to double dip during the course of a dispute by receiving or attempting to + receive money from both us and the rewards or cashback program or a bank or other + financial institution for the same transaction; +
  • +
  • + Control an Account that is linked to another impact.com Account that has engaged in any + of these restricted activities; +
  • +
  • Use the impact.com services in a manner that results in or may result in: +
      +
    • complaints.
    • +
    • + Disputes, reversals, chargebacks due to your non-compliance as a participant, + including but not limited to failure to comply with this User Agreement, +
    • +
    • + fees, fines, penalties or other liability or losses to impact.com, other + participants, third parties or you; +
    • +
    +
  • +
  • Owe any amounts to us;
  • +
  • Access the impact.com services where illegal to do so*;
  • +
  • + Take any action that imposes an unreasonable or disproportionately large load on our + websites, software, systems (including any networks and servers used to provide any of + the impact.com services) operated by us or on our behalf or the impact.com services; +
  • +
  • + Facilitate any viruses, trojan horses, malware, worms or other computer programming + routines that attempts to or may damage, disrupt, corrupt, misuse, detrimentally + interfere with, surreptitiously intercept or expropriate, or gain unauthorized access to + any system, data, information or Venmo services; +
  • +
  • + Interfere or disrupt or attempt to interfere with, copy, spider or disrupt our websites, + software, systems (including any networks and servers used to provide any of the + impact.com services) operated by us or on our behalf, any of the impact.com services or + other users’ use of any of the impact.com services; +
  • +
  • + Circumvent any of our policies or determinations about your impact.com Account such as + temporary or indefinite suspensions or other account holds, limitations or restrictions, + including, but not limited to, engaging in the following actions: attempting to open new + or additional impact.com Account(s) when you have amounts owed to us or when your + impact.com Account has been restricted, suspended or otherwise limited; opening new or + additional impact.com Accounts using information that is not your own (e.g. name, + address, email address, etc.); or using someone else’s impact.com account; or +
  • +
  • Harass and/or threaten.
  • +
+

Actions We May Take if You Engage in Any Restricted Activities

+

+ If we believe that you have engaged in any restricted activities, we may take a number of + actions to protect ourselves and others at any time at our sole discretion. The actions we + may take include, but are not limited to, the following: +

+
    +
  • + Terminate this Agreement and/or close or suspend your impact.com Account, immediately + and without penalty to us; +
  • +
  • Refuse to provide the impact.com services to you in the future;
  • +
  • + Limit your access to our websites, software, systems (including any networks and servers + used to provide any of the impact.com services) operated by us or on our behalf, your + impact.com account or any of impact.com services; +
  • +
  • Update inaccurate information provided to us; or
  • +
  • Take legal action against you.
  • +
+

+ If we close your impact.com Account or terminate your use of the impact.com services for any + reason, we’ll provide you with notice of our actions. You are responsible for all reversals, + chargebacks, claims, fees, fines, penalties, and other liability incurred by us, any rewards + or cashback program, or a third party caused by or arising out of your breach of this + agreement, and/or your use of the impact.com services or use of your impact.com by anyone + that you allow to do so. +

+

Holds and Limitations

+

What are holds and limitations

+

+ Under certain circumstances, in order to protect impact.com and the security and integrity + of our technology and services, impact.com may, in its sole discretion, take Account-level + or individual transaction actions. A hold may be placed on your impact.com account, which + temporarily makes earned money unavailable to you and prevents you from earning more during + such a hold period. +

+

+ Our decision about holds may be based on confidential criteria that are essential to our + management of risk and the protection of impact.com, or we may be restricted by regulation + or a governmental authority from disclosing certain information to you about such decisions. + We have no obligation to disclose the details of our risk management or security procedures + to you. +

+

+ impact.com will use commercially reasonable efforts to resolve temporary holds as + expeditiously as possible. +

+

Court Orders, Regulatory Requirements or Other Legal Process

+

+ If we are notified of a court order or other legal process (including garnishment or any + equivalent process) affecting you, or if we otherwise believe we are required to do so in + order to comply with applicable law or regulatory requirements, we may be required to take + certain actions, including holding payments to/from your impact.com Account, placing a hold + on your impact.com Account, or releasing funds. We will decide, in our sole discretion, + which action is required of us. We do not have an obligation to contest or appeal any court + order or legal process involving you or your impact.com account. When we implement a hold or + limitation as a result of a court order, applicable law, regulatory requirement or other + legal process, the hold or limitation may remain in place as long as we determine that we + are required or that it is necessary to do so. +

+

Error Resolutions

+

+ If we are notified of a court order or other legal process (including garnishment or any + equivalent process) affecting you, or if we otherwise believe we are required to do so in + order to comply with applicable law or regulatory requirements, we may be required to take + certain actions, including holding payments to/from your impact.com Account, placing a hold + on your impact.com Account, or releasing funds. We will decide, in our sole discretion, + which action is required of us. We do not have an obligation to contest or appeal any court + order or legal process involving you or your impact.com account. When we implement a hold or + limitation as a result of a court order, applicable law, regulatory requirement or other + legal process, the hold or limitation may remain in place as long as we determine that we + are required or that it is necessary to do so. +

+

What is an error

+

An “error” means the following:

+
    +
  • An incorrect amount is credited to your impact.com Account.
  • +
  • An incorrect amount is paid out from your impact.com Account.
  • +
  • + A reward or cashback transaction is missing from or not properly identified in your + impact.com Account. +
  • +
  • We make a computational or mathematical error related to your impact.com Account.
  • +
+

What is not considered an error

+

The following are NOT considered errors:

+
    +
  • + If you give someone authority to access your impact.com Account (for example, by giving + them your login information) and they exceed their authority. You are responsible for + any actions perpetrated through your impact.com Account. +
  • +
  • + Invalidation and reversal of a payment or pending amounts as a result of the actions + that are in violation of this Agreement or other agreement with impact.com. +
  • +
  • + Routine inquiries about the money in your Account, or about your impact.com Account or + impact.com services in general, or the status of a pending action or payout from your + impact.com Account, unless you expressly notify us of an error concerning your Account. +
  • +
+

In case of errors or questions about your rewards or cashback

+

+ Contact us through the Help Center; + or write us at Impact Tech, Inc., Attn: Error Resolution, 223 E. De La Guerra Street, + Santa Barbara, CA 93101. +

+

+ Notify us as soon as you can if you discover an error. We must hear from you no later than + 60 days after payment was made to you or, if no payment was made, when payment should have + been received by you. +

+

+ We will determine whether an error occurred within 10 business days after we hear from you + and will correct any error promptly. If we need more time, however, we may take up to 45 + days to investigate your complaint or question. Such a timeframe may be extended by up to 90 + days to investigate your complaint or question if impact.com is required to coordinate with + a third party in relation to such complaint or question. +

+

We will inform you after completing our investigation, and:

+
    +
  • + If we determine that there was an error and that you are owed rewards or cashback, we + will promptly credit your Account with such an amount. +
  • +
  • + If we decide that there was no error, we will provide you with a written explanation + through notification within your impact.com Account. +
  • +
+

+ If we discover an error, we will remedy such error promptly after we conclude that an error + has been made. If the error results in you owing money, we may satisfy such outstanding + amounts from amounts due to be paid to you from your impact.com Account, and if inadequate + to completely satisfy such outstanding amounts you will be invoiced for the balance owed, + with payment due net thirty (30) days from the date of such invoice. +

+ +

Communications Between You and Us

+

+ By providing us your email address and/or opting in to receive text messages from us on the + mobile phone number that you provide to us (which you represent and warrant that you are the + owner of that mobile number), you agree that we, including our affiliates, may contact you + at that email address and number using automated, autodialed or prerecorded messages, calls + or text messages to: (i) service your impact.com Account, (ii) investigate or prevent fraud, + or (iii) collect a debt. We will not use automated, autodialed, or prerecorded messages, + calls, or texts to contact you for marketing purposes unless we receive your prior express + written consent. You may opt out of receiving autodialed or prerecorded message calls or + texts to the mobile phone number by replying STOP to a message from the phone number you + wish to be opted out from such messages or by contacting us through the + Help Center + ;. +

+

+

+ Message and data rates may apply to text messages. The frequency of messages may vary, and + standard telephone minute and text charges may apply. Neither we nor your phone carriers are + liable for delayed or undelivered messages. Please review our + Privacy Policy + in order to better understand our commitment to maintaining your privacy, as well as our use + and disclosure of your information. We may share the mobile phone number(s) you provide to + us with service providers with whom we contract to assist us with the activities listed + above, but we will not share your mobile phone number with third parties for their own + purposes without your consent. We may communicate with you about your impact.com Account and + the impact.com services electronically. You will be considered to have received a + communication from us, if it’s delivered electronically, which may be through notification + in your impact.com Account, 24 hours after the time we post it to our website or email it. +

+

+ Unless you’re communicating with us about a matter where we’ve specified another notice address, written notices + must be sent by postal mail to: Impact Tech, Inc., Attention: Legal Department, 223 E. De La Guerra Street, Santa + Barbara, California 93101. You understand and agree that, to the extent permitted by law, we may, without further + notice or warning, monitor or record telephone conversations you or anyone acting on your behalf regarding your + impact.com Account has with us or our agents for quality control and training purposes or for our own protection. + You acknowledge and understand that while your communications with us may be overheard, monitored, or recorded, + not + all telephone lines or calls may be recorded by us, and we do not guarantee that recordings of any particular + telephone calls will be retained or retrievable. +

+

Our Rights

+

Our suspension and termination rights

+

We, in our sole discretion, reserve the right to suspend or terminate this Agreement, your access to or use of + our + websites, software, systems (including any networks and servers used to provide any of the impact.com services) + operated by us or on our behalf or some or all of the impact.com services for any reason and at any time upon + notice + to you. If you are owed rewards or cashback, we will payout such rewards or cashback upon receipt of funds from + the + rewards or cashback program.

+

Amounts owed to us

+

+ We may deduct amounts owed to us, in whole or in part, from funds paid to us by rewards and cashback programs that + are to be applied towards your impact.com Account, whether off-set or deducted later. +

+

+ If you have more than one impact.com Account, including one that is not a rewards and cashback impact.com Account, + even if you have those accounts without our authorization and in breach of this Agreement, we may set off amounts + owed to us in one impact.com Account against money in or money sent to your other impact.com Account(s). +

+

+ In addition to the above, if you have an account with an affiliate of ours and have past due amounts owed to us, + we + may debit your accounts held at our affiliates to pay any amounts that are past due. +

+

No waiver

+

+ Our failure to act with respect to a breach of any of your obligations under this Agreement by you or others does + not waive our right to act with respect to subsequent or similar breaches. +

+

Indemnification and Limitation of Liability

+

+ In this section, we use the term “impact.com” to refer to Impact Tech, Inc., our parent Impact Holdings, Inc., and + our affiliates, and each of their respective directors, officers, employees, agents, joint venturers, service + providers and suppliers. Our affiliates include each entity that we control, we are controlled by or we are under + common control with. +

+

Indemnification

+

+ You must indemnify impact.com for actions related to your impact.com account and your use of, and anyone that + you authorize to use, the impact.com services. You agree to defend, indemnify and hold impact.com harmless + from any claim or demand (including reasonable legal fees) made or incurred by any third party due to or arising + out + of your breach of this Agreement, your improper use of the impact.com services, your violation of any law or the + rights of a third party and/or the actions or inactions of any third party to whom you grant permissions to use + your + impact.com Account or access our websites, software, systems (including any networks and servers used to provide + any + of the impact.com services) operated by us or on our behalf, or any of the impact.com services on your behalf. +

+

Limitation of Liability

+

+ Impact.com’s liability is limited with respect to your impact.com Account and your use of the impact.com + services. In no event shall impact.com be liable for lost profits or any special, incidental or + consequential + damages (including without limitation damages for loss of data or loss of business) arising out of or in + connection + with our websites, software, systems (including any networks and servers used to provide any of the impact.com + services) operated by us or on our behalf, any of the impact.com services, or this Agreement (however arising, + including negligence), unless and to the extent prohibited by law. +

+

+ Our liability to you or any third parties in all circumstances is limited to the actual amount of direct damages. + In + addition, to the extent permitted by applicable law, impact.com is not liable, and you agree not to hold + impact.com + responsible, for any damages or losses (including, but not limited to, loss of money, goodwill, or reputation, + profits, or other intangible losses or any special, indirect, or consequential damages) resulting directly or + indirectly from: (1) your use of, or inability to use, our websites, software, systems (including any networks and + servers used to provide any of the impact.com services) operated by us or on our behalf, or any of the impact.com + services; (2) delays or disruptions in our websites, software, systems (including any networks and servers used to + provide any of the impact.com services) operated by us or on our behalf and any of the impact.com services; (3) + viruses or other malicious software obtained by accessing our websites, software, systems (including any networks + and servers used to provide any of the impact.com services) operated by us or on our behalf or any of the + impact.com + services or any website or service linked to our websites, software or any of the impact.com services; (4) + glitches, + bugs, errors, or inaccuracies of any kind in our websites, software, systems (including any networks and servers + used to provide any of the impact.com services) operated by us or on our behalf or any of the impact.com services + or + in the information and graphics obtained from them; (5) the content, actions, or inactions of third parties; or + (6) + a suspension or other action taken with respect to your impact.com Account. +

+

Disclaimer of Warranty and Release

+

No warranty

+

+ + The impact.com services are provided “as-is” and without any representation or warranty, whether express, + implied + or statutory. We specifically disclaim any implied warranties of title, merchantability, fitness for a + particular + purpose and non-infringement. + +

+

+ We do not have any control over the rewards and cashback programs for which the impact.com services are used. + Additionally, we cannot ensure that the rewards and cashback programs an impact.com user is dealing with will + actually fund amounts earned by participants. If we are not paid by the rewards and cashback programs, then + impact.com does not advance funds and you will not be paid. We do not guarantee continuous, uninterrupted, or + secure + access to any part of the impact.com services, and operation of our websites, software, or systems (including any + networks and servers used to provide any of the impact.com services) operated by us or on our behalf may be + interfered with by numerous factors outside of our control. We will make reasonable efforts to request that the + rewards and cashback programs fund amounts owed that have been earned by participants. If your jurisdiction does + not + allow the disclaimer of implied warranties, then the foregoing disclaimers of implied warranties do not apply. +

+

+ +

+ + Your Release of Us + +

+

+ If you have a dispute with a rewards and cashback program, you release us from any and all claims, demands and + damages (actual and consequential) of every kind and nature, known and unknown, arising out of or in any way + connected with such disputes. In entering into this release, you expressly waive any protections (whether + statutory + or otherwise, for example, pursuant to California Civil Code § 1542) that would otherwise limit the coverage of + this + release to include only those claims which you may know or suspect to exist in your favor at the time of agreeing + to + this release. +

+

+ + Dispute Resolution + +

+

+ You and impact.com agree that any claim or dispute at law or equity that has arisen or may arise between you and + us + (including claims or disputes that arise out of or relate to the impact.com services or your impact.com Account) + will be resolved in accordance with the Agreement to Arbitrate provisions set forth below, which: +

+
    +
  • + affects your and our rights and will impact how claims between you and us are resolved, including your and our + agreement to waive the right to trial by jury. +
  • +
  • + includes a Prohibition of Class and Representative Actions and Non-Individualized Relief pursuant to + which you agree to waive your right to participate in a class action lawsuit against us. +
  • +
  • + requires you to mail us a written notice to opt-out of the Agreement to Arbitrate, postmarked within 30 days + after + you 1st accept this User Agreement. +
  • +
+

+ Notice to opt-out of the Agreement to Arbitrate must be mailed via certified mail return receipt requested to - + Impact Tech, Inc., Attn: Legal Department, Santa Barbara, California 93101. Your signed notice must include your + name, address, phone number, impact.com username, and the email address(es) used to log in to the impact.com + Account(s) to which the opt-out applies. This procedure is the only way you can opt-out of the Agreement to + Arbitrate. If you opt-out of this Agreement to Arbitrate, all other parts of the Agreement will continue to apply. + Opting out of the Agreement to Arbitrate has no effect on any previous, other, or future arbitration agreements + that + you may have with us. +

+

+ Prior to submitting for arbitration, disputes between you and impact.com regarding the impact.com services may be + reported to customer service online through the Help Center at any time. The parties agree that all + preliminary + meetings and/or hearings may be conducted virtually, unless prohibited by applicable law. +

+

Agreement to Arbitrate

+

+ You and impact.com each agree that any and all disputes or claims that have arisen or may arise between you and + impact.com (including claims or disputes that arise out of or relate to the impact.com services or your impact.com + account), including without limitation federal and state statutory claims, common law claims, and those based in + contract, tort, fraud, misrepresentation or any other legal theory, shall be resolved exclusively through final + and + binding arbitration. You agree that arbitration will be on an individual (non-class, non-representative) basis. + This + Agreement to Arbitrate is intended to be broadly interpreted. +

+

+ For residents of the US and Canada, The US Federal Arbitration Act governs the interpretation and enforcement of + this Agreement to Arbitrate. For European Economic Area residents, the Directive (2013/11/EU) on alternative + dispute + resolution for consumer disputes (the 'ADR Directive') and Regulation 524/2013 on online dispute resolution for + consumer disputes (the ODR Regulation) apply. For residents of the UK, the Arbitration Act 1996 (as may be amended + or its successor legislation) applies. For residents of Japan, Arbitration Law No. 138 of 2003 (as may be amended + or + its successor legislation) applies. For residents of Australia and New Zealand, the International Arbitration Act + 1974 (Cth) (as may be amended or its successor legislation) applies. +

+

+ Before bringing any dispute or claim, you or impact.com must first send the other party, by certified mail, a + notice + of dispute. If to impact.com: Impact Tech, Inc., Attn: Legal Department, Santa Barbara, California 93101. + impact.com + will send any notice to you to the address and/or email address we have on file associated with your impact.com + Account. It is your responsibility to keep your address up to date. The notice is to include a description of the + nature and basis of the claims that are being asserted, a statement of the specific relief sought, and any + relevant + documents and supporting information reasonably available to the claiming party. +

+

+ If you and impact.com are unable to resolve the claims described in the notice within 45 days after the notice is + sent, you or impact.com may commence an arbitration proceeding. +

+

+ For residents in the EEA, you are to use the online process through the + European + Commission’s Online Dispute Resolution (ODR) via https://ec.europa.eu/consumers/odr and if not resolved within 90 + days of process initiation then arbitration through the Extrajudicial Dispute Resolution Body for Consumers and + Companies (Außergerichtliche Streitbeilegungsstelle für Verbraucher und Unternehmer e. V.) (as such a process may + be + amended or through any applicable successor body and/or legislation). +

+

For residents in the UK, any dispute arising out of or in connection with + this + Agreement, including any question regarding its existence, validity or termination, shall be referred to and + finally + resolved by arbitration through the Consumer Dispute Resolution Limited, pursuant to their rules which rules are + deemed to be incorporated by reference into this clause. The number of arbitrators shall be one, the seat, or + legal + place, of arbitration shall be London, England, and the language to be used in the arbitral proceedings shall be + English. The governing law of the contract shall be the substantive law of England and Wales. You are to initiate + a + claim through Consumer Dispute Resolution Ltd (Company No. 09189773 England and Wales), registered office address: + Unit 12 Walker Avenue, Wolverton Mill, Milton Keynes, Buckinghamshire, England, MK12 5TW, via their online process + at https://consumerarbitration.co.uk/ + (although + a paper form, offline process is available as well) and pay them a nominal fee with your submission. +

+

+ For residents of Japan, all disputes, controversies or differences arising out + of + or in connection with this contract shall be finally settled by arbitration in accordance with the Interactive + Arbitration Rules of The Japan Commercial Arbitration Association. The place of the arbitration shall be + Tokyo, + Japan. The matter shall be resolved by 1 arbitrator and the language of the arbitration shall be English. To + initiate a the process, you are to contact the JCAA Tokyo Head Office, via email at: arbitration@jcaa.or.jp; or by mail: Japan Commercial Arbitration + Association, Arbitration & Mediation Department, 3F, Hirose Bldg, 3-17, Kanda Nishiki-cho, Chiyoda-ku, + Tokyo + 101-0054, Japan TEL: +81-3-5280-5161. +

+

+ For residents of Australia and New Zealand, any dispute, controversy or + claim + arising out of, relating to or in connection with this contract, including any questions regarding its + existence, validity or termination, shall be resolved by arbitration of one arbitrator through the + Australian Centre for International Commercial Arbitration Limited (“ACICA”, ACN 006 404 664) in + accordance + with the ACICA Expedited Arbitration Rules. The seat of arbitration shall be Sydney, Australia. The + language + of the arbitration shall be English.The parties agree that any preliminary matters and/or hearings may + be + held virtually. To initiate arbitration, register your arbitration with the ACICA and pay the relevant + filing fee at https://acica.org.au/acica-connect/. If you have + questions, + contact the ACICA Secretariat via email at secretariat@acica.org.au, or by phone at +612 9223 1099. +

+

+ You and impact.com agree that each of us may bring claims against the other only on an individual basis + and + not as a plaintiff or class member in any purported class or representative action or proceeding. Unless + both you and impact.com agree otherwise, the arbitrator may not consolidate or join more than one + person's + or party's claims and may not otherwise preside over any form of a consolidated, representative or class + proceeding. Also, the arbitrator may award relief (including monetary, injunctive and declaratory + relief) + only in favor of the individual party seeking relief and only to the extent necessary to provide relief + necessitated by that party's individual claim(s). Any relief awarded cannot affect other impact.com + entities + or impact.com customers. +

+

+ For residents in the US and Canada, any arbitration will be + administered + by the American Arbitration Association (referred to as the "AAA"). A form for initiating arbitration + proceedings is available on the AAA's website at www.adr.org.The + parties agree that the following provisions shall apply: +

+
    +
  • + For all claims, the Consumer Arbitration Rules in effect at the time the arbitration is commenced, as + applicable, and as modified by this Agreement to Arbitrate, shall apply. The AAA's rules are available + at + www.adr.org. In the event that the AAA is unavailable or unwilling to administer the arbitration + consistent with this Agreement to Arbitrate, another administrator will be selected by the parties or + by + the court. +
  • +
  • + Any arbitration hearings shall be held in the county in which you reside or at another mutually agreed + location. If the value of the relief sought is $25,000 or less, the arbitration will be conducted + based + solely on written submissions, unless any party requests an in-person, telephonic, or videoconference + hearing or the arbitrator decides that a hearing is necessary. In cases where an in-person hearing is + held, you and/or impact.com may attend by telephone or videoconference, unless the arbitrator requires + otherwise. +
  • +
  • + The arbitrator will decide the substance of all claims in accordance with applicable law, including + recognized principles of equity, and will honor all claims of privilege recognized by law. No court or + arbitrator shall be bound by rulings in prior arbitrations involving different impact.com or + impact.com + customers, but a court or arbitrator will be bound by rulings in prior arbitrations involving the same + impact.com or impact.com customer to the extent required by applicable law. The award of the + arbitrator + shall be final and binding, and judgment on the award rendered by the arbitrator may be entered in any + court having jurisdiction thereof. +
  • +
  • + Payment of all AAA or arbitrator fees will be governed by the AAA's rules, unless otherwise stated in + this + Agreement to Arbitrate. In the event the arbitrator determines that either the substance of your or + impact.com’s claim or the relief sought was frivolous or brought for an improper purpose, then you or + impact.com may seek to recover from you or impact.com any fees it paid, including attorneys’ fees, to + the + extent permitted by the AAA’s rules and applicable law. +
  • +
+

+ With the exception of any of the provisions regarding prohibiting class and representative actions, if a + court decides that any part of this Agreement to Arbitrate is invalid or unenforceable, the other parts + of + this Agreement to Arbitrate shall still apply. If a court decides that any of the provisions regarding + prohibiting class and representative actions are invalid or unenforceable because it would prevent the + exercise of a non-waivable right (such as the right to pursue public injunctive relief), then any + dispute + regarding the entitlement to such relief (and only that relief) must be severed from arbitration and may + be + litigated in court. All other disputes subject to arbitration under the terms of the Agreement to + Arbitrate + shall be arbitrated under its terms. +

+

+ Notwithstanding any provision in this Agreement to the contrary, you and we agree that if we make any + amendment to this Agreement to Arbitrate (other than an amendment to any notice address or website link + provided herein) in the future, that amendment shall not apply to any claim that was filed in a legal + proceeding against impact.com or you prior to the effective date of the amendment. The amendment shall + apply + to all other disputes or claims governed by this Agreement to Arbitrate that have arisen or may arise + between you and impact.com. We will notify you of amendments to this Agreement to Arbitrate by providing + notice through email at least 30 days before the effective date of the amendments. If you do not agree + to + these amended terms, you may close your impact.com account within the 30-day period and you will not be + bound by the amended terms. +

+

Disclaimer for Program Content

+

+ Content provided to you by rewards and cashback programs is not the responsibility of impact.com. We are + not + responsible for evaluating the accuracy, truthfulness, usefulness, legality, safety, morality or + applicability of any content provided to you pursuant to your participation in rewards and cashback + programs. You are solely responsible for all content that you use to participate in rewards and cashback + programs. impact.com does not endorse, guarantee, make representations or provide warranties regarding + any + such content. +

+

Miscellaneous

+

Assignment

+

+ You may not transfer or assign any rights or obligations you have under this Agreement without our prior + written consent. We may transfer or assign this Agreement or any right or obligation under this + Agreement + at + any time. +

+

Dormant accounts

+

+ If you do not log in to your personal account for at least six (6) months, we may close your impact.com + Account and send any of your funds held in our possession to your primary address (if we have verified + the + required identifying information that you have provided to us) or, if required, escheat (send) any of + your + funds held in our possession to your state of residency or otherwise in accordance with applicable laws. + We + will determine your state of residency based on the information provided for your impact.com Account. If + your address is unknown, any of your funds held in our possession will be escheated to the State of + California. Where required, we will send you a notice prior to escheating any of your funds. If you fail + to + respond to this notice, your funds held in our possession will be escheated to the applicable state. If + you + would like to claim any escheated funds from the applicable state, please contact the applicable state’s + unclaimed property administrator. +

+

Governing Law

+

US and Canada

+

+ If you reside in the United States or Canada or any other jurisdiction excluding those covered by a + governing law provision below, you agree that except to the extent inconsistent with or preempted by + local + law or federal law and except as otherwise stated in this Agreement, the laws of the State of + California, + without regard to principles of conflict of laws, will govern this Agreement and any claim or dispute + that + has arisen or may arise between you and impact.com regarding your impact.com Account and your use of the + impact.com services or interpretation of this Agreement. +

+

Europe

+

+ If you reside in the European Economic Area, you agree that except to the extent inconsistent with or + preempted by local law and except as otherwise stated in this Agreement, the laws of Germany, without + regard + to principles of conflict of laws, will govern this Agreement and any claim or dispute that has arisen + or + may arise between you and impact.com regarding your impact.com Account and your use of the impact.com + services or interpretation of this Agreement. +

+

United Kingdom

+

+ If you reside in the United Kingdom, you agree that except to the extent inconsistent with or preempted + by + local law and except as otherwise stated in this Agreement, the laws of England and Wales, without + regard + to + principles of conflict of laws, will govern this Agreement and any claim or dispute that has arisen or + may + arise between you and impact.com regarding your impact.com Account and your use of the impact.com + services + or interpretation of this Agreement. +

+

Japan

+

+ If you reside in Japan, you agree that except to the extent inconsistent with or preempted by local law + and + except as otherwise stated in this Agreement, the laws of Japan, without regard to principles of + conflict + of + laws, will govern this Agreement and any claim or dispute that has arisen or may arise between you and + impact.com regarding your impact.com Account and your use of the impact.com services or interpretation + of + this Agreement. +

+

Australia and New Zealand

+

+ If you reside in Australia, you agree that except to the extent inconsistent with or preempted by local + law + and except as otherwise stated in this Agreement, the laws of Australia, without regard to principles of + conflict of laws, will govern this Agreement and any claim or dispute that has arisen or may arise + between + you and impact.com regarding your impact.com Account and your use of the impact.com services or + interpretation of this Agreement. +

+

Identity authentication

+

+ You authorize us, directly or through third parties, to make any inquiries we consider necessary to + verify + your identity. This may include: +

+
    +
  • + asking for further information, such as date of birth, a social security or taxpayer identification + number, physical address and other information that will allow us to reasonably identify you; +
  • +
  • + requiring steps to confirm ownership of an email address, phone number or financial account; +
  • +
  • + verifying your information against third party databases or through other sources; or +
  • +
  • + requiring you to provide a driver’s license or other identifying documents. +
  • +
+

+ Anti-money laundering and counter-terrorism financing laws may require that we verify the required + identifying information of you. We reserve the right to close, suspend, or limit access to your + impact.com + Account and/or the impact.com services in the event that, after reasonable enquiries, we are unable to + obtain information required to verify your identity or otherwise clear you. +

+

Impact.com is only a technology platform service provider

+

+ We act as a technology platform only, and not as an agent, trustee, guarantor, partner, joint venture, + or + employer of you or any person or entity that utilizes our software-as-a-service (SaaS) services. We do + not: + determine if you are liable for any taxes, or, unless otherwise expressly set out in this Agreement, + collect + or pay any taxes that may arise from use of our services. +

+

Privacy

+

+ Protecting your privacy is very important to us. Please review our Privacy Policy. in order to better understand our + commitment to maintaining account holder privacy, as well as our use and disclosure of account holder + information. +

+

Third party providers

+

The impact.com Services are provided in conjunction with third-party rewards and cashback programs. You + agree that: (1) your participation in such programs is subject to the terms that you agree with such + third + parties, (2) we may provide information and data about your impact.com Account and use of the impact.com + services to such parties, and (3) contact information that you provide to impact.com for the creation of + your impact.com Account may be shared with such third parties and that the third party’s privacy policy + shall govern such third party’s receipt of your contact information and Account data. We are not a party + to + your agreements with such third parties and we have no responsibility for the products and services + provided + by those third parties. You acknowledge and agree that this agreement is between you and impact.com, not + with any such third party. You acknowledge and agree that we are solely responsible for the impact.com + services and for providing maintenance and support services for the impact.com services.

+

[end of Agreement]

+ + +
+ + + \ No newline at end of file diff --git a/packages/mint-components/tsconfig.json b/packages/mint-components/tsconfig.json index 232ba0dbb6..e3c1f9f4ef 100644 --- a/packages/mint-components/tsconfig.json +++ b/packages/mint-components/tsconfig.json @@ -5,6 +5,8 @@ "declaration": false, "experimentalDecorators": true, "strict":false, + // Skipped checking the installed libraries for now because puppeteer keeps bugging out + "skipLibCheck": true, "lib": [ "dom", "es2017",