Merge branch 'preview/discount-pages' into 'main'

Preview/discount pages

See merge request witapp/aura-webapp!50
This commit is contained in:
Victor Ershov 2024-02-29 15:44:41 +00:00
commit bd995211c5
14 changed files with 206 additions and 114 deletions

View File

@ -1,37 +1,43 @@
import routes from "@/routes" import routes from "@/routes";
import { AuthToken } from "../types" import { AuthToken } from "../types";
import { User } from "./User" import { User } from "./User";
import { getBaseHeaders } from "../utils" import { getBaseHeaders } from "../utils";
export interface Payload { export interface PayloadRegisterByEmail {
email: string email: string;
timezone: string timezone: string;
locale: string locale: string;
} }
export interface PayloadAuthWithJWT {
jwt: string;
}
export type Payload = PayloadRegisterByEmail | PayloadAuthWithJWT;
export interface Response { export interface Response {
auth: { auth: {
token: AuthToken token: AuthToken;
payload: JwtPayload payload: JwtPayload;
user: User user: User;
} };
} }
export interface JwtPayload { export interface JwtPayload {
sub: number sub: number;
email: string email: string;
loc: string loc: string;
tz: number tz: number;
state: string state: string;
iat: number iat: number;
exp: number exp: number;
jti: string jti: string;
type: string type: string;
iss: string iss: string;
} }
export const createRequest = ({ locale, timezone, email }: Payload): Request => { export const createRequest = (payload: Payload): Request => {
const url = new URL(routes.server.token()) const url = new URL(routes.server.token());
const body = JSON.stringify({ auth: { locale, timezone, email }}) const body = JSON.stringify({ auth: { ...payload } });
return new Request(url, { method: 'POST', headers: getBaseHeaders(), body }) return new Request(url, { method: "POST", headers: getBaseHeaders(), body });
} };

View File

@ -1,28 +1,41 @@
import { useCallback, useMemo } from 'react' import { useCallback, useMemo } from "react";
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from '../store' import { actions, selectors } from "../store";
import { AuthToken, User } from '../api' import { AuthToken, User } from "../api";
import { AuthContext } from './AuthContext' import { AuthContext } from "./AuthContext";
export function AuthProvider({ children }: React.PropsWithChildren<unknown>): JSX.Element { export function AuthProvider({
const dispatch = useDispatch() children,
const token = useSelector(selectors.selectToken) }: React.PropsWithChildren<unknown>): JSX.Element {
const user = useSelector(selectors.selectUser) const dispatch = useDispatch();
const signUp = useCallback((token: AuthToken, user: User.User): AuthToken => { const token = useSelector(selectors.selectToken);
dispatch(actions.token.update(token)) const user = useSelector(selectors.selectUser);
dispatch(actions.user.update(user)) const signUp = useCallback(
return token (token: AuthToken, user: User.User): AuthToken => {
}, [dispatch]) dispatch(actions.token.update(token));
const logout = useCallback(() => dispatch(actions.reset()), [dispatch]) dispatch(actions.user.update(user));
const auth = useMemo(() => ({ dispatch(actions.form.addEmail(user.email));
signUp, if (user.profile.birthday?.length) {
logout, dispatch(actions.form.addDate(user.profile.birthday.split(" ")[0]));
token, dispatch(
user: user.id ? user : null actions.form.addTime(
}), [token, user, signUp, logout]) new Date(user.profile.birthday).toLocaleTimeString()
return ( )
<AuthContext.Provider value={auth}> );
{children} }
</AuthContext.Provider> return token;
) },
[dispatch]
);
const logout = useCallback(() => dispatch(actions.reset()), [dispatch]);
const auth = useMemo(
() => ({
signUp,
logout,
token,
user: user.id ? user : null,
}),
[token, user, signUp, logout]
);
return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>;
} }

View File

