AW-86-policyMark

This commit is contained in:
Денис Катаев 2024-06-11 17:40:00 +00:00 committed by Daniil Chemerkin
parent 7946e9ffb2
commit cdbf5924a6
13 changed files with 365 additions and 8 deletions

View File

@ -171,6 +171,8 @@ export interface ICreateAuthorizePayload {
source: ESourceAuthorization;
profile?: Partial<ICreateAuthorizeUser>;
partner?: Partial<Exclude<ICreateAuthorizeUser, "relationship_status">>;
sign?: boolean;
signDate?: string;
}
export interface ICreateAuthorizeResponse {

View File

@ -0,0 +1,34 @@
import styles from "./styles.module.css";
interface ICheckboxProps {
checked: boolean;
onChange: () => void;
}
function Checkbox({ checked, onChange }: ICheckboxProps) {
return (
<div className={styles["checkbox-wrapper-4"]}>
<input
checked={checked}
className={styles["inp-cbx"]}
id="morning"
type="checkbox"
onChange={onChange}
/>
<label className={styles["cbx"]} htmlFor="morning">
<span>
<svg width="12px" height="10px">
<use xlinkHref="#check-4"></use>
</svg>
</span>
</label>
<svg className={styles["inline-svg"]}>
<symbol id="check-4" viewBox="0 0 12 10">
<polyline points="1.5 6 4.5 9 10.5 1"></polyline>
</symbol>
</svg>
</div>
);
}
export default Checkbox;

View File

@ -0,0 +1,92 @@
.checkbox-wrapper-4 * {
box-sizing: border-box;
}
.checkbox-wrapper-4 .cbx {
-webkit-user-select: none;
user-select: none;
cursor: pointer;
padding: 2px;
border-radius: 6px;
overflow: hidden;
transition: all 0.2s ease;
display: inline-block;
}
.checkbox-wrapper-4 .cbx span {
float: left;
vertical-align: middle;
transform: translate3d(0, 0, 0);
}
.checkbox-wrapper-4 .cbx span:first-child {
position: relative;
width: 20px;
height: 20px;
border-radius: 4px;
transform: scale(1);
border: 1px solid #484848;
transition: all 0.2s ease;
box-shadow: 0 1px 1px rgba(0,16,75,0.05);
}
.checkbox-wrapper-4 .cbx span:first-child svg {
position: absolute;
top: 50%;
left: 50%;
fill: none;
stroke: #fff;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
stroke-dasharray: 16px;
stroke-dashoffset: 16px;
transition: all 0.3s ease;
transition-delay: 0.1s;
transform: translate3d(-50%, -50%, 0);
}
.checkbox-wrapper-4 .cbx span:last-child {
padding-left: 8px;
line-height: 18px;
}
.checkbox-wrapper-4 .inp-cbx {
position: absolute;
visibility: hidden;
}
.checkbox-wrapper-4 .inp-cbx:checked + .cbx span:first-child {
background: #07f;
border-color: #07f;
animation: wave-4 0.4s ease;
}
.checkbox-wrapper-4 .inp-cbx:checked + .cbx span:first-child svg {
stroke-dashoffset: 0;
}
.checkbox-wrapper-4 .inline-svg {
position: absolute;
width: 0;
height: 0;
pointer-events: none;
user-select: none;
}
@media screen and (max-width: 640px) {
.checkbox-wrapper-4 .cbx {
width: 100%;
display: inline-block;
}
}
@-moz-keyframes wave-4 {
50% {
transform: scale(0.9);
}
}
@-webkit-keyframes wave-4 {
50% {
transform: scale(0.9);
}
}
@-o-keyframes wave-4 {
50% {
transform: scale(0.9);
}
}
@keyframes wave-4 {
50% {
transform: scale(0.9);
}
}

View File

@ -0,0 +1,45 @@
import Checkbox from "../Checkbox";
import styles from "./styles.module.css";
import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from "@/store";
interface IPrivacyPolicyProps {
containerClassName?: string;
}
function PrivacyPolicy({ containerClassName = "" }: IPrivacyPolicyProps) {
const dispatch = useDispatch();
const { checked } = useSelector(selectors.selectPrivacyPolicy);
const handleChange = () => {
dispatch(actions.privacyPolicy.updateChecked(!checked));
};
return (
<div className={`${styles.container} ${containerClassName}`}>
<Checkbox checked={checked} onChange={handleChange} />
<p className={styles.text}>
I agree to the{" "}
<a
href="https://aura.wit.life/privacy"
target="_blank"
rel="noopener noreferrer"
>
Privacy Policy
</a>
,
<a
href="https://aura.wit.life/terms"
target="_blank"
rel="noopener noreferrer"
>
Terms of use
</a>{" "}
and to the use of cookies and tracking technologies, that require your
consent
</p>
</div>
);
}
export default PrivacyPolicy;

View File

@ -0,0 +1,12 @@
.container {
width: 100%;
display: flex;
flex-direction: row;
gap: 8px;
}
.text {
font-size: 14px;
line-height: 125%;
color: #515151;
}

View File

@ -0,0 +1,24 @@
function ErrorIcon() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="12" cy="12" r="11" strokeWidth={1.5} stroke="#e12b2b" />
<rect x="11.4297" y="6.85645" width="1.5" height="8" fill="#e12b2b" />
<rect
x="11.4297"
y="16.5703"
width="1.4"
height="1.4"
rx="0.571429"
fill="#e12b2b"
/>
</svg>
);
}
export default ErrorIcon;

