diff --git a/package-lock.json b/package-lock.json index f5caf0c..383d54d 100755 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "react-circular-progressbar": "^2.1.0", "react-dom": "^18.2.0", "react-ga4": "^2.1.0", + "react-helmet": "^6.1.0", "react-i18next": "^12.3.1", "react-pdf": "8.0.2", "react-player": "^2.16.0", @@ -44,6 +45,7 @@ "@types/node": "^20.5.1", "@types/react": "^18.2.6", "@types/react-dom": "^18.2.4", + "@types/react-helmet": "^6.1.11", "@types/react-router-dom": "^5.3.3", "@types/react-slick": "^0.23.13", "@typescript-eslint/eslint-plugin": "^5.59.6", @@ -1563,6 +1565,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-helmet": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.11.tgz", + "integrity": "sha512-0QcdGLddTERotCXo3VFlUSWO3ztraw8nZ6e3zJSgG7apwV5xt+pJUS8ewPBqT4NYB1optGLprNQzFleIY84u/g==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-router": { "version": "5.1.20", "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", @@ -4080,6 +4091,20 @@ "resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz", "integrity": "sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ==" }, + "node_modules/react-helmet": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", + "integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==", + "dependencies": { + "object-assign": "^4.1.1", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.1.1", + "react-side-effect": "^2.1.0" + }, + "peerDependencies": { + "react": ">=16.3.0" + } + }, "node_modules/react-i18next": { "version": "12.3.1", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.3.1.tgz", @@ -4231,6 +4256,14 @@ "react-dom": ">=16.8" } }, + "node_modules/react-side-effect": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz", + "integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==", + "peerDependencies": { + "react": "^16.3.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-slick": { "version": "0.30.2", "resolved": "https://registry.npmjs.org/react-slick/-/react-slick-0.30.2.tgz", @@ -5967,6 +6000,15 @@ "@types/react": "*" } }, + "@types/react-helmet": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.11.tgz", + "integrity": "sha512-0QcdGLddTERotCXo3VFlUSWO3ztraw8nZ6e3zJSgG7apwV5xt+pJUS8ewPBqT4NYB1optGLprNQzFleIY84u/g==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/react-router": { "version": "5.1.20", "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", @@ -7769,6 +7811,17 @@ "resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz", "integrity": "sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ==" }, + "react-helmet": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", + "integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==", + "requires": { + "object-assign": "^4.1.1", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.1.1", + "react-side-effect": "^2.1.0" + } + }, "react-i18next": { "version": "12.3.1", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.3.1.tgz", @@ -7851,6 +7904,12 @@ "react-router": "6.11.2" } }, + "react-side-effect": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz", + "integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==", + "requires": {} + }, "react-slick": { "version": "0.30.2", "resolved": "https://registry.npmjs.org/react-slick/-/react-slick-0.30.2.tgz", diff --git a/package.json b/package.json index ff002c6..8e98bcf 100755 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "react-circular-progressbar": "^2.1.0", "react-dom": "^18.2.0", "react-ga4": "^2.1.0", + "react-helmet": "^6.1.0", "react-i18next": "^12.3.1", "react-pdf": "8.0.2", "react-player": "^2.16.0", @@ -50,6 +51,7 @@ "@types/node": "^20.5.1", "@types/react": "^18.2.6", "@types/react-dom": "^18.2.4", + "@types/react-helmet": "^6.1.11", "@types/react-router-dom": "^5.3.3", "@types/react-slick": "^0.23.13", "@typescript-eslint/eslint-plugin": "^5.59.6", diff --git a/src/api/resources/User.ts b/src/api/resources/User.ts index b3252b8..7010679 100644 --- a/src/api/resources/User.ts +++ b/src/api/resources/User.ts @@ -181,6 +181,7 @@ export interface ICreateAuthorizeResponse { userId?: string; generatingVideo?: boolean; videoId?: string; + authCode?: string; } export const createAuthorizeRequest = (data: ICreateAuthorizePayload): Request => { diff --git a/src/components/pages/ABDesign/v1/pages/Onboarding/index.tsx b/src/components/pages/ABDesign/v1/pages/Onboarding/index.tsx index e2f2a0d..e5d93c2 100644 --- a/src/components/pages/ABDesign/v1/pages/Onboarding/index.tsx +++ b/src/components/pages/ABDesign/v1/pages/Onboarding/index.tsx @@ -25,10 +25,14 @@ function OnboardingPage() { const { createdDate, generatingVideo } = useSelector( selectors.selectPersonalVideo ); + const authCode = useSelector(selectors.selectAuthCode); const handleNext = useCallback(() => { - navigate(routes.client.trialChoiceV1()); - }, [navigate]); + if (authCode?.length) { + return navigate(routes.client.tryAppV1()); + } + return navigate(routes.client.trialChoiceV1()); + }, [authCode, navigate]); useEffect(() => { if (isVideoReady && progress >= 100) { diff --git a/src/components/pages/ABDesign/v1/pages/TryApp/components/AccessCodeApp/index.tsx b/src/components/pages/ABDesign/v1/pages/TryApp/components/AccessCodeApp/index.tsx new file mode 100644 index 0000000..2bb8ad6 --- /dev/null +++ b/src/components/pages/ABDesign/v1/pages/TryApp/components/AccessCodeApp/index.tsx @@ -0,0 +1,23 @@ +import Title from "@/components/Title"; +import styles from "./styles.module.css"; + +interface IAccessCodeAppProps { + code: string; +} + +function AccessCodeApp({ code }: IAccessCodeAppProps) { + return ( +
+
+ + Your access code + +
+
+ {code} +
+
+ ); +} + +export default AccessCodeApp; diff --git a/src/components/pages/ABDesign/v1/pages/TryApp/components/AccessCodeApp/styles.module.css b/src/components/pages/ABDesign/v1/pages/TryApp/components/AccessCodeApp/styles.module.css new file mode 100644 index 0000000..0718c9a --- /dev/null +++ b/src/components/pages/ABDesign/v1/pages/TryApp/components/AccessCodeApp/styles.module.css @@ -0,0 +1,42 @@ +.container { + position: relative; + margin-top: 32px; + border-radius: 24px; + min-height: 140px; + background-color: #fff; + justify-content: start; + width: 100%; +} + +.header-container { + width: 100%; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + background-color: rgb(153, 116, 246); + border-top-left-radius: 24px; + border-top-right-radius: 24px; +} + +.title { + text-transform: capitalize; + margin: 0; + font-size: 20px; + line-height: 145%; + color: #fff; + font-weight: 500; +} + +.code-container { + position: absolute; + top: calc(50% + 19px); + transform: translateY(-50%); + width: 100%; + text-align: center; +} + +.code { + font-size: 36px; + font-weight: 800; +} diff --git a/src/components/pages/ABDesign/v1/pages/TryApp/index.tsx b/src/components/pages/ABDesign/v1/pages/TryApp/index.tsx new file mode 100644 index 0000000..12a6a16 --- /dev/null +++ b/src/components/pages/ABDesign/v1/pages/TryApp/index.tsx @@ -0,0 +1,140 @@ +import { useEffect, useState } from "react"; +import TrialPaymentHeader from "../TrialPayment/components/Header"; +import Header from "../../components/Header"; +import PersonalInformation from "../TrialPayment/components/PersonalInformation"; +import WithPartnerInformation from "../TrialPayment/components/WithPartnerInformation"; +import styles from "./styles.module.css"; +import { useSelector } from "react-redux"; +import { selectors } from "@/store"; +import { getZodiacSignByDate } from "@/services/zodiac-sign"; +import Title from "@/components/Title"; +import Goal from "../TrialPayment/components/Goal"; +import Reviews from "../TrialPayment/components/Reviews"; +import { trialPaymentReviews } from "@/data/reviews"; +import AccessCodeApp from "./components/AccessCodeApp"; +import YourReading from "../TrialPayment/components/YourReading"; +import PointsList from "../TrialPayment/components/PointsList"; +import OftenAsk from "../TrialPayment/components/OftenAsk"; +import { trialPaymentPointsList } from "@/data/pointsLists"; +import BackgroundTopBlob from "../../ui/BackgroundTopBlob"; +import { useDynamicSize } from "@/hooks/useDynamicSize"; + +function TryAppPage() { + const { width: pageWidth, elementRef: pageRef } = useDynamicSize({}); + const birthdate = useSelector(selectors.selectBirthdate); + const zodiacSign = getZodiacSignByDate(birthdate); + const { + gender, + birthPlace, + partnerBirthPlace, + partnerBirthdate, + partnerGender, + goal, + flowChoice, + } = useSelector(selectors.selectQuestionnaire); + const partnerZodiacSign = getZodiacSignByDate(partnerBirthdate); + const [singleOrWithPartner, setSingleOrWithPartner] = useState< + "single" | "partner" + >("single"); + const authCode = useSelector(selectors.selectAuthCode); + + useEffect(() => { + if (["relationship", "married"].includes(flowChoice)) { + setSingleOrWithPartner("partner"); + return; + } + setSingleOrWithPartner("single"); + }, [flowChoice]); + + const downloadApp = () => { + // TODO + window.location.href = + "https://apps.apple.com/us/app/aura-astrology-horoscope/id1601978549"; + }; + + return ( +
+
+ +
+
+ + {singleOrWithPartner === "partner" && ( + + )} + {singleOrWithPartner === "single" && ( + + )} + + Your Personalized Clarity & Love Reading is ready and available in the + app for your iPhone! + + + + 1. Download App + Download on the app store + + 2. Enter Your Access Code + +
{authCode}
+

+ Enter your access code in the app to access Your Personalized Reading. + Do not share your code with anyone +

+ + + + Users love us + + + + +
+ ); +} + +export default TryAppPage; diff --git a/src/components/pages/ABDesign/v1/pages/TryApp/styles.module.css b/src/components/pages/ABDesign/v1/pages/TryApp/styles.module.css new file mode 100644 index 0000000..549a6f1 --- /dev/null +++ b/src/components/pages/ABDesign/v1/pages/TryApp/styles.module.css @@ -0,0 +1,93 @@ +.page { + background-color: #fff0f0; + height: fit-content; + min-height: 100dvh; + padding-top: 114px; + padding-bottom: 62px; + width: 100%; + max-width: 460px; + overflow: inherit; + overflow-x: clip; +} + +.header { + position: absolute; + z-index: 3; + top: 32px; + padding: 0 14px; + width: 100%; +} + +.background-top-blob-container { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: fit-content; + overflow: hidden; +} + +.background-top-blob { + scale: 1.4; +} + +.download-app-title { + font-size: 24px; + line-height: 145%; + color: #333333; + font-weight: 700; + text-align: center; + margin-top: 32px; + margin-bottom: 16px; +} + +.download-app-image { + width: 80%; + max-width: 270px; + height: 80px; + object-fit: cover; + animation: 1.5s ease 0s infinite normal none running pulse; +} + +.code-container { + font-size: 28px; + font-weight: 600; + padding: 12px; + width: 100%; + max-width: 200px; + background-color: rgba(175, 149, 241, 0.48); + border-radius: 48px; + text-align: center; +} + +.code-description { + text-align: center; + line-height: 1.3; + font-weight: 500; + font-size: 18px; + margin-top: 12px; +} + +.title { + margin-top: 24px; + font-size: 20px; + line-height: 145%; + color: #333333; + font-weight: 600; +} + +.header-button { + height: 100%; +} + +@keyframes pulse { + 0% { + transform: scale(0.9); + } + 70% { + transform: scale(1); + } + 100% { + transform: scale(0.9); + } +} diff --git a/src/hooks/authentication/use-authentication.ts b/src/hooks/authentication/use-authentication.ts index d0e92dc..d6fdf35 100644 --- a/src/hooks/authentication/use-authentication.ts +++ b/src/hooks/authentication/use-authentication.ts @@ -123,7 +123,7 @@ export const useAuthentication = () => { setIsLoading(true); setError(null) const payload = getAuthorizationPayload(email, source); - const { token, userId, generatingVideo, videoId } = await api.authorization(payload); + const { token, userId, generatingVideo, videoId, authCode } = await api.authorization(payload); const { user } = await api.getUser({ token }); if (userId?.length) { metricService.userParams({ @@ -135,7 +135,9 @@ export const useAuthentication = () => { } signUp(token, user); setToken(token); - + if (authCode?.length) { + dispatch(actions.userConfig.setAuthCode(authCode)); + } dispatch(actions.personalVideo.updateStatus({ generatingVideo: generatingVideo || false, videoId: videoId || "" })); if (generatingVideo) { metricService.reachGoal(EGoals.ROSE_VIDEO_CREATION_START) diff --git a/src/init.tsx b/src/init.tsx index dc1cf5f..587b079 100755 --- a/src/init.tsx +++ b/src/init.tsx @@ -13,6 +13,7 @@ import App from "./components/App"; import metricService from "./services/metric/metricService"; import "core-js/actual"; import { pdfjs } from "react-pdf"; +import MetaPixel from "./utils/FBMetaPixel"; pdfjs.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.js`; @@ -68,6 +69,7 @@ const init = async () => { return ( + diff --git a/src/routerComponents/ABDesign/v1/index.tsx b/src/routerComponents/ABDesign/v1/index.tsx index b940f70..0db1f57 100644 --- a/src/routerComponents/ABDesign/v1/index.tsx +++ b/src/routerComponents/ABDesign/v1/index.tsx @@ -33,6 +33,7 @@ import TrialPaymentPage from "@/components/pages/ABDesign/v1/pages/TrialPayment" import TrialPaymentWithDiscount from "@/components/pages/ABDesign/v1/pages/TrialPaymentWithDiscount"; import AdditionalDiscount from "@/components/pages/ABDesign/v1/pages/AdditionalDiscount"; import MentionedInPage from "@/components/pages/ABDesign/v1/pages/MentionedIn"; +import TryAppPage from "@/components/pages/ABDesign/v1/pages/TryApp"; function ABDesignV1Routes() { return ( @@ -137,6 +138,10 @@ function ABDesignV1Routes() { > } /> + } + /> } diff --git a/src/routes.ts b/src/routes.ts index 797a1c4..a3963ae 100755 --- a/src/routes.ts +++ b/src/routes.ts @@ -195,6 +195,7 @@ const routes = { onboardingV1: () => [host, "v1", "onboarding"].join("/"), trialChoiceV1: () => [host, "v1", "trial-choice"].join("/"), trialPaymentV1: () => [host, "v1", "trial-payment"].join("/"), + tryAppV1: () => [host, "v1", "try-app"].join("/"), trialPaymentWithDiscountV1: () => [host, "v1", "trial-payment-with-discount"].join("/"), additionalDiscountV1: () => [host, "v1", "additional-discount"].join("/"), diff --git a/src/store/index.ts b/src/store/index.ts index f1a088d..578074d 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -50,6 +50,7 @@ import userConfig, { selectUserDeviceType, selectIsShowTryApp, selectFeature, + selectAuthCode, selectIsForceShortPath, selectDateOfPaymentChatMike, } from "./userConfig"; @@ -125,6 +126,7 @@ export const selectors = { selectUserDeviceType, selectIsShowTryApp, selectFeature, + selectAuthCode, selectIsForceShortPath, selectDateOfPaymentChatMike, selectOpenAiToken, diff --git a/src/store/userConfig.ts b/src/store/userConfig.ts index 5d68a98..9e9f2c4 100644 --- a/src/store/userConfig.ts +++ b/src/store/userConfig.ts @@ -11,6 +11,7 @@ interface IUserConfig { isShowTryApp: boolean; isForceShortPath: boolean; feature: string; + authCode: string; dateOfPaymentChatMike: string; } @@ -19,6 +20,7 @@ const initialState: IUserConfig = { isShowTryApp: false, isForceShortPath: false, feature: "", + authCode: "", dateOfPaymentChatMike: "", }; @@ -45,6 +47,10 @@ const userConfigSlice = createSlice({ state.feature = action.payload; return state; }, + setAuthCode(state, action: PayloadAction) { + state.authCode = action.payload; + return state; + }, setDateOfPaymentChatMike(state, action: PayloadAction) { state.dateOfPaymentChatMike = action.payload.toISOString(); return state; @@ -70,6 +76,10 @@ export const selectFeature = createSelector( (state: { userConfig: IUserConfig }) => state.userConfig.feature, (userConfig) => userConfig ); +export const selectAuthCode = createSelector( + (state: { userConfig: IUserConfig }) => state.userConfig.authCode, + (userConfig) => userConfig +); export const selectDateOfPaymentChatMike = createSelector( (state: { userConfig: IUserConfig }) => state.userConfig.dateOfPaymentChatMike, (userConfig) => userConfig diff --git a/src/utils/FBMetaPixel/index.tsx b/src/utils/FBMetaPixel/index.tsx new file mode 100644 index 0000000..b91ead0 --- /dev/null +++ b/src/utils/FBMetaPixel/index.tsx @@ -0,0 +1,22 @@ +import { Helmet } from "react-helmet"; + +const MetaPixel = () => { + const FBScript = `!function(f,b,e,v,n,t,s) +{if(f.fbq)return;n=f.fbq=function(){n.callMethod? +n.callMethod.apply(n,arguments):n.queue.push(arguments)}; +if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0'; +n.queue=[];t=b.createElement(e);t.async=!0; +t.src=v;s=b.getElementsByTagName(e)[0]; +s.parentNode.insertBefore(t,s)}(window, document,'script', +'https://connect.facebook.net/en_US/fbevents.js'); +fbq('init', '410962885334010'); +fbq('track', 'PageView');`; + + return ( + + + + ); +}; + +export default MetaPixel;