main
add navbar & advisers page & compatibility & meditation fix hydration error on /compatibility/[id]
This commit is contained in:
parent
b1c8d4f910
commit
37841bb92a
@ -202,12 +202,12 @@
|
||||
"title": "Your Personality Type",
|
||||
"error": "Something went wrong. Please try again later."
|
||||
},
|
||||
"Breath": {
|
||||
"Meditation": {
|
||||
"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": {
|
||||
"MeditationResult": {
|
||||
"breath_relax": "Breath & Relax",
|
||||
"breath_in": "Breath in",
|
||||
"breath_out": "Breath out"
|
||||
|
||||
15
src/app/[locale]/(core)/advisers/page.tsx
Normal file
15
src/app/[locale]/(core)/advisers/page.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { Suspense } from "react";
|
||||
|
||||
import {
|
||||
AdvisersSection,
|
||||
AdvisersSectionSkeleton,
|
||||
} from "@/components/domains/dashboard";
|
||||
import { loadAssistants } from "@/entities/dashboard/loaders";
|
||||
|
||||
export default function Advisers() {
|
||||
return (
|
||||
<Suspense fallback={<AdvisersSectionSkeleton />}>
|
||||
<AdvisersSection promise={loadAssistants()} gridDisplayMode="vertical" />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
18
src/app/[locale]/(core)/compatibility/page.tsx
Normal file
18
src/app/[locale]/(core)/compatibility/page.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { Suspense } from "react";
|
||||
|
||||
import {
|
||||
CompatibilitySection,
|
||||
CompatibilitySectionSkeleton,
|
||||
} from "@/components/domains/dashboard";
|
||||
import { loadCompatibility } from "@/entities/dashboard/loaders";
|
||||
|
||||
export default function Compatibility() {
|
||||
return (
|
||||
<Suspense fallback={<CompatibilitySectionSkeleton />}>
|
||||
<CompatibilitySection
|
||||
promise={loadCompatibility()}
|
||||
gridDisplayMode="vertical"
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
.main {
|
||||
padding: 16px;
|
||||
padding-bottom: 64px;
|
||||
padding-bottom: 120px;
|
||||
}
|
||||
|
||||
.navBar {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { DrawerProvider, NavigationBar } from "@/components/layout";
|
||||
import { DrawerProvider, Header } from "@/components/layout";
|
||||
import NavigationBar from "@/components/layout/NavigationBar/NavigationBar";
|
||||
|
||||
import styles from "./layout.module.scss";
|
||||
|
||||
@ -9,8 +10,9 @@ export default function CoreLayout({
|
||||
}>) {
|
||||
return (
|
||||
<DrawerProvider>
|
||||
<NavigationBar className={styles.navBar} />
|
||||
<Header className={styles.navBar} />
|
||||
<main className={styles.main}>{children}</main>
|
||||
<NavigationBar />
|
||||
</DrawerProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { BreathPage } from "@/components/domains/breath";
|
||||
import { MeditationPage } from "@/components/domains/meditation";
|
||||
import { startGeneration } from "@/entities/generations/api";
|
||||
|
||||
import styles from "./page.module.scss";
|
||||
|
||||
export default async function Breath({
|
||||
export default async function Meditation({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ id: string }>;
|
||||
@ -17,7 +17,7 @@ export default async function Breath({
|
||||
});
|
||||
return (
|
||||
<section className={styles.container}>
|
||||
<BreathPage id={id} resultId={result?.id} />
|
||||
<MeditationPage id={id} resultId={result?.id} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
import { BreathResultPage } from "@/components/domains/breath";
|
||||
import { MeditationResultPage } from "@/components/domains/meditation";
|
||||
import { loadPalms } from "@/entities/dashboard/loaders";
|
||||
|
||||
import styles from "./page.module.scss";
|
||||
|
||||
export default async function BreathResult({
|
||||
export default async function MeditationResult({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ id: string; resultId: string }>;
|
||||
@ -15,7 +15,7 @@ export default async function BreathResult({
|
||||
|
||||
return (
|
||||
<section className={styles.container}>
|
||||
<BreathResultPage id={resultId} action={action} />
|
||||
<MeditationResultPage id={resultId} action={action} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import { FullScreenModalProvider } from "@/providers/fullscreen-blur-modal-provider";
|
||||
|
||||
export default function BreathLayout({
|
||||
export default function MeditationLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
18
src/app/[locale]/(core)/meditation/page.tsx
Normal file
18
src/app/[locale]/(core)/meditation/page.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { Suspense } from "react";
|
||||
|
||||
import {
|
||||
MeditationSection,
|
||||
MeditationSectionSkeleton,
|
||||
} from "@/components/domains/dashboard";
|
||||
import { loadMeditations } from "@/entities/dashboard/loaders";
|
||||
|
||||
export default function Meditation() {
|
||||
return (
|
||||
<Suspense fallback={<MeditationSectionSkeleton />}>
|
||||
<MeditationSection
|
||||
promise={loadMeditations()}
|
||||
gridDisplayMode="vertical"
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
export { default as BreathPage } from "./BreathPage/BreathPage";
|
||||
export { default as BreathResultPage } from "./BreathResultPage/BreathResultPage";
|
||||
export { default as StartBreathModalChild } from "./StartBreathModalChild/StartBreathModalChild";
|
||||
@ -1,7 +1,6 @@
|
||||
import Image from "next/image";
|
||||
|
||||
import { Card, MetaLabel, Typography } from "@/components/ui";
|
||||
import { IconName } from "@/components/ui/Icon/Icon";
|
||||
import { Card, IconName, MetaLabel, Typography } from "@/components/ui";
|
||||
import { Action } from "@/entities/dashboard/types";
|
||||
|
||||
import styles from "./CompatibilityCard.module.scss";
|
||||
|
||||
@ -1,27 +1,41 @@
|
||||
import Image from "next/image";
|
||||
import clsx from "clsx";
|
||||
|
||||
import { Button, Card, Icon, MetaLabel, Typography } from "@/components/ui";
|
||||
import { IconName } from "@/components/ui/Icon/Icon";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Icon,
|
||||
IconName,
|
||||
MetaLabel,
|
||||
Typography,
|
||||
} from "@/components/ui";
|
||||
import { Action } from "@/entities/dashboard/types";
|
||||
|
||||
import styles from "./MeditationCard.module.scss";
|
||||
|
||||
type MeditationCardProps = Action;
|
||||
interface MeditationCardProps extends Action {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function MeditationCard({
|
||||
imageUrl,
|
||||
title,
|
||||
type,
|
||||
minutes,
|
||||
className,
|
||||
}: MeditationCardProps) {
|
||||
return (
|
||||
<Card className={styles.card}>
|
||||
<Card className={clsx(styles.card, className)}>
|
||||
<Image
|
||||
className={styles.meditationImage}
|
||||
src={imageUrl}
|
||||
alt="Meditation image"
|
||||
width={342}
|
||||
height={216}
|
||||
style={{
|
||||
width: "auto",
|
||||
height: "216px",
|
||||
}}
|
||||
/>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.info}>
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import Image from "next/image";
|
||||
|
||||
import { Card, MetaLabel, Typography } from "@/components/ui";
|
||||
import { IconName } from "@/components/ui/Icon/Icon";
|
||||
import { Card, IconName, MetaLabel, Typography } from "@/components/ui";
|
||||
import { Action } from "@/entities/dashboard/types";
|
||||
|
||||
import styles from "./PalmCard.module.scss";
|
||||
|
||||
@ -9,6 +9,10 @@
|
||||
|
||||
.grid {
|
||||
padding-right: 16px;
|
||||
|
||||
&.vertical {
|
||||
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton.skeleton {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { use } from "react";
|
||||
import clsx from "clsx";
|
||||
|
||||
import { Grid, Section, Skeleton } from "@/components/ui";
|
||||
import { Assistant } from "@/entities/dashboard/types";
|
||||
@ -7,17 +8,24 @@ import { AdviserCard } from "../../cards";
|
||||
|
||||
import styles from "./AdvisersSection.module.scss";
|
||||
|
||||
interface AdvisersSectionProps {
|
||||
promise: Promise<Assistant[]>;
|
||||
gridDisplayMode?: "vertical" | "horizontal";
|
||||
}
|
||||
|
||||
export default function AdvisersSection({
|
||||
promise,
|
||||
}: {
|
||||
promise: Promise<Assistant[]>;
|
||||
}) {
|
||||
gridDisplayMode = "horizontal",
|
||||
}: AdvisersSectionProps) {
|
||||
const assistants = use(promise);
|
||||
const columns = Math.ceil(assistants?.length / 2);
|
||||
|
||||
return (
|
||||
<Section title="Advisers" contentClassName={styles.sectionContent}>
|
||||
<Grid columns={columns} className={styles.grid}>
|
||||
<Grid
|
||||
columns={columns}
|
||||
className={clsx(styles.grid, styles[gridDisplayMode])}
|
||||
>
|
||||
{assistants.map(adviser => (
|
||||
<AdviserCard key={adviser._id} {...adviser} />
|
||||
))}
|
||||
|
||||
@ -9,6 +9,10 @@
|
||||
|
||||
.grid {
|
||||
padding-right: 16px;
|
||||
|
||||
&.vertical {
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton.skeleton {
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import { use } from "react";
|
||||
import Link from "next/link";
|
||||
import clsx from "clsx";
|
||||
|
||||
import { Grid, Section, Skeleton } from "@/components/ui";
|
||||
import { Action } from "@/entities/dashboard/types";
|
||||
@ -11,17 +12,24 @@ import { CompatibilityCard } from "../../cards";
|
||||
|
||||
import styles from "./CompatibilitySection.module.scss";
|
||||
|
||||
interface CompatibilitySectionProps {
|
||||
promise: Promise<Action[]>;
|
||||
gridDisplayMode?: "vertical" | "horizontal";
|
||||
}
|
||||
|
||||
export default function CompatibilitySection({
|
||||
promise,
|
||||
}: {
|
||||
promise: Promise<Action[]>;
|
||||
}) {
|
||||
gridDisplayMode = "horizontal",
|
||||
}: CompatibilitySectionProps) {
|
||||
const compatibilities = use(promise);
|
||||
const columns = Math.ceil(compatibilities?.length / 2);
|
||||
|
||||
return (
|
||||
<Section title="Compatibility" contentClassName={styles.sectionContent}>
|
||||
<Grid columns={columns} className={styles.grid}>
|
||||
<Grid
|
||||
columns={columns}
|
||||
className={clsx(styles.grid, styles[gridDisplayMode])}
|
||||
>
|
||||
{compatibilities.map(compatibility => (
|
||||
<Link
|
||||
href={ROUTES.compatibility(compatibility._id)}
|
||||
|
||||
@ -9,6 +9,14 @@
|
||||
|
||||
.grid {
|
||||
padding-right: 16px;
|
||||
|
||||
&.vertical {
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cardVertical.cardVertical {
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.skeleton.skeleton {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { use } from "react";
|
||||
import Link from "next/link";
|
||||
import clsx from "clsx";
|
||||
|
||||
import { Grid, Section, Skeleton } from "@/components/ui";
|
||||
import { Action } from "@/entities/dashboard/types";
|
||||
@ -9,20 +10,33 @@ import { MeditationCard } from "../../cards";
|
||||
|
||||
import styles from "./MeditationSection.module.scss";
|
||||
|
||||
interface MeditationSectionProps {
|
||||
promise: Promise<Action[]>;
|
||||
gridDisplayMode?: "vertical" | "horizontal";
|
||||
}
|
||||
|
||||
export default function MeditationSection({
|
||||
promise,
|
||||
}: {
|
||||
promise: Promise<Action[]>;
|
||||
}) {
|
||||
gridDisplayMode = "horizontal",
|
||||
}: MeditationSectionProps) {
|
||||
const meditations = use(promise);
|
||||
const columns = meditations?.length;
|
||||
|
||||
return (
|
||||
<Section title="Meditations" contentClassName={styles.sectionContent}>
|
||||
<Grid columns={columns} className={styles.grid}>
|
||||
<Grid
|
||||
columns={columns}
|
||||
className={clsx(styles.grid, styles[gridDisplayMode])}
|
||||
>
|
||||
{meditations.map(meditation => (
|
||||
<Link href={ROUTES.breath(meditation._id)} key={meditation._id}>
|
||||
<MeditationCard key={meditation._id} {...meditation} />
|
||||
<Link href={ROUTES.meditation(meditation._id)} key={meditation._id}>
|
||||
<MeditationCard
|
||||
className={clsx(
|
||||
gridDisplayMode === "vertical" && styles.cardVertical
|
||||
)}
|
||||
key={meditation._id}
|
||||
{...meditation}
|
||||
/>
|
||||
</Link>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
@ -8,17 +8,17 @@ import { ROUTES } from "@/shared/constants/client-routes";
|
||||
|
||||
import { StartBreathModalChild } from "..";
|
||||
|
||||
interface BreathPageProps {
|
||||
interface MeditationPageProps {
|
||||
id: string;
|
||||
resultId: string;
|
||||
}
|
||||
|
||||
export default function BreathPage({ id, resultId }: BreathPageProps) {
|
||||
export default function MeditationPage({ id, resultId }: MeditationPageProps) {
|
||||
const router = useRouter();
|
||||
const { openModal, closeModal } = useFullScreenModal();
|
||||
|
||||
const handleBegin = useCallback(() => {
|
||||
router.push(ROUTES.breathResult(id, resultId));
|
||||
router.push(ROUTES.meditationResult(id, resultId));
|
||||
closeModal();
|
||||
}, [closeModal, id, resultId, router]);
|
||||
|
||||
@ -9,18 +9,18 @@ import { Action } from "@/entities/dashboard/types";
|
||||
import { useGenerationPolling } from "@/hooks/generation/useGenerationPolling";
|
||||
import { useToast } from "@/providers/toast-provider";
|
||||
|
||||
import styles from "./BreathResultPage.module.scss";
|
||||
import styles from "./MeditationResultPage.module.scss";
|
||||
|
||||
interface BreathResultPageProps {
|
||||
interface MeditationResultPageProps {
|
||||
id: string;
|
||||
action: Action | undefined;
|
||||
}
|
||||
|
||||
export default function BreathResultPage({
|
||||
export default function MeditationResultPage({
|
||||
id,
|
||||
action,
|
||||
}: BreathResultPageProps) {
|
||||
const t = useTranslations("BreathResult");
|
||||
}: MeditationResultPageProps) {
|
||||
const t = useTranslations("MeditationResult");
|
||||
const { data, error, isLoading } = useGenerationPolling(id);
|
||||
const { addToast } = useToast();
|
||||
|
||||
@ -9,7 +9,7 @@ interface IStartBreathModalChildProps {
|
||||
}
|
||||
|
||||
function StartBreathModalChild({ handleBegin }: IStartBreathModalChildProps) {
|
||||
const t = useTranslations("Breath");
|
||||
const t = useTranslations("Meditation");
|
||||
|
||||
return (
|
||||
<section className={styles.container}>
|
||||
3
src/components/domains/meditation/index.ts
Normal file
3
src/components/domains/meditation/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export { default as MeditationPage } from "./MeditationPage/MeditationPage";
|
||||
export { default as MeditationResultPage } from "./MeditationResultPage/MeditationResultPage";
|
||||
export { default as StartBreathModalChild } from "./StartBreathModalChild/StartBreathModalChild";
|
||||
@ -3,8 +3,7 @@
|
||||
import Link from "next/link";
|
||||
import clsx from "clsx";
|
||||
|
||||
import { Button, Icon, Typography } from "@/components/ui";
|
||||
import { IconName } from "@/components/ui/Icon/Icon";
|
||||
import { Button, Icon, IconName, Typography } from "@/components/ui";
|
||||
import { ROUTES } from "@/shared/constants/client-routes";
|
||||
|
||||
import styles from "./Drawer.module.scss";
|
||||
|
||||
29
src/components/layout/Header/Header.module.scss
Normal file
29
src/components/layout/Header/Header.module.scss
Normal file
@ -0,0 +1,29 @@
|
||||
.header {
|
||||
width: 100%;
|
||||
min-height: 56px;
|
||||
height: fit-content;
|
||||
padding: 16px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header > :first-child {
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
.header > :nth-child(2) {
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.header > :nth-child(n + 3) {
|
||||
justify-self: end;
|
||||
display: inline-flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.menuButton.menuButton {
|
||||
padding: 0;
|
||||
width: fit-content;
|
||||
background: none;
|
||||
}
|
||||
36
src/components/layout/Header/Header.tsx
Normal file
36
src/components/layout/Header/Header.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import clsx from "clsx";
|
||||
|
||||
import { Button, Icon, IconName } from "@/components/ui";
|
||||
import { ROUTES } from "@/shared/constants/client-routes";
|
||||
|
||||
import Logo from "../Logo/Logo";
|
||||
|
||||
import styles from "./Header.module.scss";
|
||||
|
||||
import { useDrawer } from "..";
|
||||
|
||||
interface HeaderProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function Header({ className }: HeaderProps) {
|
||||
const { open } = useDrawer();
|
||||
|
||||
return (
|
||||
<header className={clsx(styles.header, className)}>
|
||||
<Button className={styles.menuButton} onClick={open}>
|
||||
<Icon name={IconName.Menu} />
|
||||
</Button>
|
||||
<Link href={ROUTES.home()}>
|
||||
<Logo />
|
||||
</Link>
|
||||
<div>
|
||||
<Icon name={IconName.Notification} />
|
||||
<Icon name={IconName.Search} />
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@ -1,29 +1,43 @@
|
||||
.header {
|
||||
width: 100%;
|
||||
min-height: 56px;
|
||||
height: fit-content;
|
||||
padding: 16px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
.container {
|
||||
width: calc(100% - 32px);
|
||||
max-width: 400px;
|
||||
position: fixed;
|
||||
bottom: calc(0dvh + 14px);
|
||||
left: 50%;
|
||||
z-index: 9995;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
// gap: 40px;
|
||||
background-color: #e5e7eb;
|
||||
border-radius: 24px;
|
||||
padding: 16px 24px 12px;
|
||||
}
|
||||
|
||||
.header > :first-child {
|
||||
justify-self: start;
|
||||
}
|
||||
.item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
position: relative;
|
||||
|
||||
.header > :nth-child(2) {
|
||||
justify-self: center;
|
||||
}
|
||||
& .badge {
|
||||
position: absolute;
|
||||
bottom: 7px;
|
||||
left: 17px;
|
||||
}
|
||||
|
||||
.header > :nth-child(n + 3) {
|
||||
justify-self: end;
|
||||
display: inline-flex;
|
||||
gap: 16px;
|
||||
}
|
||||
& > .label {
|
||||
font-size: 11px;
|
||||
line-height: 14px;
|
||||
color: #8a8d93;
|
||||
}
|
||||
|
||||
.menuButton.menuButton {
|
||||
padding: 0;
|
||||
width: fit-content;
|
||||
background: none;
|
||||
&.active {
|
||||
& > .label {
|
||||
color: #007aff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,37 +1,51 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useLocale } from "next-intl";
|
||||
import clsx from "clsx";
|
||||
|
||||
import { Button, Icon } from "@/components/ui";
|
||||
import { IconName } from "@/components/ui/Icon/Icon";
|
||||
import { Badge, Icon, Typography } from "@/components/ui";
|
||||
import { ROUTES } from "@/shared/constants/client-routes";
|
||||
|
||||
import Logo from "../Logo/Logo";
|
||||
import { navItems } from "@/shared/constants/navigation";
|
||||
import { stripLocale } from "@/shared/utils/path";
|
||||
|
||||
import styles from "./NavigationBar.module.scss";
|
||||
|
||||
import { useDrawer } from "..";
|
||||
|
||||
interface NavigationBarProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function NavigationBar({ className }: NavigationBarProps) {
|
||||
const { open } = useDrawer();
|
||||
export default function NavigationBar() {
|
||||
const pathname = usePathname();
|
||||
const locale = useLocale();
|
||||
const pathnameWithoutLocale = stripLocale(pathname, locale);
|
||||
|
||||
return (
|
||||
<header className={clsx(styles.header, className)}>
|
||||
<Button className={styles.menuButton} onClick={open}>
|
||||
<Icon name={IconName.Menu} />
|
||||
</Button>
|
||||
<Link href={ROUTES.home()}>
|
||||
<Logo />
|
||||
</Link>
|
||||
<div>
|
||||
<Icon name={IconName.Notification} />
|
||||
<Icon name={IconName.Search} />
|
||||
</div>
|
||||
</header>
|
||||
<nav className={styles.container}>
|
||||
{navItems.map(item => {
|
||||
const isActive =
|
||||
item.href === ROUTES.home()
|
||||
? pathnameWithoutLocale === item.href
|
||||
: pathnameWithoutLocale.startsWith(item.href);
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={item.key}
|
||||
href={item.href}
|
||||
className={clsx(styles.item, { [styles.active]: isActive })}
|
||||
>
|
||||
<Icon name={item.icon} color={isActive ? "#007AFF" : "#8A8D93"}>
|
||||
{item.badge && (
|
||||
<Badge className={styles.badge}>
|
||||
<Typography weight="medium" size="xs" color="white">
|
||||
{item.badge}
|
||||
</Typography>
|
||||
</Badge>
|
||||
)}
|
||||
</Icon>
|
||||
<Typography weight="medium" className={styles.label}>
|
||||
{item.label}
|
||||
</Typography>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export { DrawerProvider, useDrawer } from "./Drawer/DrawerContext";
|
||||
export { default as Header } from "./Header/Header";
|
||||
export { default as Logo } from "./Logo/Logo";
|
||||
export { default as NavigationBar } from "./NavigationBar/NavigationBar";
|
||||
export { default as StepperBar } from "./StepperBar/StepperBar";
|
||||
|
||||
11
src/components/ui/Badge/Badge.module.scss
Normal file
11
src/components/ui/Badge/Badge.module.scss
Normal file
@ -0,0 +1,11 @@
|
||||
.badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px;
|
||||
min-width: fit-content;
|
||||
min-height: fit-content;
|
||||
aspect-ratio: 1/1;
|
||||
border-radius: 50%;
|
||||
background-color: #ff0028;
|
||||
}
|
||||
12
src/components/ui/Badge/Badge.tsx
Normal file
12
src/components/ui/Badge/Badge.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import clsx from "clsx";
|
||||
|
||||
import styles from "./Badge.module.scss";
|
||||
|
||||
interface BadgeProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function Badge({ children, className }: BadgeProps) {
|
||||
return <div className={clsx(styles.badge, className)}>{children}</div>;
|
||||
}
|
||||
@ -1,14 +1,21 @@
|
||||
import { CSSProperties, ReactNode } from "react";
|
||||
import clsx from "clsx";
|
||||
|
||||
import ArticleIcon from "./icons/Article";
|
||||
import ChevronIcon from "./icons/Chevron";
|
||||
import CrossIcon from "./icons/Cross";
|
||||
import MenuIcon from "./icons/Menu";
|
||||
import NotificationIcon from "./icons/Notification";
|
||||
import SearchIcon from "./icons/Search";
|
||||
import StarIcon from "./icons/Star";
|
||||
import VideoIcon from "./icons/Video";
|
||||
import {
|
||||
ArticleIcon,
|
||||
ChatIcon,
|
||||
ChevronIcon,
|
||||
ClipboardIcon,
|
||||
CrossIcon,
|
||||
HeartIcon,
|
||||
HomeIcon,
|
||||
LeafIcon,
|
||||
MenuIcon,
|
||||
NotificationIcon,
|
||||
SearchIcon,
|
||||
StarIcon,
|
||||
VideoIcon,
|
||||
} from "./icons";
|
||||
|
||||
export enum IconName {
|
||||
Notification,
|
||||
@ -19,6 +26,11 @@ export enum IconName {
|
||||
Chevron,
|
||||
Star,
|
||||
Cross,
|
||||
Home,
|
||||
Chat,
|
||||
Clipboard,
|
||||
Heart,
|
||||
Leaf,
|
||||
}
|
||||
|
||||
const icons: Record<
|
||||
@ -33,6 +45,11 @@ const icons: Record<
|
||||
[IconName.Chevron]: ChevronIcon,
|
||||
[IconName.Star]: StarIcon,
|
||||
[IconName.Cross]: CrossIcon,
|
||||
[IconName.Home]: HomeIcon,
|
||||
[IconName.Chat]: ChatIcon,
|
||||
[IconName.Clipboard]: ClipboardIcon,
|
||||
[IconName.Heart]: HeartIcon,
|
||||
[IconName.Leaf]: LeafIcon,
|
||||
};
|
||||
|
||||
export type IconProps = {
|
||||
|
||||
37
src/components/ui/Icon/icons/Chat.tsx
Normal file
37
src/components/ui/Icon/icons/Chat.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import { SVGProps } from "react";
|
||||
|
||||
export default function ChatIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width="25"
|
||||
height="24"
|
||||
viewBox="0 0 25 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
color={props.color !== "currentColor" ? props.color : "#8A8D93"}
|
||||
>
|
||||
<g clipPath="url(#clip0_2188_2062)">
|
||||
<g clipPath="url(#clip1_2188_2062)">
|
||||
<path
|
||||
d="M24.7969 11.25C24.7969 16.6359 19.425 21 12.7969 21C11.0579 21 9.40786 20.7 7.91723 20.1609C7.35942 20.5688 6.45005 21.1266 5.37192 21.5953C4.24692 22.0828 2.89223 22.5 1.54692 22.5C1.24223 22.5 0.97036 22.3172 0.853172 22.0359C0.735985 21.7547 0.80161 21.4359 1.01255 21.2203L1.02661 21.2062C1.04067 21.1922 1.05942 21.1734 1.08755 21.1406C1.13911 21.0844 1.2188 20.9953 1.31723 20.8734C1.50942 20.6391 1.76723 20.2922 2.02973 19.8609C2.49848 19.0828 2.9438 18.0609 3.03286 16.9125C1.62661 15.3187 0.796922 13.3641 0.796922 11.25C0.796922 5.86406 6.1688 1.5 12.7969 1.5C19.425 1.5 24.7969 5.86406 24.7969 11.25Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2188_2062">
|
||||
<rect
|
||||
width="24"
|
||||
height="24"
|
||||
fill="white"
|
||||
transform="translate(0.796875)"
|
||||
/>
|
||||
</clipPath>
|
||||
<clipPath id="clip1_2188_2062">
|
||||
<path d="M0.796875 0H24.7969V24H0.796875V0Z" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
47
src/components/ui/Icon/icons/Clipboard.tsx
Normal file
47
src/components/ui/Icon/icons/Clipboard.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import { SVGProps } from "react";
|
||||
|
||||
export default function ClipboardIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width="19"
|
||||
height="24"
|
||||
viewBox="0 0 19 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
color={props.color !== "currentColor" ? props.color : "#8A8D93"}
|
||||
>
|
||||
<g clipPath="url(#clip0_2188_2069)">
|
||||
<g clipPath="url(#clip1_2188_2069)">
|
||||
<g clipPath="url(#clip2_2188_2069)">
|
||||
<path
|
||||
d="M9.78125 0C7.82188 0 6.15312 1.25156 5.53906 3H3.78125C2.12656 3 0.78125 4.34531 0.78125 6V21C0.78125 22.6547 2.12656 24 3.78125 24H15.7812C17.4359 24 18.7812 22.6547 18.7812 21V6C18.7812 4.34531 17.4359 3 15.7812 3H14.0234C13.4094 1.25156 11.7406 0 9.78125 0ZM9.78125 3C10.1791 3 10.5606 3.15804 10.8419 3.43934C11.1232 3.72064 11.2812 4.10218 11.2812 4.5C11.2812 4.89782 11.1232 5.27936 10.8419 5.56066C10.5606 5.84196 10.1791 6 9.78125 6C9.38343 6 9.00189 5.84196 8.72059 5.56066C8.43929 5.27936 8.28125 4.89782 8.28125 4.5C8.28125 4.10218 8.43929 3.72064 8.72059 3.43934C9.00189 3.15804 9.38343 3 9.78125 3ZM6.03125 9H13.5312C13.9438 9 14.2812 9.3375 14.2812 9.75C14.2812 10.1625 13.9438 10.5 13.5312 10.5H6.03125C5.61875 10.5 5.28125 10.1625 5.28125 9.75C5.28125 9.3375 5.61875 9 6.03125 9Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2188_2069">
|
||||
<rect
|
||||
width="18"
|
||||
height="24"
|
||||
fill="white"
|
||||
transform="translate(0.78125)"
|
||||
/>
|
||||
</clipPath>
|
||||
<clipPath id="clip1_2188_2069">
|
||||
<rect
|
||||
width="18"
|
||||
height="24"
|
||||
fill="white"
|
||||
transform="translate(0.78125)"
|
||||
/>
|
||||
</clipPath>
|
||||
<clipPath id="clip2_2188_2069">
|
||||
<path d="M0.78125 0H18.7812V24H0.78125V0Z" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
36
src/components/ui/Icon/icons/Heart.tsx
Normal file
36
src/components/ui/Icon/icons/Heart.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { SVGProps } from "react";
|
||||
|
||||
export default function HeartIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width="25"
|
||||
height="24"
|
||||
viewBox="0 0 25 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
color={props.color !== "currentColor" ? props.color : "#8A8D93"}
|
||||
>
|
||||
<g clipPath="url(#clip0_2188_2075)">
|
||||
<g clipPath="url(#clip1_2188_2075)">
|
||||
<path
|
||||
d="M2.68437 14.081L11.1547 21.9888C11.5063 22.317 11.9703 22.4998 12.4531 22.4998C12.9359 22.4998 13.4 22.317 13.7516 21.9888L22.2219 14.081C23.6469 12.7545 24.4531 10.8935 24.4531 8.9482V8.67633C24.4531 5.39976 22.0859 2.60601 18.8563 2.06695C16.7188 1.7107 14.5437 2.40914 13.0156 3.93726L12.4531 4.49976L11.8906 3.93726C10.3625 2.40914 8.1875 1.7107 6.05 2.06695C2.82031 2.60601 0.453125 5.39976 0.453125 8.67633V8.9482C0.453125 10.8935 1.25937 12.7545 2.68437 14.081Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2188_2075">
|
||||
<rect
|
||||
width="24"
|
||||
height="24"
|
||||
fill="white"
|
||||
transform="translate(0.453125)"
|
||||
/>
|
||||
</clipPath>
|
||||
<clipPath id="clip1_2188_2075">
|
||||
<path d="M0.453125 0H24.4531V24H0.453125V0Z" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
37
src/components/ui/Icon/icons/Home.tsx
Normal file
37
src/components/ui/Icon/icons/Home.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import { SVGProps } from "react";
|
||||
|
||||
export default function HomeIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width="28"
|
||||
height="24"
|
||||
viewBox="0 0 28 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
color={props.color !== "currentColor" ? props.color : "#8A8D93"}
|
||||
>
|
||||
<g clipPath="url(#clip0_2188_2057)">
|
||||
<g clipPath="url(#clip1_2188_2057)">
|
||||
<path
|
||||
d="M27.2875 11.9766C27.2875 12.8203 26.5844 13.4813 25.7875 13.4813H24.2875L24.3203 20.9906C24.3203 21.1172 24.3109 21.2437 24.2969 21.3703V22.125C24.2969 23.1609 23.4578 24 22.4219 24H21.6719C21.6203 24 21.5688 24 21.5172 23.9953C21.4516 24 21.3859 24 21.3203 24H19.7969H18.6719C17.6359 24 16.7969 23.1609 16.7969 22.125V21V18C16.7969 17.1703 16.1266 16.5 15.2969 16.5H12.2969C11.4672 16.5 10.7969 17.1703 10.7969 18V21V22.125C10.7969 23.1609 9.95781 24 8.92188 24H7.79688H6.30156C6.23125 24 6.16094 23.9953 6.09062 23.9906C6.03437 23.9953 5.97813 24 5.92188 24H5.17188C4.13594 24 3.29688 23.1609 3.29688 22.125V16.875C3.29688 16.8328 3.29688 16.7859 3.30156 16.7438V13.4813H1.79688C0.953125 13.4813 0.296875 12.825 0.296875 11.9766C0.296875 11.5547 0.4375 11.1797 0.765625 10.8516L12.7844 0.375C13.1125 0.046875 13.4875 0 13.8156 0C14.1437 0 14.5187 0.09375 14.8 0.328125L26.7719 10.8516C27.1469 11.1797 27.3344 11.5547 27.2875 11.9766Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2188_2057">
|
||||
<rect
|
||||
width="27"
|
||||
height="24"
|
||||
fill="white"
|
||||
transform="translate(0.296875)"
|
||||
/>
|
||||
</clipPath>
|
||||
<clipPath id="clip1_2188_2057">
|
||||
<path d="M0.296875 0H27.2969V24H0.296875V0Z" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
36
src/components/ui/Icon/icons/Leaf.tsx
Normal file
36
src/components/ui/Icon/icons/Leaf.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { SVGProps } from "react";
|
||||
|
||||
export default function Leaf(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width="25"
|
||||
height="24"
|
||||
viewBox="0 0 25 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
color={props.color !== "currentColor" ? props.color : "#8A8D93"}
|
||||
>
|
||||
<g clipPath="url(#clip0_2188_2080)">
|
||||
<g clipPath="url(#clip1_2188_2080)">
|
||||
<path
|
||||
d="M12.8594 4.50011C9.175 4.50011 6.05781 6.91417 4.99844 10.2423C6.57344 9.44542 8.35 9.00011 10.2344 9.00011H14.3594C14.7719 9.00011 15.1094 9.33761 15.1094 9.75011C15.1094 10.1626 14.7719 10.5001 14.3594 10.5001H13.6094H10.2344C9.45625 10.5001 8.70156 10.5892 7.975 10.7532C6.76094 11.0298 5.63125 11.522 4.62813 12.1923C1.90469 14.0064 0.109375 17.1048 0.109375 20.6251V21.3751C0.109375 21.9985 0.610937 22.5001 1.23438 22.5001C1.85781 22.5001 2.35938 21.9985 2.35938 21.3751V20.6251C2.35938 18.3423 3.32969 16.2892 4.88125 14.8501C5.80938 18.3892 9.02969 21.0001 12.8594 21.0001H12.9062C19.0984 20.9673 24.1094 14.8642 24.1094 7.34074C24.1094 5.34386 23.7578 3.44542 23.1203 1.73449C22.9984 1.41105 22.525 1.42511 22.3609 1.7298C21.4797 3.3798 19.7359 4.50011 17.7344 4.50011H12.8594Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2188_2080">
|
||||
<rect
|
||||
width="24"
|
||||
height="24"
|
||||
fill="white"
|
||||
transform="translate(0.109375)"
|
||||
/>
|
||||
</clipPath>
|
||||
<clipPath id="clip1_2188_2080">
|
||||
<path d="M0.109375 0H24.1094V24H0.109375V0Z" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
13
src/components/ui/Icon/icons/index.ts
Normal file
13
src/components/ui/Icon/icons/index.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export { default as ArticleIcon } from "./Article";
|
||||
export { default as ChatIcon } from "./Chat";
|
||||
export { default as ChevronIcon } from "./Chevron";
|
||||
export { default as ClipboardIcon } from "./Clipboard";
|
||||
export { default as CrossIcon } from "./Cross";
|
||||
export { default as HeartIcon } from "./Heart";
|
||||
export { default as HomeIcon } from "./Home";
|
||||
export { default as LeafIcon } from "./Leaf";
|
||||
export { default as MenuIcon } from "./Menu";
|
||||
export { default as NotificationIcon } from "./Notification";
|
||||
export { default as SearchIcon } from "./Search";
|
||||
export { default as StarIcon } from "./Star";
|
||||
export { default as VideoIcon } from "./Video";
|
||||
@ -1,10 +1,10 @@
|
||||
import { ReactNode } from "react";
|
||||
import clsx from "clsx";
|
||||
|
||||
import Icon, { IconProps } from "../Icon/Icon";
|
||||
|
||||
import styles from "./IconLabel.module.scss";
|
||||
|
||||
import { Icon, IconProps } from "..";
|
||||
|
||||
export type IconLabelProps = {
|
||||
iconProps: IconProps;
|
||||
children: ReactNode;
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { ReactNode } from "react";
|
||||
|
||||
import IconLabel, { IconLabelProps } from "../IconLabel/IconLabel";
|
||||
import Typography from "../Typography/Typography";
|
||||
|
||||
import styles from "./MetaLabel.module.scss";
|
||||
|
||||
export type MetaLabelProps = {
|
||||
import { IconLabel, IconLabelProps } from "..";
|
||||
|
||||
type MetaLabelProps = {
|
||||
iconLabelProps: IconLabelProps;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
@ -3,11 +3,9 @@
|
||||
import { ReactNode, useCallback, useEffect, useRef, useState } from "react";
|
||||
import clsx from "clsx";
|
||||
|
||||
import { IconName } from "../Icon/Icon";
|
||||
|
||||
import styles from "./Modal.module.scss";
|
||||
|
||||
import { Button, Icon } from "..";
|
||||
import { Button, Icon, IconName } from "..";
|
||||
|
||||
interface ModalProps {
|
||||
children: ReactNode;
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import clsx from "clsx";
|
||||
|
||||
import Icon, { IconName } from "../Icon/Icon";
|
||||
|
||||
import styles from "./Stars.module.scss";
|
||||
|
||||
import { Icon, IconName } from "..";
|
||||
|
||||
interface StarsProps {
|
||||
rating?: number;
|
||||
size?: number;
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export { default as Badge } from "./Badge/Badge";
|
||||
export { default as Button } from "./Button/Button";
|
||||
export { default as Card } from "./Card/Card";
|
||||
export { default as CircleArrow } from "./CircleArrow/CircleArrow";
|
||||
@ -5,8 +6,11 @@ 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";
|
||||
export { default as IconLabel } from "./IconLabel/IconLabel";
|
||||
export { default as Icon, IconName, type IconProps } from "./Icon/Icon";
|
||||
export {
|
||||
default as IconLabel,
|
||||
type IconLabelProps,
|
||||
} from "./IconLabel/IconLabel";
|
||||
export { default as MetaLabel } from "./MetaLabel/MetaLabel";
|
||||
export { default as Modal } from "./Modal/Modal";
|
||||
export { default as NameInput } from "./NameInput/NameInput";
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useLocale, useTranslations } from "next-intl";
|
||||
|
||||
import { Typography } from "@/components/ui";
|
||||
import { SelectInput } from "@/components/ui/SelectInput/SelectInput";
|
||||
@ -20,9 +20,7 @@ const isValidDate = (year: number, month: number, day: number) => {
|
||||
|
||||
// Упрощенное определение порядка полей даты на основе локали.
|
||||
// В реальном приложении здесь лучше использовать данные из next-intl.
|
||||
const getDateInputLocaleFormat = (): ("d" | "m" | "y")[] => {
|
||||
const locale =
|
||||
typeof navigator !== "undefined" ? navigator.language : "en-US";
|
||||
const getDateInputLocaleFormat = (locale: string): ("d" | "m" | "y")[] => {
|
||||
const format = new Intl.DateTimeFormat(locale).format(new Date(2001, 1, 3)); // Используем 3/Feb/2001
|
||||
if (/^3.*2/.test(format)) return ["d", "m", "y"]; // 3/2/2001 -> d/m/y
|
||||
if (/^2.*3/.test(format)) return ["m", "d", "y"]; // 2/3/2001 -> m/d/y
|
||||
@ -49,6 +47,8 @@ export default function DatePicker({
|
||||
onBlur,
|
||||
}: DatePickerProps) {
|
||||
const t = useTranslations("DatePicker");
|
||||
const locale = useLocale();
|
||||
|
||||
const [year, setYear] = useState("");
|
||||
const [month, setMonth] = useState("");
|
||||
const [day, setDay] = useState("");
|
||||
@ -110,7 +110,10 @@ export default function DatePicker({
|
||||
}));
|
||||
}, [year, month]);
|
||||
|
||||
const localeFormat = useMemo(() => getDateInputLocaleFormat(), []);
|
||||
const localeFormat = useMemo(
|
||||
() => getDateInputLocaleFormat(locale),
|
||||
[locale]
|
||||
);
|
||||
|
||||
const inputs = {
|
||||
d: (
|
||||
|
||||
@ -39,7 +39,7 @@ const intlMiddleware = createMiddleware(routing);
|
||||
|
||||
export default async function middleware(request: NextRequest) {
|
||||
const authResponse = await createAuthMiddleware()(request);
|
||||
if (authResponse.status !== 200) {
|
||||
if (authResponse.status !== 200 && process.env.NODE_ENV === "production") {
|
||||
return authResponse;
|
||||
}
|
||||
|
||||
|
||||
@ -3,8 +3,8 @@ const ROOT_ROUTE = "/";
|
||||
const profilePrefix = "profile";
|
||||
const retainingFunnelPrefix = "retaining";
|
||||
|
||||
const createRoute = (segments: string[]): string => {
|
||||
return ROOT_ROUTE + segments.join("/");
|
||||
const createRoute = (segments: Array<string | undefined>): string => {
|
||||
return ROOT_ROUTE + segments.filter(Boolean).join("/");
|
||||
};
|
||||
|
||||
export const ROUTES = {
|
||||
@ -13,13 +13,16 @@ export const ROUTES = {
|
||||
// Auth
|
||||
authCallback: () => createRoute(["auth", "callback"]),
|
||||
|
||||
// Breath
|
||||
breath: (id: string) => createRoute(["breath", id]),
|
||||
breathResult: (id: string, resultId: string) =>
|
||||
createRoute(["breath", id, "result", resultId]),
|
||||
// Advisers
|
||||
advisers: () => createRoute(["advisers"]),
|
||||
|
||||
// Meditation
|
||||
meditation: (id?: string) => createRoute(["meditation", id]),
|
||||
meditationResult: (id: string, resultId: string) =>
|
||||
createRoute(["meditation", id, "result", resultId]),
|
||||
|
||||
// Compatibility
|
||||
compatibility: (id: string) => createRoute(["compatibility", id]),
|
||||
compatibility: (id?: string) => createRoute(["compatibility", id]),
|
||||
compatibilityResult: (id: string, resultId: string) =>
|
||||
createRoute(["compatibility", id, "result", resultId]),
|
||||
|
||||
@ -56,4 +59,13 @@ export const ROUTES = {
|
||||
payment: () => createRoute(["payment"]),
|
||||
paymentSuccess: () => createRoute(["payment", "success"]),
|
||||
paymentFailed: () => createRoute(["payment", "failed"]),
|
||||
|
||||
// Chat
|
||||
chat: () => createRoute(["chat"]),
|
||||
|
||||
// // Compatibility
|
||||
// compatibilities: () => createRoute(["compatibilities"]),
|
||||
|
||||
// // Meditation
|
||||
// meditations: () => createRoute(["meditations"]),
|
||||
};
|
||||
|
||||
45
src/shared/constants/navigation.tsx
Normal file
45
src/shared/constants/navigation.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import { IconName } from "@/components/ui";
|
||||
|
||||
import { ROUTES } from "./client-routes";
|
||||
|
||||
interface NavItem {
|
||||
key: string;
|
||||
label: string;
|
||||
icon: IconName;
|
||||
href: string;
|
||||
badge?: number;
|
||||
}
|
||||
|
||||
export const navItems: NavItem[] = [
|
||||
{
|
||||
key: "home",
|
||||
label: "Home",
|
||||
icon: IconName.Home,
|
||||
href: ROUTES.home(),
|
||||
},
|
||||
{
|
||||
key: "chat",
|
||||
label: "Chat",
|
||||
icon: IconName.Chat,
|
||||
href: ROUTES.chat(),
|
||||
badge: 12,
|
||||
},
|
||||
{
|
||||
key: "advisers",
|
||||
label: "Advi...",
|
||||
icon: IconName.Clipboard,
|
||||
href: ROUTES.advisers(),
|
||||
},
|
||||
{
|
||||
key: "compatibility",
|
||||
label: "Comp...",
|
||||
icon: IconName.Heart,
|
||||
href: ROUTES.compatibility(),
|
||||
},
|
||||
{
|
||||
key: "meditation",
|
||||
label: "Medi...",
|
||||
icon: IconName.Leaf,
|
||||
href: ROUTES.meditation(),
|
||||
},
|
||||
];
|
||||
6
src/shared/utils/path.ts
Normal file
6
src/shared/utils/path.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export function stripLocale(pathname: string, locale: string) {
|
||||
if (pathname.startsWith(`/${locale}`)) {
|
||||
return pathname.slice(locale.length + 1) || "/";
|
||||
}
|
||||
return pathname;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user