Preview/discount pages

This commit is contained in:
Денис Катаев 2024-02-29 15:44:41 +00:00 committed by Victor Ershov
parent 238f6dc261
commit 3aee33898c
14 changed files with 206 additions and 114 deletions

View File

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

View File

@ -1,28 +1,41 @@
import { useCallback, useMemo } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { actions, selectors } from '../store'
import { AuthToken, User } from '../api'
import { AuthContext } from './AuthContext'
import { useCallback, useMemo } from "react";
import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from "../store";
import { AuthToken, User } from "../api";
import { AuthContext } from "./AuthContext";
export function AuthProvider({ children }: React.PropsWithChildren<unknown>): JSX.Element {
const dispatch = useDispatch()
const token = useSelector(selectors.selectToken)
const user = useSelector(selectors.selectUser)
const signUp = useCallback((token: AuthToken, user: User.User): AuthToken => {
dispatch(actions.token.update(token))
dispatch(actions.user.update(user))
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>
)
export function AuthProvider({
children,
}: React.PropsWithChildren<unknown>): JSX.Element {
const dispatch = useDispatch();
const token = useSelector(selectors.selectToken);
const user = useSelector(selectors.selectUser);
const signUp = useCallback(
(token: AuthToken, user: User.User): AuthToken => {
dispatch(actions.token.update(token));
dispatch(actions.user.update(user));
dispatch(actions.form.addEmail(user.email));
if (user.profile.birthday?.length) {
dispatch(actions.form.addDate(user.profile.birthday.split(" ")[0]));
dispatch(
actions.form.addTime(
new Date(user.profile.birthday).toLocaleTimeString()
)
);
}
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,
useLocation,
useNavigate,
useSearchParams,
} from "react-router-dom";
import { useAuth } from "@/auth";
import { useDispatch, useSelector } from "react-redux";
@ -112,7 +113,9 @@ function App(): JSX.Element {
const navigate = useNavigate();
const api = useApi();
const dispatch = useDispatch();
const { token, user } = useAuth();
const { token, user, signUp } = useAuth();
const [searchParams] = useSearchParams();
const jwtToken = searchParams.get("token");
useEffect(() => {
if (!isProduction) return;
@ -137,6 +140,18 @@ function App(): JSX.Element {
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(() => {
(async () => {
if (!token.length || !user) return;
@ -185,8 +200,11 @@ function App(): JSX.Element {
// set user device type
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));
} else {
dispatch(actions.userConfig.addDeviceType(EUserDeviceType.android));
}
}, [dispatch]);
@ -473,7 +491,6 @@ function Layout({ setIsSpecialOfferOpen }: LayoutProps): JSX.Element {
}, [dataItems]);
const onCloseFullDataModal = (_birthDate: string) => {
console.log("onCloseFullDataModal", _birthDate);
dispatch(actions.form.addDate(_birthDate));
setIsShowFullDataModal(getIsShowFullDataModal(dataItems));
};
@ -584,8 +601,7 @@ function PrivateOutlet(): JSX.Element {
}
function PrivateSubscriptionOutlet(): JSX.Element {
// const isProduction = import.meta.env.MODE === "production";
const isProduction = false;
const isProduction = import.meta.env.MODE === "production";
const status = useSelector(selectors.selectStatus);
return status === "subscribed" || !isProduction ? (
<Outlet />

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -52,7 +52,11 @@ function TryAppPage() {
return (
<section className={`${styles.page} page`}>
<Header buttonClick={downloadApp} />
<Header
buttonClick={downloadApp}
buttonText="get my reading in the app"
buttonClassName={styles["header-button"]}
/>
{singleOrWithPartner === "partner" && (
<WithPartnerInformation
zodiacSign={zodiacSign}
@ -90,7 +94,7 @@ function TryAppPage() {
alt="Download on the app store"
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>
<p className={styles["code-description"]}>
Enter your access code in the app to access Your Personalized Reading.
@ -102,6 +106,8 @@ function TryAppPage() {
zodiacSign={zodiacSign}
buttonClick={downloadApp}
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
variant="h2"

View File

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