Develop
This commit is contained in:
parent
fb4ae000ec
commit
0a315b1b13
@ -32,7 +32,9 @@ import {
|
||||
UserVideos,
|
||||
UserPDF,
|
||||
Locale,
|
||||
Session
|
||||
Session,
|
||||
Login,
|
||||
Password
|
||||
} from './resources'
|
||||
|
||||
const api = {
|
||||
@ -80,6 +82,8 @@ const api = {
|
||||
getPalmistryLines: createMethod<Palmistry.Payload, Palmistry.Response>(Palmistry.createRequest),
|
||||
// New Authorization
|
||||
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
|
||||
getPaywallByPlacementKey: createMethod<Paywall.PayloadGet, Paywall.ResponseGet>(Paywall.createRequestGet),
|
||||
// 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 Locale from "./Locale";
|
||||
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 PaymentSuccessPage from "../PaymentPage/results/SuccessPage";
|
||||
import PaymentFailPage from "../PaymentPage/results/ErrorPage";
|
||||
import AuthPage from "../AuthPage";
|
||||
// import AuthPage from "../AuthPage";
|
||||
import AuthResultPage from "../AuthResultPage";
|
||||
import MagicBallPage from "../pages/MagicBall";
|
||||
import BestiesHoroscopeResult from "../pages/BestiesHoroscopeResult";
|
||||
@ -130,6 +130,7 @@ import AddConsultant from "../palmistry/AdditionalPurchases/pages/AddConsultant"
|
||||
import AddGuides from "../palmistry/AdditionalPurchases/pages/AddGuides";
|
||||
import SkipTrial from "../palmistry/AdditionalPurchases/pages/SkipTrial";
|
||||
import { parseQueryParams } from "@/services/url";
|
||||
import Auth from "../pages/Auth";
|
||||
|
||||
const isProduction = import.meta.env.MODE === "production";
|
||||
|
||||
@ -141,7 +142,10 @@ function App(): JSX.Element {
|
||||
const location = useLocation();
|
||||
const [isSpecialOfferOpen, setIsSpecialOfferOpen] = useState<boolean>(false);
|
||||
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 api = useApi();
|
||||
const dispatch = useDispatch();
|
||||
@ -283,13 +287,13 @@ function App(): JSX.Element {
|
||||
getApng();
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const response = await fetch("/padlock_icon_animation_closing.png");
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
setPadLockApng(parseAPNG(arrayBuffer));
|
||||
})();
|
||||
}, []);
|
||||
// useEffect(() => {
|
||||
// (async () => {
|
||||
// const response = await fetch("/padlock_icon_animation_closing.png");
|
||||
// const arrayBuffer = await response.arrayBuffer();
|
||||
// setPadLockApng(parseAPNG(arrayBuffer));
|
||||
// })();
|
||||
// }, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) return;
|
||||
@ -308,6 +312,7 @@ function App(): JSX.Element {
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
<Route path={routes.client.auth()} element={<Auth />} />
|
||||
<Route path="*" element={<ABDesignV1Routes />} />
|
||||
<Route path={`${palmistryV1Prefix}/*`} element={<PalmistryV1Routes />} />
|
||||
<Route path={`${routes.client.mikeV1()}/*`} element={<MikeV1Routes />} />
|
||||
@ -825,10 +830,10 @@ function App(): JSX.Element {
|
||||
path={routes.client.emailEnter()}
|
||||
element={<EmailEnterPage />}
|
||||
/>
|
||||
<Route
|
||||
{/* <Route
|
||||
path={routes.client.auth()}
|
||||
element={<AuthPage padLockApng={padLockApng} />}
|
||||
/>
|
||||
/> */}
|
||||
<Route
|
||||
path={routes.client.authResult()}
|
||||
element={<AuthResultPage />}
|
||||
|
||||
@ -141,6 +141,9 @@ function HomePage(): JSX.Element {
|
||||
token,
|
||||
key,
|
||||
});
|
||||
if (pdf?.status === "not_allowed") {
|
||||
return pdf;
|
||||
}
|
||||
if (!pdf?.url?.length || pdf.status !== "ready") {
|
||||
await sleep(5000);
|
||||
return getUserPDF(key);
|
||||
|
||||
@ -18,6 +18,7 @@ import { usePreloadImages } from "@/hooks/preload/images";
|
||||
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
|
||||
import { useSession } from "@/hooks/session/useSession";
|
||||
import { EGender, ESourceAuthorization } from "@/api/resources/User";
|
||||
import AlreadyHaveAccount from "@/components/ui/AlreadyHaveAccount";
|
||||
|
||||
function GenderPalmistry() {
|
||||
const { translate } = useTranslations(ELocalesPlacement.PalmistryV1);
|
||||
@ -76,6 +77,7 @@ function GenderPalmistry() {
|
||||
</Title>
|
||||
<p className={styles.description}>{translate("/gender.description")}</p>
|
||||
<ChooseGender onSelectGender={selectGender} />
|
||||
<AlreadyHaveAccount />
|
||||
<PrivacyPolicy containerClassName={styles["privacy-policy"]} />
|
||||
{gender && !privacyPolicyChecked && (
|
||||
<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 { useSession } from "@/hooks/session/useSession";
|
||||
import { EGender, ESourceAuthorization } from "@/api/resources/User";
|
||||
import AlreadyHaveAccount from "@/components/ui/AlreadyHaveAccount";
|
||||
|
||||
interface IGenderPageProps {
|
||||
productKey?: EProductKeys;
|
||||
@ -208,6 +209,7 @@ function GenderPage({ productKey }: IGenderPageProps): JSX.Element {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<AlreadyHaveAccount />
|
||||
<PrivacyPolicy containerClassName={styles["privacy-policy"]} />
|
||||
{selectedGender && !privacyPolicyChecked && (
|
||||
<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 { useAuth } from "@/auth";
|
||||
import { getClientTimezone } from "@/locales";
|
||||
@ -10,6 +10,7 @@ import moment from "moment";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { useTranslations } from "@/hooks/translations";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Response } from "@/api/resources/Login";
|
||||
|
||||
|
||||
|
||||
@ -118,6 +119,47 @@ export const useAuthentication = () => {
|
||||
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) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
@ -156,8 +198,16 @@ export const useAuthentication = () => {
|
||||
error,
|
||||
token,
|
||||
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("/"),
|
||||
|
||||
login: () => [dApiHost, "users", "auth", "login"].join("/"),
|
||||
|
||||
resetPassword: () => [dApiHost, "users", "auth", "password"].join("/"),
|
||||
|
||||
assistants: () => [apiHost, prefix, "ai", "assistants.json"].join("/"),
|
||||
setExternalChatIdAssistants: (chatId: string) =>
|
||||
[apiHost, prefix, "ai", "assistants", chatId, "chats.json"].join("/"),
|
||||
@ -568,7 +572,7 @@ export const getRouteBy = (status: UserStatus): string => {
|
||||
return routes.client.genderV1();
|
||||
case "registred":
|
||||
case "unsubscribed":
|
||||
return routes.client.trialPayment();
|
||||
return routes.client.trialPaymentV1();
|
||||
case "subscribed":
|
||||
return routes.client.home();
|
||||
default:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user