Merge branch 'preview/discount-pages' into 'main'
Preview/discount pages See merge request witapp/aura-webapp!50
This commit is contained in:
commit
bd995211c5
@ -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 });
|
||||
};
|
||||
|
||||
@ -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>;
|
||||
}
|
||||
|
||||
@ -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 />
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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 &&
|
||||
|
||||
@ -17,7 +17,6 @@ function OnboardingPage() {
|
||||
}, [navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("onboardingTitles", activeIndexTitle);
|
||||
setPeriodClassName("to-nontransparent");
|
||||
classNameTimeOut.current = setTimeout(() => {
|
||||
setPeriodClassName("to-transparent");
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -121,3 +121,7 @@
|
||||
min-height: 0;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.email {
|
||||
font-weight: 500;
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user