@ -6,6 +6,7 @@ import {
Outlet, Outlet,
useLocation, useLocation,
useNavigate, useNavigate,
useSearchParams,
} from "react-router-dom"; } from "react-router-dom";
import { useAuth } from "@/auth"; import { useAuth } from "@/auth";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
@ -112,7 +113,9 @@ function App(): JSX.Element {
const navigate = useNavigate(); const navigate = useNavigate();
const api = useApi(); const api = useApi();
const dispatch = useDispatch(); const dispatch = useDispatch();
const { token, user } = useAuth(); const { token, user, signUp } = useAuth();
const [searchParams] = useSearchParams();
const jwtToken = searchParams.get("token");
useEffect(() => { useEffect(() => {
if (!isProduction) return; if (!isProduction) return;
@ -137,6 +140,18 @@ function App(): JSX.Element {
const { data } = useApiCall<Asset[]>(assetsData); const { data } = useApiCall<Asset[]>(assetsData);
// jwt auth
useEffect(() => {
(async () => {
if (!jwtToken) return;
const auth = await api.auth({ jwt: jwtToken });
const {
auth: { token, user },
} = auth;
signUp(token, user);
})();
}, [api, jwtToken, signUp]);
useEffect(() => { useEffect(() => {
(async () => { (async () => {
if (!token.length || !user) return; if (!token.length || !user) return;
@ -185,8 +200,11 @@ function App(): JSX.Element {
// set user device type // set user device type
useEffect(() => { useEffect(() => {
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) { const isIOS = /iPhone|iPad|iPod/i.test(navigator.userAgent);
if (isIOS) {
dispatch(actions.userConfig.addDeviceType(EUserDeviceType.ios)); dispatch(actions.userConfig.addDeviceType(EUserDeviceType.ios));
} else {
dispatch(actions.userConfig.addDeviceType(EUserDeviceType.android));
} }
}, [dispatch]); }, [dispatch]);
@ -473,7 +491,6 @@ function Layout({ setIsSpecialOfferOpen }: LayoutProps): JSX.Element {
}, [dataItems]); }, [dataItems]);
const onCloseFullDataModal = (_birthDate: string) => { const onCloseFullDataModal = (_birthDate: string) => {
console.log("onCloseFullDataModal", _birthDate);
dispatch(actions.form.addDate(_birthDate)); dispatch(actions.form.addDate(_birthDate));
setIsShowFullDataModal(getIsShowFullDataModal(dataItems)); setIsShowFullDataModal(getIsShowFullDataModal(dataItems));
}; };
@ -584,8 +601,7 @@ function PrivateOutlet(): JSX.Element {
} }
function PrivateSubscriptionOutlet(): JSX.Element { function PrivateSubscriptionOutlet(): JSX.Element {
// const isProduction = import.meta.env.MODE === "production"; const isProduction = import.meta.env.MODE === "production";
const isProduction = false;
const status = useSelector(selectors.selectStatus); const status = useSelector(selectors.selectStatus);
return status === "subscribed" || !isProduction ? ( return status === "subscribed" || !isProduction ? (
<Outlet /> <Outlet />

View File

@ -41,6 +41,9 @@ function EmailEnterPage(): JSX.Element {
const timezone = getClientTimezone(); const timezone = getClientTimezone();
const locale = i18n.language; const locale = i18n.language;
const { subPlan } = useParams(); const { subPlan } = useParams();
const { gender, flowChoice, birthPlace } = useSelector(
selectors.selectQuestionnaire
);
useEffect(() => { useEffect(() => {
if (subPlan) { if (subPlan) {
@ -111,7 +114,15 @@ function EmailEnterPage(): JSX.Element {
} = auth; } = auth;
signUp(token, user); signUp(token, user);
const payload = { const payload = {
user: { profile_attributes: { birthday } }, user: {
profile_attributes: {
birthday,
gender,
full_name: name,
relationship_status: flowChoice,
},
birthplace_attributes: { address: birthPlace },
},
token, token,
}; };
const updatedUser = await api.updateUser(payload).catch((error) => { const updatedUser = await api.updateUser(payload).catch((error) => {

View File

@ -15,23 +15,23 @@ import {
} from "@/services/zodiac-sign"; } from "@/services/zodiac-sign";
import { actions, selectors } from "@/store"; import { actions, selectors } from "@/store";
import { getRandomArbitrary } from "@/services/random-value"; import { getRandomArbitrary } from "@/services/random-value";
import Title from "../Title"; // import Title from "../Title";
// import { UserDailyForecast } from "@/api/resources/UserDailyForecasts"; // import { UserDailyForecast } from "@/api/resources/UserDailyForecasts";
import { EPathsFromHome } from "@/store/siteConfig"; import { EPathsFromHome } from "@/store/siteConfig";
import { buildFilename, saveFile } from "../WallpaperPage/utils"; import { buildFilename, saveFile } from "../WallpaperPage/utils";
import Onboarding from "../Onboarding"; import Onboarding from "../Onboarding";
import TextWithFinger from "../TextWithFinger"; import TextWithFinger from "../TextWithFinger";
import Slider from "../Slider"; // import Slider from "../Slider";
import BestiesHoroscopeSlider, { Horoscope } from "../BestiesHoroscopeSlider"; // import BestiesHoroscopeSlider, { Horoscope } from "../BestiesHoroscopeSlider";
import PredictionMoonsSlider, { // import PredictionMoonsSlider, {
IPredictionMoon, // IPredictionMoon,
} from "../PredictionMoonsSlider"; // } from "../PredictionMoonsSlider";
import { predictionMoonsPeriods } from "@/data"; import { predictionMoonsPeriods } from "@/data";
import WallpapersZodiacSign from "../WallpapersZodiacSign"; // import WallpapersZodiacSign from "../WallpapersZodiacSign";
import ThermalSlider from "../ThermalSlider"; // import ThermalSlider from "../ThermalSlider";
import MoonPhaseTracker from "../MoonPhaseTracker"; // import MoonPhaseTracker from "../MoonPhaseTracker";
import EnergyVampirism from "../EnergyVampirism"; // import EnergyVampirism from "../EnergyVampirism";
import NameHoroscopeSlider from "../NameHoroscopeSlider"; // import NameHoroscopeSlider from "../NameHoroscopeSlider";
const buttonTextFormatter = (text: string): JSX.Element => { const buttonTextFormatter = (text: string): JSX.Element => {
const sentences = text.split("."); const sentences = text.split(".");
@ -56,6 +56,7 @@ function HomePage(): JSX.Element {
const zodiacSign = getZodiacSignByDate(birthdate); const zodiacSign = getZodiacSignByDate(birthdate);
const [asset, setAsset] = useState<Asset>(); const [asset, setAsset] = useState<Asset>();
const [moonsAssets, setMoonsAssets] = useState<Asset[]>([]); const [moonsAssets, setMoonsAssets] = useState<Asset[]>([]);
moonsAssets
const api = useApi(); const api = useApi();
const homeConfig = useSelector(selectors.selectHome); const homeConfig = useSelector(selectors.selectHome);
@ -63,8 +64,10 @@ function HomePage(): JSX.Element {
const onboardingConfigHome = useSelector(selectors.selectOnboardingHome); const onboardingConfigHome = useSelector(selectors.selectOnboardingHome);
const compatibilities = useSelector(selectors.selectCompatibilities); const compatibilities = useSelector(selectors.selectCompatibilities);
compatibilities
const user = useSelector(selectors.selectUser); const user = useSelector(selectors.selectUser);
user
const [isShowOnboardingHome, setIsShowOnboardingHome] = useState( const [isShowOnboardingHome, setIsShowOnboardingHome] = useState(
!onboardingConfigHome?.isShown !onboardingConfigHome?.isShown
@ -168,36 +171,36 @@ function HomePage(): JSX.Element {
saveFile(asset.url.replace("http://", "https://"), buildFilename("1")); saveFile(asset.url.replace("http://", "https://"), buildFilename("1"));
}; };
const handleBestiesHoroscope = (item: Horoscope) => { // const handleBestiesHoroscope = (item: Horoscope) => {
const { name, birthDate } = item; // const { name, birthDate } = item;
navigate( // navigate(
`${routes.client.horoscopeBestiesResult()}?name=${name}&birthDate=${birthDate}` // `${routes.client.horoscopeBestiesResult()}?name=${name}&birthDate=${birthDate}`
); // );
}; // };
const handleThermal = (item: Horoscope) => { // const handleThermal = (item: Horoscope) => {
const { name, birthDate } = item; // const { name, birthDate } = item;
navigate( // navigate(
`${routes.client.thermalResult()}?name=${name}&birthDate=${birthDate}` // `${routes.client.thermalResult()}?name=${name}&birthDate=${birthDate}`
); // );
}; // };
const handlePredictionMoon = (item: IPredictionMoon) => { // const handlePredictionMoon = (item: IPredictionMoon) => {
const { period } = item; // const { period } = item;
navigate(`${routes.client.predictionMoonResult()}?period=${period}`); // navigate(`${routes.client.predictionMoonResult()}?period=${period}`);
}; // };
const handleMoonPhaseTracker = () => { // const handleMoonPhaseTracker = () => {
navigate(`${routes.client.moonPhaseTracker()}?period=today`); // navigate(`${routes.client.moonPhaseTracker()}?period=today`);
}; // };
const handleEnergyVampirism = () => { // const handleEnergyVampirism = () => {
navigate(`${routes.client.energyVampirismResult()}?period=today`); // navigate(`${routes.client.energyVampirismResult()}?period=today`);
}; // };
const handleNameHoroscope = (item: IPredictionMoon) => { // const handleNameHoroscope = (item: IPredictionMoon) => {
navigate(`${routes.client.nameHoroscopeResult()}?period=${item.period}`); // navigate(`${routes.client.nameHoroscopeResult()}?period=${item.period}`);
}; // };
return ( return (
<section <section
@ -287,7 +290,7 @@ function HomePage(): JSX.Element {
</div> </div>
{/* SLIDERS */} {/* SLIDERS */}
<div className={styles.sliders}> {/* <div className={styles.sliders}>
{!!compatibilities.length && ( {!!compatibilities.length && (
<div className={styles["slider"]}> <div className={styles["slider"]}>
<Title variant="h2" className={styles["sliders__title"]}> <Title variant="h2" className={styles["sliders__title"]}>
@ -323,16 +326,16 @@ function HomePage(): JSX.Element {
))} ))}
</Slider> </Slider>
</div> </div>
</div> </div> */}
{/* END SLIDERS */} {/* END SLIDERS */}
<Title variant="h2" className={styles["sliders__title"]}> {/* <Title variant="h2" className={styles["sliders__title"]}>
{"Energy Vampirism"} {"Energy Vampirism"}
</Title> </Title>
<EnergyVampirism onClick={handleEnergyVampirism} /> <EnergyVampirism onClick={handleEnergyVampirism} /> */}
{/* SLIDERS */} {/* SLIDERS */}
<div className={styles.sliders}> {/* <div className={styles.sliders}>
<div className={styles["slider"]}> <div className={styles["slider"]}>
<Title variant="h2" className={styles["sliders__title"]}> <Title variant="h2" className={styles["sliders__title"]}>
{t("Your Name's Horoscope")} {t("Your Name's Horoscope")}
@ -349,16 +352,16 @@ function HomePage(): JSX.Element {
))} ))}
</Slider> </Slider>
</div> </div>
</div> </div> */}
{/* END SLIDERS */} {/* END SLIDERS */}
<Title variant="h2" className={styles["sliders__title"]}> {/* <Title variant="h2" className={styles["sliders__title"]}>
{"AI-based unique Walpapers"} {"AI-based unique Walpapers"}
</Title> </Title>
<WallpapersZodiacSign /> <WallpapersZodiacSign /> */}
{/* SLIDERS */} {/* SLIDERS */}
<div className={styles.sliders}> {/* <div className={styles.sliders}>
{!!compatibilities.length && ( {!!compatibilities.length && (
<div className={styles["slider"]}> <div className={styles["slider"]}>
<Title variant="h2" className={styles["sliders__title"]}> <Title variant="h2" className={styles["sliders__title"]}>
@ -377,10 +380,10 @@ function HomePage(): JSX.Element {
</Slider> </Slider>
</div> </div>
)} )}
</div> </div> */}
{/* END SLIDERS */} {/* END SLIDERS */}
<MoonPhaseTracker onClick={handleMoonPhaseTracker} /> {/* <MoonPhaseTracker onClick={handleMoonPhaseTracker} /> */}
{/* <div className={styles["content__daily-forecast"]}> {/* <div className={styles["content__daily-forecast"]}>
{dailyForecast && {dailyForecast &&

View File

@ -17,7 +17,6 @@ function OnboardingPage() {
}, [navigate]); }, [navigate]);
useEffect(() => { useEffect(() => {
console.log("onboardingTitles", activeIndexTitle);
setPeriodClassName("to-nontransparent"); setPeriodClassName("to-nontransparent");
classNameTimeOut.current = setTimeout(() => { classNameTimeOut.current = setTimeout(() => {
setPeriodClassName("to-transparent"); setPeriodClassName("to-transparent");

View File

@ -22,6 +22,7 @@ function BirthdateCustomAnswer({
const questionnaire = useSelector(selectors.selectQuestionnaire); const questionnaire = useSelector(selectors.selectQuestionnaire);
const birthdateFromStore = const birthdateFromStore =
affiliation === "self" ? selfBirthdate : questionnaire.partnerBirthdate; affiliation === "self" ? selfBirthdate : questionnaire.partnerBirthdate;
const [birthdate, setBirthdate] = useState(birthdateFromStore); const [birthdate, setBirthdate] = useState(birthdateFromStore);
const [isDisabled, setIsDisabled] = useState(true); const [isDisabled, setIsDisabled] = useState(true);
const handleValid = (_birthdate: string) => { const handleValid = (_birthdate: string) => {

View File

@ -22,6 +22,7 @@ function BirthtimeCustomAnswer({
const questionnaire = useSelector(selectors.selectQuestionnaire); const questionnaire = useSelector(selectors.selectQuestionnaire);
const birthtimeFromStore = const birthtimeFromStore =
affiliation === "self" ? selfBirthtime : questionnaire.partnerBirthtime; affiliation === "self" ? selfBirthtime : questionnaire.partnerBirthtime;
const [birthtime, setBirthtime] = useState(birthtimeFromStore); const [birthtime, setBirthtime] = useState(birthtimeFromStore);
const handleChange = (_birthtime: string) => { const handleChange = (_birthtime: string) => {

View File

@ -23,6 +23,7 @@ function TrialChoicePage() {
const navigate = useNavigate(); const navigate = useNavigate();
const selectedPrice = useSelector(selectors.selectSelectedPrice); const selectedPrice = useSelector(selectors.selectSelectedPrice);
const homeConfig = useSelector(selectors.selectHome); const homeConfig = useSelector(selectors.selectHome);
const email = useSelector(selectors.selectEmail);
const [subPlans, setSubPlans] = useState<ISubscriptionPlan[]>([]); const [subPlans, setSubPlans] = useState<ISubscriptionPlan[]>([]);
const [isDisabled, setIsDisabled] = useState(true); const [isDisabled, setIsDisabled] = useState(true);
@ -117,6 +118,7 @@ function TrialChoicePage() {
direction="right-left" direction="right-left"
/> />
</div> </div>
<p className={styles.email}>{email}</p>
<MainButton <MainButton
className={styles.button} className={styles.button}
disabled={isDisabled} disabled={isDisabled}

View File

@ -121,3 +121,7 @@
min-height: 0; min-height: 0;
height: 50px; height: 50px;
} }
.email {
font-weight: 500;
}

View File

@ -3,15 +3,24 @@ import DiscountExpires from "../DiscountExpires";
import styles from "./styles.module.css"; import styles from "./styles.module.css";
interface IHeaderProps { interface IHeaderProps {
buttonText?: string;
buttonClassName?: string;
buttonClick: () => void; buttonClick: () => void;
} }
function Header({ buttonClick }: IHeaderProps) { function Header({
buttonClick,
buttonText = "get my reading",
buttonClassName = "",
}: IHeaderProps) {
return ( return (
<header className={styles.header}> <header className={styles.header}>
<DiscountExpires /> <DiscountExpires />
<CustomButton className={styles.button} onClick={buttonClick}> <CustomButton
get my reading className={`${styles.button} ${buttonClassName}`}
onClick={buttonClick}
>
{buttonText}
</CustomButton> </CustomButton>
</header> </header>
); );

View File

@ -8,6 +8,8 @@ interface IYourReadingProps {
gender: string; gender: string;
zodiacSign: string; zodiacSign: string;
singleOrWithPartner: "single" | "partner"; singleOrWithPartner: "single" | "partner";
buttonText?: string;
callToActionText?: string;
buttonClick: () => void; buttonClick: () => void;
} }
@ -15,6 +17,8 @@ function YourReading({
gender, gender,
zodiacSign, zodiacSign,
singleOrWithPartner = "single", singleOrWithPartner = "single",
buttonText = "get my reading",
callToActionText = "To read the full reading you need get access",
buttonClick, buttonClick,
}: IYourReadingProps) { }: IYourReadingProps) {
const [points, setPoints] = useState(yourReadingList); const [points, setPoints] = useState(yourReadingList);
@ -60,9 +64,9 @@ function YourReading({
</div> </div>
<div className={styles["cover-container"]}> <div className={styles["cover-container"]}>
<div className={styles["cover"]}></div> <div className={styles["cover"]}></div>
<p>To read the full reading you need get access</p> <p>{callToActionText}</p>
<CustomButton className={styles.button} onClick={buttonClick}> <CustomButton className={styles.button} onClick={buttonClick}>
get my reading {buttonText}
</CustomButton> </CustomButton>
</div> </div>
</div> </div>

View File

@ -52,7 +52,11 @@ function TryAppPage() {
return ( return (
<section className={`${styles.page} page`}> <section className={`${styles.page} page`}>
<Header buttonClick={downloadApp} /> <Header
buttonClick={downloadApp}
buttonText="get my reading in the app"
buttonClassName={styles["header-button"]}
/>
{singleOrWithPartner === "partner" && ( {singleOrWithPartner === "partner" && (
<WithPartnerInformation <WithPartnerInformation
zodiacSign={zodiacSign} zodiacSign={zodiacSign}
@ -90,7 +94,7 @@ function TryAppPage() {
alt="Download on the app store" alt="Download on the app store"
onClick={downloadApp} onClick={downloadApp}
/> />
<span className={styles["download-app-title"]}>1. Download App</span> <span className={styles["download-app-title"]}>2. Enter Your Access Code</span>
<div className={styles["code-container"]}>FV1HBP</div> <div className={styles["code-container"]}>FV1HBP</div>
<p className={styles["code-description"]}> <p className={styles["code-description"]}>
Enter your access code in the app to access Your Personalized Reading. Enter your access code in the app to access Your Personalized Reading.
@ -102,6 +106,8 @@ function TryAppPage() {
zodiacSign={zodiacSign} zodiacSign={zodiacSign}
buttonClick={downloadApp} buttonClick={downloadApp}
singleOrWithPartner={singleOrWithPartner} singleOrWithPartner={singleOrWithPartner}
callToActionText="To read the full reading you need get access through the app for your iPhone"
buttonText="get my reading in the app"
/> />
<Title <Title
variant="h2" variant="h2"

View File

@ -22,6 +22,7 @@
max-width: 270px; max-width: 270px;
height: 80px; height: 80px;
object-fit: cover; object-fit: cover;
animation: 1.5s ease 0s infinite normal none running pulse;
} }
.code-container { .code-container {
@ -46,3 +47,19 @@
.title { .title {
font-weight: 700; font-weight: 700;
} }
.header-button {
height: 100%;
}
@keyframes pulse {
0% {
transform: scale(0.9);
}
70% {
transform: scale(1);
}
100% {
transform: scale(0.9);
}
}