View File

@ -0,0 +1,28 @@
import React from "react";
import ErrorIcon from "./ErrorIcon";
import styles from "./styles.module.css";
interface IToastProps {
variant: "error";
children: React.ReactNode;
classNameContainer?: string;
classNameToast?: string;
}
function Toast({
variant,
children,
classNameContainer = "",
classNameToast = "",
}: IToastProps) {
return (
<div className={`${styles.container} ${classNameContainer}`}>
<div className={`${styles.toast} ${styles[variant]} ${classNameToast}`}>
{variant === "error" && <ErrorIcon />}
{children}
</div>
</div>
);
}
export default Toast;

View File

@ -0,0 +1,26 @@
.toast {
width: 100%;
display: grid;
grid-template-columns: 24px 1fr;
gap: 6px;
align-items: center;
padding: 16px;
border-radius: 12px;
font-size: 14px;
color: #000;
animation: appearance .8s linear(0 0%, 0 1.8%, 0.01 3.6%, 0.08 10.03%, 0.15 14.25%, 0.2 14.34%, 0.31 14.14%, 0.41 17.21%, 0.49 19.04%, 0.58 20.56%, 0.66 22.07%, 0.76 23.87%, 0.84 26.07%, 0.93 28.04%, 1.03 31.14%, 1.09 37.31%, 1.09 44.28%, 1.02 49.41%, 0.96 55%, 0.98 64%, 0.99 74.4%, 1 86.4%, 1 100%);
animation-fill-mode: forwards;
}
.toast.error {
background-color: #ffdcdc;
}
@keyframes appearance {
0% {
transform: translateY(100%);
}
100% {
transform: translateY(0);
}
}

View File

