Merge branch 'develop' into 'main'
Develop See merge request witapp/aura-webapp!427
This commit is contained in:
commit
27808d8fbd
@ -32,7 +32,9 @@ import {
|
|||||||
UserVideos,
|
UserVideos,
|
||||||
UserPDF,
|
UserPDF,
|
||||||
Locale,
|
Locale,
|
||||||
Session
|
Session,
|
||||||
|
Login,
|
||||||
|
Password
|
||||||
} from './resources'
|
} from './resources'
|
||||||
|
|
||||||
const api = {
|
const api = {
|
||||||
@ -80,6 +82,8 @@ const api = {
|
|||||||
getPalmistryLines: createMethod<Palmistry.Payload, Palmistry.Response>(Palmistry.createRequest),
|
getPalmistryLines: createMethod<Palmistry.Payload, Palmistry.Response>(Palmistry.createRequest),
|
||||||
// New Authorization
|
// New Authorization
|
||||||
authorization: createMethod<User.ICreateAuthorizePayload, User.ICreateAuthorizeResponse>(User.createAuthorizeRequest),
|
authorization: createMethod<User.ICreateAuthorizePayload, User.ICreateAuthorizeResponse>(User.createAuthorizeRequest),
|
||||||
|
login: createMethod<Login.Payload, Login.Response>(Login.createRequest),
|
||||||
|
resetPassword: createMethod<Password.Payload, Password.Response>(Password.resetRequest),
|
||||||
// Paywall
|
// Paywall
|
||||||
getPaywallByPlacementKey: createMethod<Paywall.PayloadGet, Paywall.ResponseGet>(Paywall.createRequestGet),
|
getPaywallByPlacementKey: createMethod<Paywall.PayloadGet, Paywall.ResponseGet>(Paywall.createRequestGet),
|
||||||
// Payment
|
// Payment
|
||||||
|
|||||||
27
src/api/resources/Login.ts
Normal file
27
src/api/resources/Login.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import routes from "@/routes";
|
||||||
|
import { getBaseHeaders } from "../utils";
|
||||||
|
|
||||||
|
export interface Payload {
|
||||||
|
email: string;
|
||||||
|
locale: string;
|
||||||
|
timezone: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Response = {
|
||||||
|
status: string,
|
||||||
|
message: string
|
||||||
|
} | {
|
||||||
|
token: string;
|
||||||
|
userId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createRequest = (data: Payload) => {
|
||||||
|
const url = new URL(routes.server.login());
|
||||||
|
const body = JSON.stringify(data);
|
||||||
|
return new Request(url, {
|
||||||
|
method: "POST",
|
||||||
|
body,
|
||||||
|
headers: getBaseHeaders()
|
||||||
|
});
|
||||||
|
};
|
||||||
22
src/api/resources/Password.ts
Normal file
22
src/api/resources/Password.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import routes from "@/routes";
|
||||||
|
import { getBaseHeaders } from "../utils";
|
||||||
|
|
||||||
|
export interface Payload {
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Response = {
|
||||||
|
status: string;
|
||||||
|
message: string;
|
||||||
|
password?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const resetRequest = (data: Payload) => {
|
||||||
|
const url = new URL(routes.server.resetPassword());
|
||||||
|
const body = JSON.stringify(data);
|
||||||
|
return new Request(url, {
|
||||||
|
method: "POST",
|
||||||
|
body,
|
||||||
|
headers: getBaseHeaders()
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -31,3 +31,5 @@ export * as UserVideos from "./UserVideos";
|
|||||||
export * as UserPDF from "./UserPDF";
|
export * as UserPDF from "./UserPDF";
|
||||||
export * as Locale from "./Locale";
|
export * as Locale from "./Locale";
|
||||||
export * as Session from "./Session";
|
export * as Session from "./Session";
|
||||||
|
export * as Login from "./Login";
|
||||||
|
export * as Password from "./Password";
|
||||||
|
|||||||
@ -58,7 +58,7 @@ import { Asset } from "@/api/resources/Assets";
|
|||||||
import PaymentResultPage from "../PaymentPage/results";
|
import PaymentResultPage from "../PaymentPage/results";
|
||||||
import PaymentSuccessPage from "../PaymentPage/results/SuccessPage";
|
import PaymentSuccessPage from "../PaymentPage/results/SuccessPage";
|
||||||
import PaymentFailPage from "../PaymentPage/results/ErrorPage";
|
import PaymentFailPage from "../PaymentPage/results/ErrorPage";
|
||||||
import AuthPage from "../AuthPage";
|
// import AuthPage from "../AuthPage";
|
||||||
import AuthResultPage from "../AuthResultPage";
|
import AuthResultPage from "../AuthResultPage";
|
||||||
import MagicBallPage from "../pages/MagicBall";
|
import MagicBallPage from "../pages/MagicBall";
|
||||||
import BestiesHoroscopeResult from "../pages/BestiesHoroscopeResult";
|
import BestiesHoroscopeResult from "../pages/BestiesHoroscopeResult";
|
||||||
@ -130,6 +130,7 @@ import AddConsultant from "../palmistry/AdditionalPurchases/pages/AddConsultant"
|
|||||||
import AddGuides from "../palmistry/AdditionalPurchases/pages/AddGuides";
|
import AddGuides from "../palmistry/AdditionalPurchases/pages/AddGuides";
|
||||||
import SkipTrial from "../palmistry/AdditionalPurchases/pages/SkipTrial";
|
import SkipTrial from "../palmistry/AdditionalPurchases/pages/SkipTrial";
|
||||||
import { parseQueryParams } from "@/services/url";
|
import { parseQueryParams } from "@/services/url";
|
||||||
|
import Auth from "../pages/Auth";
|
||||||
|
|
||||||
const isProduction = import.meta.env.MODE === "production";
|
const isProduction = import.meta.env.MODE === "production";
|
||||||
|
|
||||||
@ -141,7 +142,10 @@ function App(): JSX.Element {
|
|||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [isSpecialOfferOpen, setIsSpecialOfferOpen] = useState<boolean>(false);
|
const [isSpecialOfferOpen, setIsSpecialOfferOpen] = useState<boolean>(false);
|
||||||
const [leoApng, setLeoApng] = useState<Error | APNG>(Error);
|
const [leoApng, setLeoApng] = useState<Error | APNG>(Error);
|
||||||
const [padLockApng, setPadLockApng] = useState<Error | APNG>(Error);
|
// const [
|
||||||
|
// padLockApng,
|
||||||
|
// setPadLockApng,
|
||||||
|
// ] = useState<Error | APNG>(Error);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -283,13 +287,13 @@ function App(): JSX.Element {
|
|||||||
getApng();
|
getApng();
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
(async () => {
|
// (async () => {
|
||||||
const response = await fetch("/padlock_icon_animation_closing.png");
|
// const response = await fetch("/padlock_icon_animation_closing.png");
|
||||||
const arrayBuffer = await response.arrayBuffer();
|
// const arrayBuffer = await response.arrayBuffer();
|
||||||
setPadLockApng(parseAPNG(arrayBuffer));
|
// setPadLockApng(parseAPNG(arrayBuffer));
|
||||||
})();
|
// })();
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
@ -308,6 +312,7 @@ function App(): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
|
<Route path={routes.client.auth()} element={<Auth />} />
|
||||||
<Route path="*" element={<ABDesignV1Routes />} />
|
<Route path="*" element={<ABDesignV1Routes />} />
|
||||||
<Route path={`${palmistryV1Prefix}/*`} element={<PalmistryV1Routes />} />
|
<Route path={`${palmistryV1Prefix}/*`} element={<PalmistryV1Routes />} />
|
||||||
<Route path={`${routes.client.mikeV1()}/*`} element={<MikeV1Routes />} />
|
<Route path={`${routes.client.mikeV1()}/*`} element={<MikeV1Routes />} />
|
||||||
@ -825,10 +830,10 @@ function App(): JSX.Element {
|
|||||||
path={routes.client.emailEnter()}
|
path={routes.client.emailEnter()}
|
||||||
element={<EmailEnterPage />}
|
element={<EmailEnterPage />}
|
||||||
/>
|
/>
|
||||||
<Route
|
{/* <Route
|
||||||
path={routes.client.auth()}
|
path={routes.client.auth()}
|
||||||
element={<AuthPage padLockApng={padLockApng} />}
|
element={<AuthPage padLockApng={padLockApng} />}
|
||||||
/>
|
/> */}
|
||||||
<Route
|
<Route
|
||||||
path={routes.client.authResult()}
|
path={routes.client.authResult()}
|
||||||
element={<AuthResultPage />}
|
element={<AuthResultPage />}
|
||||||
|
|||||||
@ -141,6 +141,9 @@ function HomePage(): JSX.Element {
|
|||||||
token,
|
token,
|
||||||
key,
|
key,
|
||||||
});
|
});
|
||||||
|
if (pdf?.status === "not_allowed") {
|
||||||
|
return pdf;
|
||||||
|
}
|
||||||
if (!pdf?.url?.length || pdf.status !== "ready") {
|
if (!pdf?.url?.length || pdf.status !== "ready") {
|
||||||
await sleep(5000);
|
await sleep(5000);
|
||||||
return getUserPDF(key);
|
return getUserPDF(key);
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import { usePreloadImages } from "@/hooks/preload/images";
|
|||||||
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
|
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
|
||||||
import { useSession } from "@/hooks/session/useSession";
|
import { useSession } from "@/hooks/session/useSession";
|
||||||
import { EGender, ESourceAuthorization } from "@/api/resources/User";
|
import { EGender, ESourceAuthorization } from "@/api/resources/User";
|
||||||
|
import AlreadyHaveAccount from "@/components/ui/AlreadyHaveAccount";
|
||||||
|
|
||||||
function GenderPalmistry() {
|
function GenderPalmistry() {
|
||||||
const { translate } = useTranslations(ELocalesPlacement.PalmistryV1);
|
const { translate } = useTranslations(ELocalesPlacement.PalmistryV1);
|
||||||
@ -76,6 +77,7 @@ function GenderPalmistry() {
|
|||||||
</Title>
|
</Title>
|
||||||
<p className={styles.description}>{translate("/gender.description")}</p>
|
<p className={styles.description}>{translate("/gender.description")}</p>
|
||||||
<ChooseGender onSelectGender={selectGender} />
|
<ChooseGender onSelectGender={selectGender} />
|
||||||
|
<AlreadyHaveAccount />
|
||||||
<PrivacyPolicy containerClassName={styles["privacy-policy"]} />
|
<PrivacyPolicy containerClassName={styles["privacy-policy"]} />
|
||||||
{gender && !privacyPolicyChecked && (
|
{gender && !privacyPolicyChecked && (
|
||||||
<Toast classNameContainer={styles["toast-container"]} variant="error">
|
<Toast classNameContainer={styles["toast-container"]} variant="error">
|
||||||
|
|||||||
@ -0,0 +1,51 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import styles from "./styles.module.css";
|
||||||
|
import { FormField } from "@/types";
|
||||||
|
|
||||||
|
interface IPasswordInputProps {
|
||||||
|
value: string;
|
||||||
|
placeholder: string;
|
||||||
|
onValid: (value: string) => void;
|
||||||
|
onInvalid: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValidPassword = (password: string) => {
|
||||||
|
return !!(password.length >= 6 && password.length < 30);
|
||||||
|
};
|
||||||
|
|
||||||
|
function PasswordInput({
|
||||||
|
inputClassName,
|
||||||
|
value,
|
||||||
|
placeholder,
|
||||||
|
onValid,
|
||||||
|
onInvalid,
|
||||||
|
}: IPasswordInputProps & Partial<FormField<string>>) {
|
||||||
|
const [password, setPassword] = useState(value);
|
||||||
|
|
||||||
|
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const password = event.target.value;
|
||||||
|
if (!isValidPassword(password)) {
|
||||||
|
onInvalid();
|
||||||
|
} else {
|
||||||
|
onValid(password);
|
||||||
|
}
|
||||||
|
setPassword(password);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles["input-container"]}>
|
||||||
|
<input
|
||||||
|
className={inputClassName}
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
id="password"
|
||||||
|
value={password}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder=" "
|
||||||
|
/>
|
||||||
|
<span className={styles["input__placeholder"]}>{placeholder}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PasswordInput;
|
||||||
@ -22,6 +22,7 @@ import { useTranslations } from "@/hooks/translations";
|
|||||||
import { ELocalesPlacement } from "@/locales";
|
import { ELocalesPlacement } from "@/locales";
|
||||||
import { useSession } from "@/hooks/session/useSession";
|
import { useSession } from "@/hooks/session/useSession";
|
||||||
import { EGender, ESourceAuthorization } from "@/api/resources/User";
|
import { EGender, ESourceAuthorization } from "@/api/resources/User";
|
||||||
|
import AlreadyHaveAccount from "@/components/ui/AlreadyHaveAccount";
|
||||||
|
|
||||||
interface IGenderPageProps {
|
interface IGenderPageProps {
|
||||||
productKey?: EProductKeys;
|
productKey?: EProductKeys;
|
||||||
@ -208,6 +209,7 @@ function GenderPage({ productKey }: IGenderPageProps): JSX.Element {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
<AlreadyHaveAccount />
|
||||||
<PrivacyPolicy containerClassName={styles["privacy-policy"]} />
|
<PrivacyPolicy containerClassName={styles["privacy-policy"]} />
|
||||||
{selectedGender && !privacyPolicyChecked && (
|
{selectedGender && !privacyPolicyChecked && (
|
||||||
<Toast classNameContainer={styles["toast-container"]} variant="error">
|
<Toast classNameContainer={styles["toast-container"]} variant="error">
|
||||||
|
|||||||
40
src/components/pages/Auth/ResetYourPassword/index.tsx
Normal file
40
src/components/pages/Auth/ResetYourPassword/index.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Dispatch, SetStateAction } from "react";
|
||||||
|
import styles from "./styles.module.scss";
|
||||||
|
import { ErrorPayload, Password, useApi } from "@/api";
|
||||||
|
|
||||||
|
interface IResetPasswordProps {
|
||||||
|
email: string;
|
||||||
|
setResultResetPassword: Dispatch<
|
||||||
|
SetStateAction<Password.Response | undefined>
|
||||||
|
>;
|
||||||
|
onClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ResetYourPassword({
|
||||||
|
email,
|
||||||
|
setResultResetPassword,
|
||||||
|
onClick,
|
||||||
|
}: IResetPasswordProps) {
|
||||||
|
const api = useApi();
|
||||||
|
|
||||||
|
const resetPassword = async () => {
|
||||||
|
if (!email) return;
|
||||||
|
try {
|
||||||
|
setResultResetPassword(await api.resetPassword({ email }));
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const response = (error as ErrorPayload<Password.Response>).responseData;
|
||||||
|
if (response?.message && !!response?.status) {
|
||||||
|
setResultResetPassword(response);
|
||||||
|
}
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button className={styles.text} onClick={onClick ? onClick : resetPassword}>
|
||||||
|
Reset your password
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ResetYourPassword;
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
.text {
|
||||||
|
padding: 0;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
line-height: 127%;
|
||||||
|
font-size: 14px !important;
|
||||||
|
color: #9974f6 !important;
|
||||||
|
text-decoration: underline;
|
||||||
|
margin: 20px auto;
|
||||||
|
user-select: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
250
src/components/pages/Auth/index.tsx
Normal file
250
src/components/pages/Auth/index.tsx
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
import routes from "@/routes";
|
||||||
|
import styles from "./styles.module.scss";
|
||||||
|
import { useTranslations } from "@/hooks/translations";
|
||||||
|
import { ELocalesPlacement } from "@/locales";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useDynamicSize } from "@/hooks/useDynamicSize";
|
||||||
|
import { useAuthentication } from "@/hooks/authentication/use-authentication";
|
||||||
|
import { actions, selectors } from "@/store";
|
||||||
|
import { usePaywall } from "@/hooks/paywall/usePaywall";
|
||||||
|
import { EPlacementKeys, IPaywallProduct } from "@/api/resources/Paywall";
|
||||||
|
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
|
||||||
|
import metricService, {
|
||||||
|
EGoals,
|
||||||
|
EMetrics,
|
||||||
|
} from "@/services/metric/metricService";
|
||||||
|
import BackgroundTopBlob from "../ABDesign/v1/ui/BackgroundTopBlob";
|
||||||
|
import Title from "@/components/Title";
|
||||||
|
import EmailInput from "../ABDesign/v1/pages/EmailEnterPage/EmailInput";
|
||||||
|
import QuestionnaireGreenButton from "../ABDesign/v1/ui/GreenButton";
|
||||||
|
import Loader, { LoaderColor } from "@/components/Loader";
|
||||||
|
import Policy from "@/components/Policy";
|
||||||
|
import Header from "../ABDesign/v1/components/Header";
|
||||||
|
import PasswordInput from "../ABDesign/v1/pages/EmailEnterPage/PasswordInput";
|
||||||
|
import ResetYourPassword from "./ResetYourPassword";
|
||||||
|
import Toast from "../ABDesign/v1/components/Toast";
|
||||||
|
import { Password } from "@/api";
|
||||||
|
|
||||||
|
interface IAuthPage {
|
||||||
|
redirectUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Auth({ redirectUrl = routes.client.home() }: IAuthPage) {
|
||||||
|
const { translate } = useTranslations(ELocalesPlacement.V1);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [isDisabled, setIsDisabled] = useState(true);
|
||||||
|
const [isValidEmail, setIsValidEmail] = useState(false);
|
||||||
|
const [isValidPassword, setIsValidPassword] = useState(false);
|
||||||
|
const [isAuth, setIsAuth] = useState(false);
|
||||||
|
const { subPlan } = useParams();
|
||||||
|
const { width: pageWidth, elementRef: pageRef } = useDynamicSize({});
|
||||||
|
const { error, isLoading, token, user, authorizationWithPassword } =
|
||||||
|
useAuthentication();
|
||||||
|
const { gender } = useSelector(selectors.selectQuestionnaire);
|
||||||
|
const activeProductFromStore = useSelector(selectors.selectActiveProduct);
|
||||||
|
const { products } = usePaywall({
|
||||||
|
placementKey: EPlacementKeys["aura.placement.redesign.main"],
|
||||||
|
localesPlacement: ELocalesPlacement.V1,
|
||||||
|
});
|
||||||
|
const [activeProduct, setActiveProduct] = useState<IPaywallProduct | null>(
|
||||||
|
activeProductFromStore
|
||||||
|
);
|
||||||
|
const [toastText, setToastText] = useState("");
|
||||||
|
const [resultResetPassword, setResultResetPassword] =
|
||||||
|
useState<Password.Response>();
|
||||||
|
|
||||||
|
useLottie({
|
||||||
|
preloadKey: ELottieKeys.handWithStars,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (subPlan) {
|
||||||
|
const targetProduct = products.find(
|
||||||
|
(product) =>
|
||||||
|
String(
|
||||||
|
product?.trialPrice
|
||||||
|
? Math.floor((product?.trialPrice + 1) / 100)
|
||||||
|
: product.key.replace(".", "")
|
||||||
|
) === subPlan
|
||||||
|
);
|
||||||
|
if (targetProduct) {
|
||||||
|
setActiveProduct(targetProduct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [subPlan, products]);
|
||||||
|
|
||||||
|
const handleValidEmail = (email: string) => {
|
||||||
|
dispatch(actions.form.addEmail(email));
|
||||||
|
setEmail(email);
|
||||||
|
setIsValidEmail(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleValidPassword = (password: string) => {
|
||||||
|
// if (password) {
|
||||||
|
// dispatch(
|
||||||
|
// actions.user.update({
|
||||||
|
// password,
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
setPassword(password);
|
||||||
|
setIsValidPassword(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setToastText("");
|
||||||
|
if (isValidPassword && isValidEmail) {
|
||||||
|
setIsDisabled(false);
|
||||||
|
} else {
|
||||||
|
setIsDisabled(true);
|
||||||
|
}
|
||||||
|
}, [isValidEmail, email, password, isValidPassword]);
|
||||||
|
|
||||||
|
const handleClick = async () => {
|
||||||
|
authorize();
|
||||||
|
metricService.reachGoal(EGoals.ENTERED_EMAIL, [
|
||||||
|
EMetrics.KLAVIYO,
|
||||||
|
EMetrics.YANDEX,
|
||||||
|
EMetrics.FACEBOOK,
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const authorize = async () => {
|
||||||
|
const result = await authorizationWithPassword(email, password);
|
||||||
|
if (!!result && "message" in result && result?.message?.length) {
|
||||||
|
setToastText(result.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (user && token?.length && !isLoading && !error) {
|
||||||
|
dispatch(
|
||||||
|
actions.payment.update({
|
||||||
|
activeProduct,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setIsAuth(true);
|
||||||
|
dispatch(actions.paywalls.resetIsMustUpdate());
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
navigate(redirectUrl);
|
||||||
|
}, 1000);
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
activeProduct,
|
||||||
|
dispatch,
|
||||||
|
error,
|
||||||
|
isLoading,
|
||||||
|
navigate,
|
||||||
|
redirectUrl,
|
||||||
|
token?.length,
|
||||||
|
user,
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (resultResetPassword && resultResetPassword.message) {
|
||||||
|
setToastText(resultResetPassword.message);
|
||||||
|
}
|
||||||
|
}, [resultResetPassword]);
|
||||||
|
|
||||||
|
const handleClickResetPassword = () => {
|
||||||
|
setToastText("Wrong email");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
className={`${styles.page} page`}
|
||||||
|
ref={pageRef}
|
||||||
|
style={{ backgroundColor: gender === "male" ? "#C1E5FF" : "#f7ebff" }}
|
||||||
|
>
|
||||||
|
<BackgroundTopBlob
|
||||||
|
width={pageWidth}
|
||||||
|
className={styles["background-top-blob"]}
|
||||||
|
height={180}
|
||||||
|
/>
|
||||||
|
<Header className={styles.header} />
|
||||||
|
<Title variant="h2" className={styles.title}>
|
||||||
|
{translate("/email.title")}
|
||||||
|
</Title>
|
||||||
|
<p className={styles["not-share"]}>{translate("/email.description")}</p>
|
||||||
|
<EmailInput
|
||||||
|
name="email"
|
||||||
|
value={email}
|
||||||
|
placeholder={translate("/email.placeholder_email")}
|
||||||
|
onValid={handleValidEmail}
|
||||||
|
onInvalid={() => setIsValidEmail(false)}
|
||||||
|
/>
|
||||||
|
<PasswordInput
|
||||||
|
value={password}
|
||||||
|
placeholder={"Password"}
|
||||||
|
onValid={handleValidPassword}
|
||||||
|
onInvalid={() => setIsValidPassword(false)}
|
||||||
|
/>
|
||||||
|
<QuestionnaireGreenButton
|
||||||
|
className={styles.button}
|
||||||
|
onClick={handleClick}
|
||||||
|
disabled={isDisabled}
|
||||||
|
>
|
||||||
|
{isLoading && <Loader color={LoaderColor.White} />}
|
||||||
|
{!isLoading &&
|
||||||
|
!(!error?.length && !isLoading && isAuth) &&
|
||||||
|
translate("continue")}
|
||||||
|
{!error?.length && !isLoading && isAuth && (
|
||||||
|
<img
|
||||||
|
className={styles["success-icon"]}
|
||||||
|
src="/SuccessIcon-white.svg"
|
||||||
|
alt="Success Icon"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</QuestionnaireGreenButton>
|
||||||
|
<ResetYourPassword
|
||||||
|
email={email}
|
||||||
|
setResultResetPassword={setResultResetPassword}
|
||||||
|
onClick={!isValidEmail ? handleClickResetPassword : undefined}
|
||||||
|
/>
|
||||||
|
<Policy sizing="medium" className={styles.policy}>
|
||||||
|
{translate("/email.policy", {
|
||||||
|
eulaLink: (
|
||||||
|
<a
|
||||||
|
className={styles.link}
|
||||||
|
href="https://aura.wit.life/terms"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{translate("/email.policy_eula")}
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
privacyPolicy: (
|
||||||
|
<a
|
||||||
|
className={styles.link}
|
||||||
|
href="https://aura.wit.life/privacy"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{translate("privacy_policy")}
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
</Policy>
|
||||||
|
{/* {!!error?.length && (
|
||||||
|
<Title variant="h3" style={{ color: "red", margin: 0 }}>
|
||||||
|
Something went wrong
|
||||||
|
</Title>
|
||||||
|
)} */}
|
||||||
|
{!!toastText && (
|
||||||
|
<Toast classNameContainer={styles["toast-container"]} variant="error">
|
||||||
|
<span className={styles["toast-text"]}>{toastText}</span>
|
||||||
|
</Toast>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Auth;
|
||||||
128
src/components/pages/Auth/styles.module.scss
Normal file
128
src/components/pages/Auth/styles.module.scss
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
.page {
|
||||||
|
height: fit-content;
|
||||||
|
min-height: 100dvh;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 460px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding-bottom: 86px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 27px;
|
||||||
|
line-height: 125%;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #000;
|
||||||
|
margin-top: 70px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-share {
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 125%;
|
||||||
|
text-align: center;
|
||||||
|
max-width: 330px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.policy {
|
||||||
|
// margin-top: 20px;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.policy > p {
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 125%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
font-size: 12px !important;
|
||||||
|
color: #9974f6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-icon {
|
||||||
|
height: 100%;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
max-width: 400px;
|
||||||
|
min-width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container > input {
|
||||||
|
appearance: none;
|
||||||
|
border-radius: 14px;
|
||||||
|
color: #121620;
|
||||||
|
font-size: 15px;
|
||||||
|
height: 48px;
|
||||||
|
line-height: 125%;
|
||||||
|
outline: none;
|
||||||
|
padding: 16px 24px 5px;
|
||||||
|
transition: border-color 0.3s ease;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container > input:focus {
|
||||||
|
border-color: #000;
|
||||||
|
transition-delay: 0.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container > input:focus + .input__placeholder,
|
||||||
|
.input-container > input:not(:placeholder-shown) + .input__placeholder {
|
||||||
|
font-size: 12px;
|
||||||
|
top: 12px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input__placeholder {
|
||||||
|
color: #8e8e93;
|
||||||
|
font-size: 16px;
|
||||||
|
left: 24px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
transition: top 0.3s ease, color 0.3s ease, font-size 0.3s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
user-select: none;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background-top-blob {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
scale: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
z-index: 3;
|
||||||
|
width: calc(100% + 36px) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-container {
|
||||||
|
position: fixed;
|
||||||
|
bottom: calc(0dvh + 16px);
|
||||||
|
margin-top: 16px;
|
||||||
|
max-width: 460px;
|
||||||
|
padding: 0 24px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-text {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
18
src/components/ui/AlreadyHaveAccount/index.tsx
Normal file
18
src/components/ui/AlreadyHaveAccount/index.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import styles from "./styles.module.scss";
|
||||||
|
import routes from "@/routes";
|
||||||
|
|
||||||
|
function AlreadyHaveAccount() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const navigateAuth = () => {
|
||||||
|
navigate(routes.client.auth());
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<button className={styles["have-account"]} onClick={navigateAuth}>
|
||||||
|
Already have an account? Sign in
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AlreadyHaveAccount;
|
||||||
9
src/components/ui/AlreadyHaveAccount/styles.module.scss
Normal file
9
src/components/ui/AlreadyHaveAccount/styles.module.scss
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
.have-account {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 140%;
|
||||||
|
color: rgb(79, 79, 79);
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { useApi } from "@/api";
|
import { ErrorPayload, useApi } from "@/api";
|
||||||
import { EGender, ESourceAuthorization, ICreateAuthorizePayload } from "@/api/resources/User";
|
import { EGender, ESourceAuthorization, ICreateAuthorizePayload } from "@/api/resources/User";
|
||||||
import { useAuth } from "@/auth";
|
import { useAuth } from "@/auth";
|
||||||
import { getClientTimezone } from "@/locales";
|
import { getClientTimezone } from "@/locales";
|
||||||
@ -10,6 +10,7 @@ import moment from "moment";
|
|||||||
import { useCallback, useMemo, useState } from "react";
|
import { useCallback, useMemo, useState } from "react";
|
||||||
import { useTranslations } from "@/hooks/translations";
|
import { useTranslations } from "@/hooks/translations";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { Response } from "@/api/resources/Login";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -118,6 +119,47 @@ export const useAuthentication = () => {
|
|||||||
dateOfCheck
|
dateOfCheck
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const authorizationWithPassword = useCallback(async (email: string, password: string) => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
setError(null)
|
||||||
|
const payload = {
|
||||||
|
email,
|
||||||
|
locale,
|
||||||
|
timezone: getClientTimezone(),
|
||||||
|
password
|
||||||
|
}
|
||||||
|
const loginResult = await api.login(payload);
|
||||||
|
const token = "token" in loginResult ? loginResult.token : null;
|
||||||
|
const userId = "userId" in loginResult ? loginResult.userId : null;
|
||||||
|
const status = "status" in loginResult ? loginResult.status : null;
|
||||||
|
const message = "message" in loginResult ? loginResult.message : null;
|
||||||
|
if (!token) {
|
||||||
|
return {
|
||||||
|
status,
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const { user } = await api.getUser({ token });
|
||||||
|
if (userId?.length) {
|
||||||
|
metricService.userParams({
|
||||||
|
email: user.email,
|
||||||
|
UserID: userId
|
||||||
|
})
|
||||||
|
metricService.setUserID(userId);
|
||||||
|
}
|
||||||
|
signUp(token, user);
|
||||||
|
setToken(token);
|
||||||
|
dispatch(actions.status.update("registred"));
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const response = (error as ErrorPayload<Response>).responseData
|
||||||
|
setError((!!response && "message" in response && response.message) || (error as Error).message);
|
||||||
|
return response
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}, [api, dispatch, locale, signUp])
|
||||||
|
|
||||||
const authorization = useCallback(async (email: string, source: ESourceAuthorization) => {
|
const authorization = useCallback(async (email: string, source: ESourceAuthorization) => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@ -156,8 +198,16 @@ export const useAuthentication = () => {
|
|||||||
error,
|
error,
|
||||||
token,
|
token,
|
||||||
user,
|
user,
|
||||||
authorization
|
authorization,
|
||||||
|
authorizationWithPassword,
|
||||||
}),
|
}),
|
||||||
[isLoading, error, token, user, authorization]
|
[
|
||||||
|
isLoading,
|
||||||
|
error,
|
||||||
|
token,
|
||||||
|
user,
|
||||||
|
authorization,
|
||||||
|
authorizationWithPassword,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -310,6 +310,10 @@ const routes = {
|
|||||||
|
|
||||||
dApiGetRealToken: () => [dApiHost, "users", "auth", "token"].join("/"),
|
dApiGetRealToken: () => [dApiHost, "users", "auth", "token"].join("/"),
|
||||||
|
|
||||||
|
login: () => [dApiHost, "users", "auth", "login"].join("/"),
|
||||||
|
|
||||||
|
resetPassword: () => [dApiHost, "users", "auth", "password"].join("/"),
|
||||||
|
|
||||||
assistants: () => [apiHost, prefix, "ai", "assistants.json"].join("/"),
|
assistants: () => [apiHost, prefix, "ai", "assistants.json"].join("/"),
|
||||||
setExternalChatIdAssistants: (chatId: string) =>
|
setExternalChatIdAssistants: (chatId: string) =>
|
||||||
[apiHost, prefix, "ai", "assistants", chatId, "chats.json"].join("/"),
|
[apiHost, prefix, "ai", "assistants", chatId, "chats.json"].join("/"),
|
||||||
@ -568,7 +572,7 @@ export const getRouteBy = (status: UserStatus): string => {
|
|||||||
return routes.client.genderV1();
|
return routes.client.genderV1();
|
||||||
case "registred":
|
case "registred":
|
||||||
case "unsubscribed":
|
case "unsubscribed":
|
||||||
return routes.client.trialPayment();
|
return routes.client.trialPaymentV1();
|
||||||
case "subscribed":
|
case "subscribed":
|
||||||
return routes.client.home();
|
return routes.client.home();
|
||||||
default:
|
default:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user