main
integrate meditations
This commit is contained in:
parent
12836b372d
commit
8d456c5d0a
@ -201,5 +201,15 @@
|
|||||||
"PalmistryResult": {
|
"PalmistryResult": {
|
||||||
"title": "Your Personality Type",
|
"title": "Your Personality Type",
|
||||||
"error": "Something went wrong. Please try again later."
|
"error": "Something went wrong. Please try again later."
|
||||||
|
},
|
||||||
|
"Breath": {
|
||||||
|
"title": "Stop and breathe to help you relax and focus on what really matters.",
|
||||||
|
"subtitle": "Breathing practice will help improve your aura. Breath in the positive energy, breathe out the negative...",
|
||||||
|
"button": "BEGIN"
|
||||||
|
},
|
||||||
|
"BreathResult": {
|
||||||
|
"breath_relax": "Breath & Relax",
|
||||||
|
"breath_in": "Breath in",
|
||||||
|
"breath_out": "Breath out"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/app/[locale]/(core)/breath/[id]/page.module.scss
Normal file
10
src/app/[locale]/(core)/breath/[id]/page.module.scss
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
.container {
|
||||||
|
width: 100dvw;
|
||||||
|
height: calc(100dvh - 56px);
|
||||||
|
background-color: #000;
|
||||||
|
position: absolute;
|
||||||
|
overflow: hidden;
|
||||||
|
left: 0;
|
||||||
|
top: 56px;
|
||||||
|
padding: 16px 16px 64px;
|
||||||
|
}
|
||||||
23
src/app/[locale]/(core)/breath/[id]/page.tsx
Normal file
23
src/app/[locale]/(core)/breath/[id]/page.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { BreathPage } from "@/components/domains/breath";
|
||||||
|
import { startGeneration } from "@/entities/generations/api";
|
||||||
|
|
||||||
|
import styles from "./page.module.scss";
|
||||||
|
|
||||||
|
export default async function Breath({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ id: string }>;
|
||||||
|
}) {
|
||||||
|
// const { id } = await params;
|
||||||
|
await params;
|
||||||
|
const id = "684a07f6ca4395c285362d4f";
|
||||||
|
const result = await startGeneration({
|
||||||
|
actionType: "palm",
|
||||||
|
actionId: id,
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<section className={styles.container}>
|
||||||
|
<BreathPage id={id} resultId={result?.id} />
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
.container {
|
||||||
|
width: 100dvw;
|
||||||
|
height: calc(100dvh - 56px);
|
||||||
|
background-color: #000;
|
||||||
|
position: absolute;
|
||||||
|
overflow: hidden;
|
||||||
|
left: 0;
|
||||||
|
top: 56px;
|
||||||
|
padding: 16px 16px 64px;
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
import { BreathResultPage } from "@/components/domains/breath";
|
||||||
|
import { loadPalms } from "@/entities/dashboard/loaders";
|
||||||
|
|
||||||
|
import styles from "./page.module.scss";
|
||||||
|
|
||||||
|
export default async function BreathResult({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ id: string; resultId: string }>;
|
||||||
|
}) {
|
||||||
|
const { id, resultId } = await params;
|
||||||
|
// const actions = await loadMeditations();
|
||||||
|
const actions = await loadPalms();
|
||||||
|
const action = actions?.find(action => action._id === id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={styles.container}>
|
||||||
|
<BreathResultPage id={resultId} action={action} />
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
9
src/app/[locale]/(core)/breath/layout.tsx
Normal file
9
src/app/[locale]/(core)/breath/layout.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { FullScreenModalProvider } from "@/providers/fullscreen-blur-modal-provider";
|
||||||
|
|
||||||
|
export default function BreathLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return <FullScreenModalProvider>{children}</FullScreenModalProvider>;
|
||||||
|
}
|
||||||
@ -1,9 +1,10 @@
|
|||||||
import { Suspense, use } from "react";
|
import { Suspense, use } from "react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
import CompatibilityActionFieldsForm, {
|
import {
|
||||||
|
CompatibilityActionFieldsForm,
|
||||||
CompatibilityActionFieldsFormSkeleton,
|
CompatibilityActionFieldsFormSkeleton,
|
||||||
} from "@/components/domains/compatibility/CompatibilityActionFieldsForm/CompatibilityActionFieldsForm";
|
} from "@/components/domains/compatibility";
|
||||||
import { Typography } from "@/components/ui";
|
import { Typography } from "@/components/ui";
|
||||||
import { loadCompatibilityActionFields } from "@/entities/compatibilityActionFields/loaders";
|
import { loadCompatibilityActionFields } from "@/entities/compatibilityActionFields/loaders";
|
||||||
import { loadCompatibility } from "@/entities/dashboard/loaders";
|
import { loadCompatibility } from "@/entities/dashboard/loaders";
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { use } from "react";
|
import { use } from "react";
|
||||||
|
|
||||||
import CompatibilityResultPage from "@/components/domains/compatibility/CompatibilityResultPage/CompatibilityResultPage";
|
import { CompatibilityResultPage } from "@/components/domains/compatibility";
|
||||||
import { Typography } from "@/components/ui";
|
import { Typography } from "@/components/ui";
|
||||||
import { loadCompatibility } from "@/entities/dashboard/loaders";
|
import { loadCompatibility } from "@/entities/dashboard/loaders";
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import PalmistryResultPage from "@/components/domains/palmistry/PalmistryResultPage/PalmistryResultPage";
|
import { PalmistryResultPage } from "@/components/domains/palmistry";
|
||||||
import { Typography } from "@/components/ui";
|
import { Typography } from "@/components/ui";
|
||||||
import { loadPalms } from "@/entities/dashboard/loaders";
|
import { loadPalms } from "@/entities/dashboard/loaders";
|
||||||
import { startGeneration } from "@/entities/generations/api";
|
import { startGeneration } from "@/entities/generations/api";
|
||||||
|
|||||||
30
src/components/domains/breath/BreathPage/BreathPage.tsx
Normal file
30
src/components/domains/breath/BreathPage/BreathPage.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useCallback, useEffect } from "react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
import { useFullScreenModal } from "@/providers/fullscreen-blur-modal-provider";
|
||||||
|
import { ROUTES } from "@/shared/constants/client-routes";
|
||||||
|
|
||||||
|
import { StartBreathModalChild } from "..";
|
||||||
|
|
||||||
|
interface BreathPageProps {
|
||||||
|
id: string;
|
||||||
|
resultId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BreathPage({ id, resultId }: BreathPageProps) {
|
||||||
|
const router = useRouter();
|
||||||
|
const { openModal, closeModal } = useFullScreenModal();
|
||||||
|
|
||||||
|
const handleBegin = useCallback(() => {
|
||||||
|
router.push(ROUTES.breathResult(id, resultId));
|
||||||
|
closeModal();
|
||||||
|
}, [closeModal, id, resultId, router]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
openModal(<StartBreathModalChild handleBegin={handleBegin} />);
|
||||||
|
}, [handleBegin, openModal]);
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
@ -0,0 +1,105 @@
|
|||||||
|
.title {
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result {
|
||||||
|
line-height: 25px;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loadingContainer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breathRelax {
|
||||||
|
animation-name: breath-relax;
|
||||||
|
animation-duration: 10s;
|
||||||
|
animation-iteration-count: 1;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textPosition {
|
||||||
|
position: absolute;
|
||||||
|
bottom: calc(50dvh - 56px);
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.breathIn {
|
||||||
|
animation-name: breath-in;
|
||||||
|
animation-duration: 10s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breathOut {
|
||||||
|
animation-name: breath-out;
|
||||||
|
animation-duration: 10s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes breath-relax {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
5% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
10% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
95% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
scale: 1;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes breath-in {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
scale: 1;
|
||||||
|
}
|
||||||
|
10% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0;
|
||||||
|
scale: 2;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes breath-out {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
scale: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0;
|
||||||
|
scale: 2;
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
90% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
scale: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,136 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
import { Spinner, Typography } from "@/components/ui";
|
||||||
|
import { Action } from "@/entities/dashboard/types";
|
||||||
|
import { useGenerationPolling } from "@/hooks/generation/useGenerationPolling";
|
||||||
|
import { useToast } from "@/providers/toast-provider";
|
||||||
|
|
||||||
|
import styles from "./BreathResultPage.module.scss";
|
||||||
|
|
||||||
|
interface BreathResultPageProps {
|
||||||
|
id: string;
|
||||||
|
action: Action | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BreathResultPage({
|
||||||
|
id,
|
||||||
|
action,
|
||||||
|
}: BreathResultPageProps) {
|
||||||
|
const t = useTranslations("BreathResult");
|
||||||
|
const { data, error, isLoading } = useGenerationPolling(id);
|
||||||
|
const { addToast } = useToast();
|
||||||
|
|
||||||
|
const [animationState, setAnimationState] = useState<
|
||||||
|
"preview" | "breath" | "result"
|
||||||
|
>("preview");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (animationState === "preview") {
|
||||||
|
const previewTimeOut = setTimeout(() => {
|
||||||
|
setAnimationState("breath");
|
||||||
|
}, 10_000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(previewTimeOut);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (animationState === "breath") {
|
||||||
|
const breathTimeOut = setTimeout(() => {
|
||||||
|
setAnimationState("result");
|
||||||
|
}, 40_000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(breathTimeOut);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [animationState]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (error) {
|
||||||
|
addToast({
|
||||||
|
variant: "error",
|
||||||
|
message: t("error"),
|
||||||
|
duration: 5000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [error]);
|
||||||
|
|
||||||
|
if (isLoading && animationState === "result") {
|
||||||
|
return (
|
||||||
|
<div className={styles.loadingContainer}>
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (animationState === "result") {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Typography
|
||||||
|
as="h1"
|
||||||
|
size="xl"
|
||||||
|
weight="semiBold"
|
||||||
|
color="white"
|
||||||
|
className={styles.title}
|
||||||
|
>
|
||||||
|
{action?.title}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
as="p"
|
||||||
|
size="lg"
|
||||||
|
align="left"
|
||||||
|
color="white"
|
||||||
|
className={styles.result}
|
||||||
|
>
|
||||||
|
{data?.result}
|
||||||
|
</Typography>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{animationState === "preview" && (
|
||||||
|
<Typography
|
||||||
|
as="h2"
|
||||||
|
size="xl"
|
||||||
|
color="white"
|
||||||
|
className={clsx(styles.breathRelax, styles.textPosition)}
|
||||||
|
>
|
||||||
|
{t("breath_relax")}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{animationState === "breath" && (
|
||||||
|
<>
|
||||||
|
<div className={styles.textPosition}>
|
||||||
|
<Typography
|
||||||
|
as="h2"
|
||||||
|
size="xl"
|
||||||
|
color="white"
|
||||||
|
className={styles.breathIn}
|
||||||
|
>
|
||||||
|
{t("breath_in")}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<div className={styles.textPosition}>
|
||||||
|
<Typography
|
||||||
|
as="h2"
|
||||||
|
size="xl"
|
||||||
|
color="white"
|
||||||
|
className={styles.breathOut}
|
||||||
|
>
|
||||||
|
{t("breath_out")}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #0000009e;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
flex: 1 1;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 52px 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #c2c2c3;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
padding: 16px 32px;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 172px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.symbol {
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
animation-name: appearance;
|
||||||
|
animation-timing-function: ease;
|
||||||
|
animation-duration: 0.3s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes appearance {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
import { Button, Typography } from "@/components/ui";
|
||||||
|
|
||||||
|
import styles from "./StartBreathModalChild.module.scss";
|
||||||
|
|
||||||
|
interface IStartBreathModalChildProps {
|
||||||
|
handleBegin: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function StartBreathModalChild({ handleBegin }: IStartBreathModalChildProps) {
|
||||||
|
const t = useTranslations("Breath");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={styles.container}>
|
||||||
|
<div className={styles.text}>
|
||||||
|
<Typography
|
||||||
|
as="h4"
|
||||||
|
weight="semiBold"
|
||||||
|
color="white"
|
||||||
|
className={styles.title}
|
||||||
|
>
|
||||||
|
{t("title")
|
||||||
|
.split("")
|
||||||
|
.map((symbol, index) => (
|
||||||
|
<span
|
||||||
|
className={styles.symbol}
|
||||||
|
style={{ animationDelay: `${index * 0.05}s` }}
|
||||||
|
key={index}
|
||||||
|
>
|
||||||
|
{symbol}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</Typography>
|
||||||
|
<Typography as="h4" className={styles.subtitle}>
|
||||||
|
{t("subtitle")
|
||||||
|
.split("")
|
||||||
|
.map((symbol, index) => (
|
||||||
|
<span
|
||||||
|
className={styles.symbol}
|
||||||
|
style={{
|
||||||
|
animationDelay: `${
|
||||||
|
(t("title").split("").length + index) * 0.05
|
||||||
|
}s`,
|
||||||
|
}}
|
||||||
|
key={index}
|
||||||
|
>
|
||||||
|
{symbol}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<Button className={styles.button} onClick={handleBegin}>
|
||||||
|
<Typography weight="bold" color="white">
|
||||||
|
{t("button")}
|
||||||
|
</Typography>
|
||||||
|
</Button>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StartBreathModalChild;
|
||||||
3
src/components/domains/breath/index.ts
Normal file
3
src/components/domains/breath/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { default as BreathPage } from "./BreathPage/BreathPage";
|
||||||
|
export { default as BreathResultPage } from "./BreathResultPage/BreathResultPage";
|
||||||
|
export { default as StartBreathModalChild } from "./StartBreathModalChild/StartBreathModalChild";
|
||||||
5
src/components/domains/compatibility/index.ts
Normal file
5
src/components/domains/compatibility/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export {
|
||||||
|
default as CompatibilityActionFieldsForm,
|
||||||
|
CompatibilityActionFieldsFormSkeleton,
|
||||||
|
} from "./CompatibilityActionFieldsForm/CompatibilityActionFieldsForm";
|
||||||
|
export { default as CompatibilityResultPage } from "./CompatibilityResultPage/CompatibilityResultPage";
|
||||||
@ -1,7 +1,9 @@
|
|||||||
import { use } from "react";
|
import { use } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
import { Grid, Section, Skeleton } from "@/components/ui";
|
import { Grid, Section, Skeleton } from "@/components/ui";
|
||||||
import { Action } from "@/entities/dashboard/types";
|
import { Action } from "@/entities/dashboard/types";
|
||||||
|
import { ROUTES } from "@/shared/constants/client-routes";
|
||||||
|
|
||||||
import { MeditationCard } from "../../cards";
|
import { MeditationCard } from "../../cards";
|
||||||
|
|
||||||
@ -19,7 +21,9 @@ export default function MeditationSection({
|
|||||||
<Section title="Meditations" contentClassName={styles.sectionContent}>
|
<Section title="Meditations" contentClassName={styles.sectionContent}>
|
||||||
<Grid columns={columns} className={styles.grid}>
|
<Grid columns={columns} className={styles.grid}>
|
||||||
{meditations.map(meditation => (
|
{meditations.map(meditation => (
|
||||||
<MeditationCard key={meditation._id} {...meditation} />
|
<Link href={ROUTES.breath(meditation._id)} key={meditation._id}>
|
||||||
|
<MeditationCard key={meditation._id} {...meditation} />
|
||||||
|
</Link>
|
||||||
))}
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Section>
|
</Section>
|
||||||
|
|||||||
1
src/components/domains/palmistry/index.ts
Normal file
1
src/components/domains/palmistry/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default as PalmistryResultPage } from "./PalmistryResultPage/PalmistryResultPage";
|
||||||
@ -6,4 +6,5 @@
|
|||||||
0px 10px 15px 0px rgba(0, 0, 0, 0.1),
|
0px 10px 15px 0px rgba(0, 0, 0, 0.1),
|
||||||
0px 4px 6px 0px rgba(0, 0, 0, 0.1);
|
0px 4px 6px 0px rgba(0, 0, 0, 0.1);
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,68 @@
|
|||||||
|
.modal {
|
||||||
|
width: 100%;
|
||||||
|
height: 100dvh;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 8888;
|
||||||
|
/* background-color: #000; */
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transition: opacity 3s ease;
|
||||||
|
-moz-transition: opacity 3s ease;
|
||||||
|
-ms-transition: opacity 3s ease;
|
||||||
|
-o-transition: opacity 3s ease;
|
||||||
|
transition: opacity 3s ease;
|
||||||
|
will-change: opacity;
|
||||||
|
animation: disappearance 3s ease;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.open {
|
||||||
|
opacity: 1;
|
||||||
|
animation: appearance 3s ease;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
animation: appearance-content 3s ease;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes appearance {
|
||||||
|
0% {
|
||||||
|
-webkit-backdrop-filter: blur(0);
|
||||||
|
backdrop-filter: blur(0);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
-webkit-backdrop-filter: blur(50px);
|
||||||
|
backdrop-filter: blur(50px);
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes appearance-content {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes disappearance {
|
||||||
|
0% {
|
||||||
|
-webkit-backdrop-filter: blur(50px);
|
||||||
|
backdrop-filter: blur(50px);
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
-webkit-backdrop-filter: blur(0);
|
||||||
|
backdrop-filter: blur(0);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
import styles from "./FullScreenBlurModal.module.scss";
|
||||||
|
|
||||||
|
interface FullScreenBlurModalProps {
|
||||||
|
className?: string;
|
||||||
|
classNameContent?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
children: React.ReactNode;
|
||||||
|
isOpen: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FullScreenBlurModal({
|
||||||
|
className = "",
|
||||||
|
classNameContent = "",
|
||||||
|
children,
|
||||||
|
style,
|
||||||
|
isOpen,
|
||||||
|
}: FullScreenBlurModalProps) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen) {
|
||||||
|
document.body.classList.add("no-scroll");
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.body.classList.remove("no-scroll");
|
||||||
|
};
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={style}
|
||||||
|
className={clsx(styles.modal, className, isOpen && styles.open)}
|
||||||
|
>
|
||||||
|
<div className={clsx(styles.content, classNameContent)}>{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@ export { default as Button } from "./Button/Button";
|
|||||||
export { default as Card } from "./Card/Card";
|
export { default as Card } from "./Card/Card";
|
||||||
export { default as CircleArrow } from "./CircleArrow/CircleArrow";
|
export { default as CircleArrow } from "./CircleArrow/CircleArrow";
|
||||||
export { default as EmailInput } from "./EmailInput/EmailInput";
|
export { default as EmailInput } from "./EmailInput/EmailInput";
|
||||||
|
export { default as FullScreenBlurModal } from "./FullScreenBlurModal/FullScreenBlurModal";
|
||||||
export { default as GPTAnimationText } from "./GPTAnimationText/GPTAnimationText";
|
export { default as GPTAnimationText } from "./GPTAnimationText/GPTAnimationText";
|
||||||
export { default as Grid } from "./Grid/Grid";
|
export { default as Grid } from "./Grid/Grid";
|
||||||
export { default as Icon } from "./Icon/Icon";
|
export { default as Icon } from "./Icon/Icon";
|
||||||
|
|||||||
58
src/providers/fullscreen-blur-modal-provider.tsx
Normal file
58
src/providers/fullscreen-blur-modal-provider.tsx
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, {
|
||||||
|
createContext,
|
||||||
|
ReactNode,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
|
||||||
|
import { FullScreenBlurModal } from "@/components/ui";
|
||||||
|
|
||||||
|
interface FullScreenModalContextType {
|
||||||
|
openModal: (content: React.ReactNode) => void;
|
||||||
|
closeModal: () => void;
|
||||||
|
isOpen: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FullScreenModalContext = createContext<
|
||||||
|
FullScreenModalContextType | undefined
|
||||||
|
>(undefined);
|
||||||
|
|
||||||
|
export const useFullScreenModal = () => {
|
||||||
|
const context = useContext(FullScreenModalContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error(
|
||||||
|
"useFullScreenModal must be used within FullScreenModalProvider"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface FullScreenModalProviderProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FullScreenModalProvider = ({
|
||||||
|
children,
|
||||||
|
}: FullScreenModalProviderProps) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [content, setContent] = useState<React.ReactNode>(null);
|
||||||
|
|
||||||
|
const openModal = useCallback((modalContent: React.ReactNode) => {
|
||||||
|
setContent(modalContent);
|
||||||
|
setIsOpen(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const closeModal = useCallback(() => {
|
||||||
|
setIsOpen(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FullScreenModalContext.Provider value={{ openModal, closeModal, isOpen }}>
|
||||||
|
{children}
|
||||||
|
<FullScreenBlurModal isOpen={isOpen}>{content}</FullScreenBlurModal>
|
||||||
|
</FullScreenModalContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -10,6 +10,11 @@ const createRoute = (segments: string[]): string => {
|
|||||||
export const ROUTES = {
|
export const ROUTES = {
|
||||||
home: () => createRoute([]),
|
home: () => createRoute([]),
|
||||||
|
|
||||||
|
// Breath
|
||||||
|
breath: (id: string) => createRoute(["breath", id]),
|
||||||
|
breathResult: (id: string, resultId: string) =>
|
||||||
|
createRoute(["breath", id, "result", resultId]),
|
||||||
|
|
||||||
// Compatibility
|
// Compatibility
|
||||||
compatibility: (id: string) => createRoute(["compatibility", id]),
|
compatibility: (id: string) => createRoute(["compatibility", id]),
|
||||||
compatibilityResult: (id: string, resultId: string) =>
|
compatibilityResult: (id: string, resultId: string) =>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user