@ -3,14 +3,16 @@ import Title from "@/components/Title";
import { Gender } from "@/data";
import { EProductKeys } from "@/data/products";
import routes from "@/routes";
import { actions } from "@/store";
import { actions, selectors } from "@/store";
import { useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";
import BackgroundTopBlob from "../../ui/BackgroundTopBlob";
import { useDynamicSize } from "@/hooks/useDynamicSize";
import Header from "../../components/Header";
import { genders } from "../../data/genders";
import PrivacyPolicy from "../../components/PrivacyPolicy";
import Toast from "../../components/Toast";
interface IGenderPageProps {
productKey?: EProductKeys;
@ -22,16 +24,26 @@ function GenderPage({ productKey }: IGenderPageProps): JSX.Element {
const { targetId } = useParams();
const { width: pageWidth, elementRef: pageRef } = useDynamicSize({});
const [selectedGender, setSelectedGender] = useState<Gender | null>(null);
const { checked: privacyPolicyChecked } = useSelector(
selectors.selectPrivacyPolicy
);
useEffect(() => {
const isShowTryApp = targetId === "i";
dispatch(actions.userConfig.addIsShowTryApp(isShowTryApp));
}, [dispatch, targetId]);
const selectGender = async (gender: Gender) => {
setSelectedGender(gender);
useEffect(() => {
if (privacyPolicyChecked && selectedGender) {
handleNext();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [privacyPolicyChecked, selectedGender]);
const handleNext = async () => {
if (!selectedGender) return;
await new Promise((resolve) => setTimeout(resolve, 1000));
dispatch(actions.questionnaire.update({ gender: gender.id }));
dispatch(actions.questionnaire.update({ gender: selectedGender.id }));
if (productKey === EProductKeys["moons.pdf.aura"]) {
return navigate(routes.client.epeBirthdate());
}
@ -41,6 +53,18 @@ function GenderPage({ productKey }: IGenderPageProps): JSX.Element {
navigate(`/v1/questionnaire/profile/flowChoice`);
};
const selectGender = async (gender: Gender) => {
if (selectedGender?.id === gender.id) {
setSelectedGender(null);
} else {
setSelectedGender(gender);
}
if (!privacyPolicyChecked) {
return;
}
handleNext();
};
const getButtonBGColor = (gender: Gender): string => {
const { colorAssociation } = gender;
if (Array.isArray(colorAssociation)) {
@ -114,6 +138,12 @@ function GenderPage({ productKey }: IGenderPageProps): JSX.Element {
</div>
))}
</div>
<PrivacyPolicy containerClassName={styles["privacy-policy"]} />
{selectedGender && !privacyPolicyChecked && (
<Toast classNameContainer={styles["toast-container"]} variant="error">
To continue, please accept our terms and policies
</Toast>
)}
</section>
);
}

View File

@ -106,18 +106,43 @@
.gender--selected {
transform: scale(1.1);
/* animation: gender-click 1s linear; */
/* animation: gender-click 1.4s linear; */
}
.gender--selected .gender__slide-element {
left: calc(100% - 27px - 10px);
/* animation: gender-slide 1.4s linear; */
}
.privacy-policy {
max-width: 316px;
margin-top: 26px;
}
.toast-container {
margin-top: 16px;
}
@keyframes gender-click {
0% {
transform: scale(1);
}
100% {
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
@keyframes gender-slide {
0% {
left: 10px;
}
50% {
left: calc(100% - 27px - 10px);
}
100% {
left: 10px;
}
}

View File

@ -33,6 +33,7 @@ export const useAuthentication = () => {
partnerBirthPlace,
partnerBirthtime,
} = useSelector(selectors.selectQuestionnaire)
const { checked, dateOfCheck } = useSelector(selectors.selectPrivacyPolicy)
const birthdateFromForm = useSelector(selectors.selectBirthdate);
const birthtimeFromForm = useSelector(selectors.selectBirthtime);
@ -94,7 +95,9 @@ export const useAuthentication = () => {
birthplace: {
address: partnerBirthPlace,
},
}
},
sign: checked,
signDate: dateOfCheck
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
@ -108,6 +111,8 @@ export const useAuthentication = () => {
partnerName,
username,
birthtime,
checked,
dateOfCheck
]);
const authorization = useCallback(async (email: string, source: ESourceAuthorization) => {

View File

@ -69,6 +69,7 @@ import palmistry, {
selectPalmistryLines,
} from "./palmistry";
import { selectPaywallsIsMustUpdate, selectPaywalls } from "./paywalls";
import privacyPolicy, { actions as privacyPolicyActions, selectPrivacyPolicy } from "./privacyPolicy";
const preloadedState = loadStore();
export const actions = {
@ -88,6 +89,7 @@ export const actions = {
questionnaire: questionnaireActions,
userConfig: userConfigActions,
palmistry: palmistryActions,
privacyPolicy: privacyPolicyActions,
reset: createAction("reset"),
};
export const selectors = {
@ -120,6 +122,7 @@ export const selectors = {
selectPalmistryLines,
selectPaywalls,
selectPaywallsIsMustUpdate,
selectPrivacyPolicy,
...formSelectors,
};
@ -140,6 +143,7 @@ export const reducer = combineReducers({
userConfig,
palmistry,
paywalls,
privacyPolicy
});
export type RootState = ReturnType<typeof reducer>;

View File

@ -0,0 +1,30 @@
import { createSlice, createSelector } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'
interface IPrivacyPolicy {
checked: boolean
dateOfCheck: string
}
const initialState: IPrivacyPolicy = {
checked: false,
dateOfCheck: '',
}
const privacyPolicySlice = createSlice({
name: 'privacyPolicy',
initialState,
reducers: {
updateChecked(state, action: PayloadAction<boolean>) {
return { ...state, checked: action.payload, dateOfCheck: new Date().toISOString() }
},
},
extraReducers: (builder) => builder.addCase('reset', () => initialState),
})
export const { actions } = privacyPolicySlice
export const selectPrivacyPolicy = createSelector(
(state: { privacyPolicy: IPrivacyPolicy }) => state.privacyPolicy,
(privacyPolicy) => privacyPolicy
)
export default privacyPolicySlice.reducer