main
integrate meditations
This commit is contained in:
parent
12836b372d
commit
8d456c5d0a
@ -201,5 +201,15 @@
|
||||
"PalmistryResult": {
|
||||
"title": "Your Personality Type",
|
||||
"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 { useTranslations } from "next-intl";
|
||||
|
||||
import CompatibilityActionFieldsForm, {
|
||||
import {
|
||||
CompatibilityActionFieldsForm,
|
||||
CompatibilityActionFieldsFormSkeleton,
|
||||
} from "@/components/domains/compatibility/CompatibilityActionFieldsForm/CompatibilityActionFieldsForm";
|
||||
} from "@/components/domains/compatibility";
|
||||
import { Typography } from "@/components/ui";
|
||||
import { loadCompatibilityActionFields } from "@/entities/compatibilityActionFields/loaders";
|
||||
import { loadCompatibility } from "@/entities/dashboard/loaders";
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { use } from "react";
|
||||
|
||||
import CompatibilityResultPage from "@/components/domains/compatibility/CompatibilityResultPage/CompatibilityResultPage";
|
||||
import { CompatibilityResultPage } from "@/components/domains/compatibility";
|
||||
import { Typography } from "@/components/ui";
|
||||
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 { loadPalms } from "@/entities/dashboard/loaders";
|
||||
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 Link from "next/link";
|
||||
|
||||
import { Grid, Section, Skeleton } from "@/components/ui";
|
||||
import { Action } from "@/entities/dashboard/types";
|
||||
import { ROUTES } from "@/shared/constants/client-routes";
|
||||
|
||||
import { MeditationCard } from "../../cards";
|
||||
|
||||
@ -19,7 +21,9 @@ export default function MeditationSection({
|
||||
<Section title="Meditations" contentClassName={styles.sectionContent}>
|
||||
<Grid columns={columns} className={styles.grid}>
|
||||
{meditations.map(meditation => (
|
||||
<MeditationCard key={meditation._id} {...meditation} />
|
||||
<Link href={ROUTES.breath(meditation._id)} key={meditation._id}>
|
||||
<MeditationCard key={meditation._id} {...meditation} />
|
||||
</Link>
|
||||
))}
|
||||
</Grid>
|
||||
</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 4px 6px 0px rgba(0, 0, 0, 0.1);
|
||||
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 CircleArrow } from "./CircleArrow/CircleArrow";
|
||||
export { default as EmailInput } from "./EmailInput/EmailInput";
|
||||
export { default as FullScreenBlurModal } from "./FullScreenBlurModal/FullScreenBlurModal";
|
||||
export { default as GPTAnimationText } from "./GPTAnimationText/GPTAnimationText";
|
||||
export { default as Grid } from "./Grid/Grid";
|
||||
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 = {
|
||||
home: () => createRoute([]),
|
||||
|
||||
// Breath
|
||||
breath: (id: string) => createRoute(["breath", id]),
|
||||
breathResult: (id: string, resultId: string) =>
|
||||
createRoute(["breath", id, "result", resultId]),
|
||||
|
||||
// Compatibility
|
||||
compatibility: (id: string) => createRoute(["compatibility", id]),
|
||||
compatibilityResult: (id: string, resultId: string) =>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user