-
diff --git a/src/app/(authenticated)/dashboard/org/[[...organization-profile]]/OrgProfile.tsx b/src/app/(authenticated)/dashboard/org/[[...organization-profile]]/OrgProfile.tsx
new file mode 100644
index 0000000000..223abcf2e7
--- /dev/null
+++ b/src/app/(authenticated)/dashboard/org/[[...organization-profile]]/OrgProfile.tsx
@@ -0,0 +1,19 @@
+'use client';
+
+import { useTheme } from 'next-themes';
+import { dark } from '@clerk/themes';
+import { OrganizationProfile } from '@clerk/nextjs';
+
+export const OrgProfile = () => {
+ const { resolvedTheme } = useTheme();
+ const baseTheme = resolvedTheme === 'dark' ? dark : undefined;
+
+ return (
+
+ );
+};
diff --git a/src/app/(authenticated)/dashboard/org/[[...organization-profile]]/page.tsx b/src/app/(authenticated)/dashboard/org/[[...organization-profile]]/page.tsx
new file mode 100644
index 0000000000..fcfdb851af
--- /dev/null
+++ b/src/app/(authenticated)/dashboard/org/[[...organization-profile]]/page.tsx
@@ -0,0 +1,5 @@
+import { OrgProfile } from './OrgProfile';
+
+export default function Page() {
+ return
;
+}
diff --git a/src/app/(authenticated)/dashboard/organization-profile/[[...organization-profile]]/page.tsx b/src/app/(authenticated)/dashboard/organization-profile/[[...organization-profile]]/page.tsx
deleted file mode 100644
index 0f633bb973..0000000000
--- a/src/app/(authenticated)/dashboard/organization-profile/[[...organization-profile]]/page.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { OrganizationProfile } from '@clerk/nextjs';
-import { useTranslations } from 'next-intl';
-
-import { TitleBar } from '@/components/dashboard/TitleBar';
-
-export default function Page() {
- const t = useTranslations('OrganizationProfile');
-
- return (
- <>
-
-
- >
- );
-}
diff --git a/src/app/(authenticated)/dashboard/page.tsx b/src/app/(authenticated)/dashboard/page.tsx
index 7b2c42d5bd..1e6b0da064 100644
--- a/src/app/(authenticated)/dashboard/page.tsx
+++ b/src/app/(authenticated)/dashboard/page.tsx
@@ -2,36 +2,10 @@ import { useTranslations } from 'next-intl';
import { TitleBar } from '@/components/dashboard/TitleBar';
-const DashboardIndexPage = () => {
+export default function Page() {
const t = useTranslations('DashboardIndex');
return (
- <>
-
-
-
-
-
- {t('message_state_title')}
-
-
-
- >
+
);
-};
-
-export default DashboardIndexPage;
+}
diff --git a/src/app/(authenticated)/dashboard/user-profile/[[...user-profile]]/page.tsx b/src/app/(authenticated)/dashboard/user-profile/[[...user-profile]]/page.tsx
deleted file mode 100644
index e10967b15d..0000000000
--- a/src/app/(authenticated)/dashboard/user-profile/[[...user-profile]]/page.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { UserProfile } from '@clerk/nextjs';
-import { useTranslations } from 'next-intl';
-
-import { TitleBar } from '@/components/dashboard/TitleBar';
-
-export default function Page() {
- const t = useTranslations('UserProfile');
-
- return (
- <>
-
-
- >
- );
-}
diff --git a/src/app/(authenticated)/dashboard/user/[[...user-profile]]/UserProfile.tsx b/src/app/(authenticated)/dashboard/user/[[...user-profile]]/UserProfile.tsx
new file mode 100644
index 0000000000..f5b50a1142
--- /dev/null
+++ b/src/app/(authenticated)/dashboard/user/[[...user-profile]]/UserProfile.tsx
@@ -0,0 +1,18 @@
+'use client';
+
+import { useTheme } from 'next-themes';
+import { dark } from '@clerk/themes';
+import { UserProfile as ClerkUserProfile } from '@clerk/nextjs';
+
+export const UserProfile = () => {
+ const { resolvedTheme } = useTheme();
+ const baseTheme = resolvedTheme === 'dark' ? dark : undefined;
+
+ return (
+
+ );
+};
diff --git a/src/app/(authenticated)/dashboard/user/[[...user-profile]]/page.tsx b/src/app/(authenticated)/dashboard/user/[[...user-profile]]/page.tsx
new file mode 100644
index 0000000000..baee4da88c
--- /dev/null
+++ b/src/app/(authenticated)/dashboard/user/[[...user-profile]]/page.tsx
@@ -0,0 +1,5 @@
+import { UserProfile } from './UserProfile';
+
+export default function Page() {
+ return
;
+}
diff --git a/src/app/(authenticated)/layout.tsx b/src/app/(authenticated)/layout.tsx
index 5abcbb091f..6008686c30 100644
--- a/src/app/(authenticated)/layout.tsx
+++ b/src/app/(authenticated)/layout.tsx
@@ -1,7 +1,7 @@
import { redirect } from 'next/navigation';
import { auth as clerkAuth } from '@clerk/nextjs/server';
-export default async function CenteredLayout({
+export default async function AuthenticatedLayout({
children,
}: {
children: React.ReactNode;
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 0f74245c78..a396d13bb3 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -6,7 +6,7 @@ import { getLocale } from 'next-intl/server';
import { enUS, frFR } from '@clerk/localizations';
import { ClerkProvider } from '@clerk/nextjs';
-import { ThemeProvider } from '@/components/ThemeProvider';
+import { ThemeProvider } from '@/components/layout/ThemeProvider';
export const metadata: Metadata = {
icons: [
diff --git a/src/app/onboarding/create-org/CreateOrg.tsx b/src/app/onboarding/create-org/CreateOrg.tsx
new file mode 100644
index 0000000000..71249f4e85
--- /dev/null
+++ b/src/app/onboarding/create-org/CreateOrg.tsx
@@ -0,0 +1,17 @@
+'use client';
+
+import { useTheme } from 'next-themes';
+import { CreateOrganization } from '@clerk/nextjs';
+import { dark } from '@clerk/themes';
+
+export const CreateOrg = () => {
+ const { resolvedTheme } = useTheme();
+ const baseTheme = resolvedTheme === 'dark' ? dark : undefined;
+
+ return (
+
+ );
+};
diff --git a/src/app/onboarding/create-org/page.tsx b/src/app/onboarding/create-org/page.tsx
index 158f936d73..e37ad9bb07 100644
--- a/src/app/onboarding/create-org/page.tsx
+++ b/src/app/onboarding/create-org/page.tsx
@@ -1,8 +1,7 @@
-import { redirect } from 'next/navigation';
-import { CreateOrganization } from '@clerk/nextjs';
-import { auth as clerkAuth } from '@clerk/nextjs/server';
import { getLocale, getTranslations } from 'next-intl/server';
+import { CreateOrg } from './CreateOrg';
+
export async function generateMetadata() {
const locale = await getLocale();
const t = await getTranslations({ locale, namespace: 'Dashboard' });
@@ -12,18 +11,5 @@ export async function generateMetadata() {
export const dynamic = 'force-dynamic';
export default async function Page() {
- const auth = await clerkAuth();
-
- if (!auth.userId) {
- redirect('/');
- }
-
- return (
-
-
-
- );
+ return
;
}
diff --git a/src/app/onboarding/layout.tsx b/src/app/onboarding/layout.tsx
new file mode 100644
index 0000000000..f7ad0f5bda
--- /dev/null
+++ b/src/app/onboarding/layout.tsx
@@ -0,0 +1,20 @@
+import { redirect } from 'next/navigation';
+import { auth as clerkAuth } from '@clerk/nextjs/server';
+
+export default async function OnboardingLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ const auth = await clerkAuth();
+
+ if (!auth.userId) {
+ redirect('/');
+ }
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/app/onboarding/select-org/SelectOrg.tsx b/src/app/onboarding/select-org/SelectOrg.tsx
new file mode 100644
index 0000000000..9015e75a1a
--- /dev/null
+++ b/src/app/onboarding/select-org/SelectOrg.tsx
@@ -0,0 +1,18 @@
+'use client';
+
+import { useTheme } from 'next-themes';
+import { dark } from '@clerk/themes';
+import { OrganizationList } from '@clerk/nextjs';
+
+export const SelectOrg = () => {
+ const { resolvedTheme } = useTheme();
+ const baseTheme = resolvedTheme === 'dark' ? dark : undefined;
+
+ return (
+
+ );
+};
diff --git a/src/app/onboarding/select-org/page.tsx b/src/app/onboarding/select-org/page.tsx
index bb3851fa3c..fd9ef9fdb8 100644
--- a/src/app/onboarding/select-org/page.tsx
+++ b/src/app/onboarding/select-org/page.tsx
@@ -1,8 +1,7 @@
-import { redirect } from 'next/navigation';
-import { OrganizationList } from '@clerk/nextjs';
-import { auth as clerkAuth } from '@clerk/nextjs/server';
import { getLocale, getTranslations } from 'next-intl/server';
+import { SelectOrg } from './SelectOrg';
+
export async function generateMetadata() {
const locale = await getLocale();
const t = await getTranslations({ locale, namespace: 'Dashboard' });
@@ -12,18 +11,5 @@ export async function generateMetadata() {
export const dynamic = 'force-dynamic';
export default async function Page() {
- const auth = await clerkAuth();
-
- if (!auth.userId) {
- redirect('/');
- }
-
- return (
-
-
-
- );
+ return
;
}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index bc2affdd11..532c9ebd2a 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,26 +1,20 @@
+import { redirect } from 'next/navigation';
import { auth as clerkAuth } from '@clerk/nextjs/server';
import { getLocale, getTranslations, setRequestLocale } from 'next-intl/server';
-import { Features } from '@/components/templates/Features';
-import { Footer } from '@/components/templates/Footer';
-import { Hero } from '@/components/templates/Hero';
-import { Navbar } from '@/components/templates/Navbar';
-import { redirect } from 'next/navigation';
+import { Navbar, Footer } from '@/components/layout';
+import { Hero, Features } from '@/components/landing';
export async function generateMetadata() {
const locale = await getLocale();
const t = await getTranslations({ locale, namespace: 'Index' });
-
- return {
- title: t('meta_title'),
- description: t('meta_description'),
- };
+ return { title: t('meta_title'), description: t('meta_description') };
}
export default async function Page() {
- const auth = await clerkAuth();
+ const { userId } = await clerkAuth();
- if (auth.userId) {
+ if (userId) {
redirect('/dashboard');
}
diff --git a/src/app/sign-in/[[...sign-in]]/SignIn.tsx b/src/app/sign-in/[[...sign-in]]/SignIn.tsx
new file mode 100644
index 0000000000..a12e84d7b9
--- /dev/null
+++ b/src/app/sign-in/[[...sign-in]]/SignIn.tsx
@@ -0,0 +1,22 @@
+'use client';
+
+import Link from 'next/link';
+import { useTheme } from 'next-themes';
+import { dark } from '@clerk/themes';
+import { SignIn as ClerkSignIn } from '@clerk/nextjs';
+
+import { Logo } from '@/components/layout';
+
+export const SignIn = () => {
+ const { resolvedTheme } = useTheme();
+ const baseTheme = resolvedTheme === 'dark' ? dark : undefined;
+
+ return (
+
+
+
+
+
+
+ );
+};
diff --git a/src/app/sign-in/[[...sign-in]]/page.tsx b/src/app/sign-in/[[...sign-in]]/page.tsx
index 56e4df8d53..e84bb8bfda 100644
--- a/src/app/sign-in/[[...sign-in]]/page.tsx
+++ b/src/app/sign-in/[[...sign-in]]/page.tsx
@@ -1,6 +1,7 @@
-import { SignIn } from '@clerk/nextjs';
import { getLocale, getTranslations } from 'next-intl/server';
+import { SignIn } from './SignIn';
+
export async function generateMetadata() {
const locale = await getLocale();
const t = await getTranslations({ locale, namespace: 'SignIn' });
@@ -8,9 +9,5 @@ export async function generateMetadata() {
}
export default function Page() {
- return (
-
-
-
- );
+ return
;
}
diff --git a/src/app/sign-up/[[...sign-up]]/SignUp.tsx b/src/app/sign-up/[[...sign-up]]/SignUp.tsx
new file mode 100644
index 0000000000..6e5b6d25f8
--- /dev/null
+++ b/src/app/sign-up/[[...sign-up]]/SignUp.tsx
@@ -0,0 +1,22 @@
+'use client';
+
+import Link from 'next/link';
+import { useTheme } from 'next-themes';
+import { dark } from '@clerk/themes';
+import { SignUp as ClerkSignUp } from '@clerk/nextjs';
+
+import { Logo } from '@/components/layout';
+
+export const SignUp = () => {
+ const { resolvedTheme } = useTheme();
+ const baseTheme = resolvedTheme === 'dark' ? dark : undefined;
+
+ return (
+
+
+
+
+
+
+ );
+};
diff --git a/src/app/sign-up/[[...sign-up]]/page.tsx b/src/app/sign-up/[[...sign-up]]/page.tsx
index a689bbf2dd..038a2156ab 100644
--- a/src/app/sign-up/[[...sign-up]]/page.tsx
+++ b/src/app/sign-up/[[...sign-up]]/page.tsx
@@ -1,6 +1,7 @@
-import { SignUp } from '@clerk/nextjs';
import { getLocale, getTranslations } from 'next-intl/server';
+import { SignUp } from './SignUp';
+
export async function generateMetadata() {
const locale = await getLocale();
const t = await getTranslations({ locale, namespace: 'SignUp' });
@@ -8,9 +9,5 @@ export async function generateMetadata() {
}
export default function Page() {
- return (
-
-
-
- );
+ return
;
}
diff --git a/src/components/ActiveLink.tsx b/src/components/ActiveLink.tsx
deleted file mode 100644
index 3f9aaab682..0000000000
--- a/src/components/ActiveLink.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-'use client';
-
-import Link from 'next/link';
-import { usePathname } from 'next/navigation';
-
-import { cn } from '@/lib/utils';
-
-type ActiveLinkProps = {
- href: string;
- children: React.ReactNode;
-};
-
-export const ActiveLink = ({ href, children }: ActiveLinkProps) => {
- const pathname = usePathname();
-
- return (
-
- {children}
-
- );
-};
diff --git a/src/components/ToggleMenuButton.test.tsx b/src/components/ToggleMenuButton.test.tsx
deleted file mode 100644
index 62bbcabc66..0000000000
--- a/src/components/ToggleMenuButton.test.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { render, screen } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import { vi } from 'vitest';
-
-import { ToggleMenuButton } from './ToggleMenuButton';
-
-describe('ToggleMenuButton', () => {
- describe('onClick props', () => {
- it('should call the callback when the user click on the button', async () => {
- const handler = vi.fn();
-
- render(
);
- const button = screen.getByRole('button');
- await userEvent.click(button);
-
- expect(handler).toHaveBeenCalled();
- });
- });
-});
diff --git a/src/components/ToggleMenuButton.tsx b/src/components/ToggleMenuButton.tsx
deleted file mode 100644
index 4062dbb5b9..0000000000
--- a/src/components/ToggleMenuButton.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { type ForwardedRef, forwardRef } from 'react';
-
-import { Button } from '@/components/ui';
-
-type ToggleMenuButtonProps = {
- onClick?: () => void;
-};
-
-const ToggleMenuButtonInternal = (
- props: ToggleMenuButtonProps,
- ref?: ForwardedRef
,
-) => (
-
-);
-
-const ToggleMenuButton = forwardRef(ToggleMenuButtonInternal);
-
-export { ToggleMenuButton };
diff --git a/src/components/dashboard/DashboardHeader.tsx b/src/components/dashboard/DashboardHeader.tsx
index 18ef1638df..ebda268c98 100644
--- a/src/components/dashboard/DashboardHeader.tsx
+++ b/src/components/dashboard/DashboardHeader.tsx
@@ -1,111 +1,45 @@
'use client';
-import { useMemo } from 'react';
import Link from 'next/link';
-import {
- OrganizationSwitcher,
- UserButton,
- useOrganization,
-} from '@clerk/nextjs';
-import { useTranslations } from 'next-intl';
+import { useTheme } from 'next-themes';
+import { OrganizationSwitcher, UserButton } from '@clerk/nextjs';
+import { dark } from '@clerk/themes';
-import { ActiveLink } from '@/components/ActiveLink';
-import { LocaleSwitcher } from '@/components/LocaleSwitcher';
-import { ThemeSwitcher } from '@/components/ThemeSwitcher';
-import { ToggleMenuButton } from '@/components/ToggleMenuButton';
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
- Separator,
-} from '@/components/ui';
-import { Logo } from '@/components/templates/Logo';
+import { LocaleSwitcher } from '@/components/layout/LocaleSwitcher';
+import { ThemeSwitcher } from '@/components/layout/ThemeSwitcher';
+import { Logo } from '@/components/layout/Logo';
export const DashboardHeader = () => {
- const t = useTranslations('DashboardLayout');
- const { organization } = useOrganization();
-
- const menu = useMemo(() => {
- const menu = [{ href: '/dashboard', label: t('home') }];
-
- return organization
- ? [
- ...menu,
- {
- href: '/dashboard/organization-profile/organization-members',
- label: t('members'),
- },
- { href: '/dashboard/organization-profile', label: t('settings') },
- ]
- : menu;
- }, [organization, t]);
+ const { resolvedTheme } = useTheme();
+ const baseTheme = resolvedTheme === 'dark' ? dark : undefined;
return (
<>
-
-
+
+
-
-
-
- -
-
-
-
-
-
-
- {menu.map(({ href, label }) => (
-
- {label}
-
- ))}
-
-
-
-
+
diff --git a/src/components/dashboard/DashboardSection.tsx b/src/components/dashboard/DashboardSection.tsx
deleted file mode 100644
index dda186c188..0000000000
--- a/src/components/dashboard/DashboardSection.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-type DashboardSectionProps = {
- title: string;
- description: string;
- children: React.ReactNode;
-};
-
-export const DashboardSection = ({
- title,
- description,
- children,
-}: DashboardSectionProps) => (
-
-
-
{title}
-
- {description}
-
- {children}
-
-
-);
diff --git a/src/components/dashboard/TitleBar.tsx b/src/components/dashboard/TitleBar.tsx
index 633431641c..d89a177938 100644
--- a/src/components/dashboard/TitleBar.tsx
+++ b/src/components/dashboard/TitleBar.tsx
@@ -1,13 +1,14 @@
-export const TitleBar = (props: {
+type TitleBarProps = {
title: React.ReactNode;
description?: React.ReactNode;
-}) => (
-
-
{props.title}
+};
- {props.description && (
+export const TitleBar = ({ title, description }: TitleBarProps) => (
+
+
{title}
+ {description && (
- {props.description}
+ {description}
)}
diff --git a/src/components/landing/CenteredFooter.test.tsx b/src/components/landing/CenteredFooter.test.tsx
deleted file mode 100644
index e340d8c3bb..0000000000
--- a/src/components/landing/CenteredFooter.test.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { render, screen } from '@testing-library/react';
-import { NextIntlClientProvider } from 'next-intl';
-
-import messages from '@/i18n/locales/en.json';
-
-import { CenteredFooter } from './CenteredFooter';
-
-describe('CenteredFooter', () => {
- describe('Render method', () => {
- it('should have copyright information', () => {
- render(
-
-
- Random children
-
- ,
- );
-
- const copyright = screen.getByText(/© Copyright/);
-
- expect(copyright).toBeInTheDocument();
- });
- });
-});
diff --git a/src/components/landing/CenteredFooter.tsx b/src/components/landing/CenteredFooter.tsx
deleted file mode 100644
index 1bc94a3d49..0000000000
--- a/src/components/landing/CenteredFooter.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import React from 'react';
-
-import { APP_NAME } from '@/lib/constants';
-
-type CenteredFooterProps = {
- iconList: React.ReactNode;
- legalLinks: React.ReactNode;
- children?: React.ReactNode;
-};
-
-export const CenteredFooter = ({
- iconList,
- legalLinks,
- children,
-}: CenteredFooterProps) => (
-
- {children && (
-
- )}
-
-
-
- © Copyright {new Date().getFullYear()} {APP_NAME}.
-
-
-
-
-);
diff --git a/src/components/landing/CenteredHero.tsx b/src/components/landing/CenteredHero.tsx
deleted file mode 100644
index af672d555c..0000000000
--- a/src/components/landing/CenteredHero.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-type CenteredHeroProps = {
- title: React.ReactNode;
- description: string;
- buttons?: React.ReactNode;
-};
-
-export const CenteredHero = ({
- title,
- description,
- buttons,
-}: CenteredHeroProps) => (
- <>
-
- {title}
-
-
- {description}
-
- {buttons && (
-
- {buttons}
-
- )}
- >
-);
diff --git a/src/components/landing/CenteredMenu.tsx b/src/components/landing/CenteredMenu.tsx
deleted file mode 100644
index 59a0fd6597..0000000000
--- a/src/components/landing/CenteredMenu.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-'use client';
-
-import Link from 'next/link';
-
-import { ToggleMenuButton } from '@/components/ToggleMenuButton';
-import { useMenu } from '@/hooks/useMenu';
-import { cn } from '@/lib/utils';
-
-type CenteredMenuProps = {
- logo: React.ReactNode;
- rightMenu: React.ReactNode;
- children?: React.ReactNode;
-};
-
-export const CenteredMenu = ({
- logo,
- rightMenu,
- children,
-}: CenteredMenuProps) => {
- const { showMenu, handleToggleMenu } = useMenu();
-
- const navClass = cn('max-lg:w-full max-lg:bg-secondary max-lg:p-5', {
- 'max-lg:hidden': !showMenu,
- });
-
- return (
-
-
{logo}
-
-
-
- {children && (
-
- )}
-
-
- );
-};
diff --git a/src/components/landing/FeatureCard.tsx b/src/components/landing/FeatureCard.tsx
deleted file mode 100644
index 374e276b6d..0000000000
--- a/src/components/landing/FeatureCard.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-export const FeatureCard = (props: {
- icon: React.ReactNode;
- title: string;
- children: React.ReactNode;
-}) => (
-
-
- {props.icon}
-
-
-
{props.title}
-
-
-
-
{props.children}
-
-);
diff --git a/src/components/landing/Features.tsx b/src/components/landing/Features.tsx
new file mode 100644
index 0000000000..9460c359eb
--- /dev/null
+++ b/src/components/landing/Features.tsx
@@ -0,0 +1,148 @@
+import { useTranslations } from 'next-intl';
+
+import { Section } from '@/components/layout';
+
+export const Features = () => {
+ const t = useTranslations('Features');
+
+ return (
+
+
+
+ {t('section_subtitle')}
+
+
{t('section_title')}
+
+ {t('section_description')}
+
+
+
+
+
+
+
+ }
+ title={t('feature1_title')}
+ >
+ {t('feature_description')}
+
+
+
+
+
+ }
+ title={t('feature2_title')}
+ >
+ {t('feature_description')}
+
+
+
+
+
+ }
+ title={t('feature3_title')}
+ >
+ {t('feature_description')}
+
+
+
+
+
+ }
+ title={t('feature4_title')}
+ >
+ {t('feature_description')}
+
+
+
+
+
+ }
+ title={t('feature5_title')}
+ >
+ {t('feature_description')}
+
+
+
+
+
+ }
+ title={t('feature6_title')}
+ >
+ {t('feature_description')}
+
+
+
+ );
+};
+
+type FeatureCardProps = {
+ icon: React.ReactNode;
+ title: string;
+ children: React.ReactNode;
+};
+
+const FeatureCard = ({ icon, title, children }: FeatureCardProps) => (
+
+
+ {icon}
+
+
{title}
+
+
{children}
+
+);
diff --git a/src/components/templates/Hero.tsx b/src/components/landing/Hero.tsx
similarity index 56%
rename from src/components/templates/Hero.tsx
rename to src/components/landing/Hero.tsx
index 880ff7e582..e33fe711fe 100644
--- a/src/components/templates/Hero.tsx
+++ b/src/components/landing/Hero.tsx
@@ -1,23 +1,24 @@
import { useTranslations } from 'next-intl';
-import { CenteredHero } from '@/components/landing/CenteredHero';
-import { Section } from '@/components/landing/Section';
+import { Section } from '@/components/layout/Section';
export const Hero = () => {
const t = useTranslations('Hero');
return (
-
+ {t.rich('title', {
important: (chunks) => (
{chunks}
),
})}
- description={t('description')}
- />
+
+
+ {t('description')}
+
);
};
diff --git a/src/components/landing/Section.tsx b/src/components/landing/Section.tsx
deleted file mode 100644
index 3cd9366f1c..0000000000
--- a/src/components/landing/Section.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { cn } from '@/lib/utils';
-
-export const Section = (props: {
- children: React.ReactNode;
- title?: string;
- subtitle?: string;
- description?: string;
- className?: string;
-}) => (
-
- {(props.title || props.subtitle || props.description) && (
-
- {props.subtitle && (
-
- {props.subtitle}
-
- )}
-
- {props.title && (
-
{props.title}
- )}
-
- {props.description && (
-
- {props.description}
-
- )}
-
- )}
-
-
{props.children}
-
-);
diff --git a/src/components/landing/index.ts b/src/components/landing/index.ts
new file mode 100644
index 0000000000..bc116cba6f
--- /dev/null
+++ b/src/components/landing/index.ts
@@ -0,0 +1,2 @@
+export { Hero } from './Hero';
+export { Features } from './Features';
diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx
new file mode 100644
index 0000000000..98ea89df7b
--- /dev/null
+++ b/src/components/layout/Footer.tsx
@@ -0,0 +1,52 @@
+import Link from 'next/link';
+import { useTranslations } from 'next-intl';
+
+import { Section } from '@/components/layout';
+import { APP_NAME } from '@/lib/constants';
+
+export const Footer = () => {
+ const t = useTranslations('Footer');
+
+ return (
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
+
+
+ © Copyright {new Date().getFullYear()} {APP_NAME}.
+
+
+ -
+ {t('terms_of_service')}
+
+ -
+ {t('privacy_policy')}
+
+
+
+
+
+ );
+};
diff --git a/src/components/LocaleSwitcher.tsx b/src/components/layout/LocaleSwitcher.tsx
similarity index 100%
rename from src/components/LocaleSwitcher.tsx
rename to src/components/layout/LocaleSwitcher.tsx
index 5e500e2882..1ca8dc1adc 100644
--- a/src/components/LocaleSwitcher.tsx
+++ b/src/components/layout/LocaleSwitcher.tsx
@@ -9,10 +9,10 @@ import { setLocale } from '@/actions/locale';
import {
Button,
DropdownMenu,
+ DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
- DropdownMenuTrigger,
} from '@/components/ui';
import { Globe } from 'lucide-react';
diff --git a/src/components/templates/Logo.tsx b/src/components/layout/Logo.tsx
similarity index 50%
rename from src/components/templates/Logo.tsx
rename to src/components/layout/Logo.tsx
index cfb2084921..cc5d946bed 100644
--- a/src/components/templates/Logo.tsx
+++ b/src/components/layout/Logo.tsx
@@ -1,5 +1,5 @@
import { APP_NAME } from '@/lib/constants';
export const Logo = () => (
- {APP_NAME}
+ {APP_NAME}
);
diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx
new file mode 100644
index 0000000000..702ffedd50
--- /dev/null
+++ b/src/components/layout/Navbar.tsx
@@ -0,0 +1,41 @@
+'use client';
+
+import Link from 'next/link';
+import { useTranslations } from 'next-intl';
+
+import { Button } from '@/components/ui';
+import { Section, LocaleSwitcher, ThemeSwitcher } from '@/components/layout';
+
+import { Logo } from './Logo';
+
+export const Navbar = () => {
+ const t = useTranslations('Navbar');
+
+ return (
+
+
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ );
+};
diff --git a/src/components/layout/Section.tsx b/src/components/layout/Section.tsx
new file mode 100644
index 0000000000..d728f880e5
--- /dev/null
+++ b/src/components/layout/Section.tsx
@@ -0,0 +1,9 @@
+import { cn } from '@/lib/utils';
+
+type SectionProps = React.HTMLAttributes;
+
+export const Section = ({ className, children, ...rest }: SectionProps) => (
+
+);
diff --git a/src/components/ThemeProvider.tsx b/src/components/layout/ThemeProvider.tsx
similarity index 100%
rename from src/components/ThemeProvider.tsx
rename to src/components/layout/ThemeProvider.tsx
diff --git a/src/components/ThemeSwitcher.tsx b/src/components/layout/ThemeSwitcher.tsx
similarity index 63%
rename from src/components/ThemeSwitcher.tsx
rename to src/components/layout/ThemeSwitcher.tsx
index ee98b72c9c..a92dbb2d11 100644
--- a/src/components/ThemeSwitcher.tsx
+++ b/src/components/layout/ThemeSwitcher.tsx
@@ -6,13 +6,14 @@ import { useTheme } from 'next-themes';
import {
Button,
DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuRadioGroup,
+ DropdownMenuRadioItem,
} from '@/components/ui';
export function ThemeSwitcher() {
- const { setTheme } = useTheme();
+ const { theme, setTheme } = useTheme();
return (
@@ -23,15 +24,11 @@ export function ThemeSwitcher() {
- setTheme('light')}>
- Light
-
- setTheme('dark')}>
- Dark
-
- setTheme('system')}>
- System
-
+
+ Light
+ Dark
+ System
+
);
diff --git a/src/components/layout/index.ts b/src/components/layout/index.ts
new file mode 100644
index 0000000000..f84adfd68d
--- /dev/null
+++ b/src/components/layout/index.ts
@@ -0,0 +1,6 @@
+export { Navbar } from './Navbar';
+export { Footer } from './Footer';
+export { Section } from './Section';
+export { LocaleSwitcher } from './LocaleSwitcher';
+export { ThemeSwitcher } from './ThemeSwitcher';
+export { Logo } from './Logo';
diff --git a/src/components/templates/Features.tsx b/src/components/templates/Features.tsx
deleted file mode 100644
index 4e83fb7880..0000000000
--- a/src/components/templates/Features.tsx
+++ /dev/null
@@ -1,129 +0,0 @@
-import { useTranslations } from 'next-intl';
-
-import { FeatureCard } from '@/components/landing/FeatureCard';
-import { Section } from '@/components/landing/Section';
-
-export const Features = () => {
- const t = useTranslations('Features');
-
- return (
-
-
-
-
-
-
-
- }
- title={t('feature1_title')}
- >
- {t('feature_description')}
-
-
-
-
-
- }
- title={t('feature2_title')}
- >
- {t('feature_description')}
-
-
-
-
-
- }
- title={t('feature3_title')}
- >
- {t('feature_description')}
-
-
-
-
-
- }
- title={t('feature4_title')}
- >
- {t('feature_description')}
-
-
-
-
-
- }
- title={t('feature5_title')}
- >
- {t('feature_description')}
-
-
-
-
-
- }
- title={t('feature6_title')}
- >
- {t('feature_description')}
-
-
-
-
- );
-};
diff --git a/src/components/templates/Footer.tsx b/src/components/templates/Footer.tsx
deleted file mode 100644
index d843a7229a..0000000000
--- a/src/components/templates/Footer.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import Link from 'next/link';
-import { useTranslations } from 'next-intl';
-
-import { CenteredFooter } from '@/components/landing/CenteredFooter';
-import { Section } from '@/components/landing/Section';
-
-export const Footer = () => {
- const t = useTranslations('Footer');
-
- return (
-
-
- -
-
-
-
-
- -
-
-
-
-
- -
-
-
-
-
- >
- }
- legalLinks={
- <>
- -
- {t('terms_of_service')}
-
- -
- {t('privacy_policy')}
-
- >
- }
- />
-
- );
-};
diff --git a/src/components/templates/Navbar.tsx b/src/components/templates/Navbar.tsx
deleted file mode 100644
index 690c001de5..0000000000
--- a/src/components/templates/Navbar.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import Link from 'next/link';
-import { useTranslations } from 'next-intl';
-
-import { Button } from '@/components/ui';
-import { LocaleSwitcher } from '@/components/LocaleSwitcher';
-import { ThemeSwitcher } from '@/components/ThemeSwitcher';
-import { CenteredMenu } from '@/components/landing/CenteredMenu';
-import { Section } from '@/components/landing/Section';
-
-import { Logo } from './Logo';
-
-export const Navbar = () => {
- const t = useTranslations('Navbar');
-
- return (
-
- }
- rightMenu={
- <>
- -
-
-
- -
-
-
- -
- {t('sign_in')}
-
- -
-
-
- >
- }
- />
-
- );
-};
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
index 28691b2c76..29f98ef9ee 100644
--- a/src/components/ui/button.tsx
+++ b/src/components/ui/button.tsx
@@ -5,7 +5,7 @@ import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive cursor-pointer",
{
variants: {
variant: {
diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx
index 26027549db..700a399a4c 100644
--- a/src/components/ui/dropdown-menu.tsx
+++ b/src/components/ui/dropdown-menu.tsx
@@ -74,7 +74,8 @@ function DropdownMenuItem({
data-inset={inset}
data-variant={variant}
className={cn(
- "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+ "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+ 'cursor-pointer',
className,
)}
{...props}
@@ -92,7 +93,8 @@ function DropdownMenuCheckboxItem({
{
- describe('Render hook', () => {
- it("shouldn't show the menu by default", async () => {
- const { result } = renderHook(() => useMenu());
-
- expect(result.current.showMenu).toBeFalsy();
- });
-
- it('should make the menu visible by toggling the menu', () => {
- const { result } = renderHook(() => useMenu());
-
- act(() => {
- result.current.handleToggleMenu();
- });
-
- expect(result.current.showMenu).toBeTruthy();
- });
-
- it("shouldn't make the menu visible after toggling and closing the menu", () => {
- const { result } = renderHook(() => useMenu());
-
- act(() => {
- result.current.handleClose();
- });
-
- expect(result.current.showMenu).toBeFalsy();
- });
-
- it("shouldn't make the menu visible after toggling the menu twice", () => {
- const { result } = renderHook(() => useMenu());
-
- act(() => {
- result.current.handleToggleMenu();
- result.current.handleToggleMenu();
- });
-
- expect(result.current.showMenu).toBeFalsy();
- });
- });
-});
diff --git a/src/hooks/useMenu.ts b/src/hooks/useMenu.ts
deleted file mode 100644
index 903289b896..0000000000
--- a/src/hooks/useMenu.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { useState } from 'react';
-
-/**
- * React Hook to toggle element. Mostly used for responsive menu.
- * @hook
- */
-export const useMenu = () => {
- const [showMenu, setShowMenu] = useState(false);
-
- const handleToggleMenu = () => {
- setShowMenu((prevState) => !prevState);
- };
-
- const handleClose = () => {
- setShowMenu(false);
- };
-
- return { showMenu, handleToggleMenu, handleClose };
-};
diff --git a/src/lib/server/Env.ts b/src/lib/server/env.ts
similarity index 100%
rename from src/lib/server/Env.ts
rename to src/lib/server/env.ts
diff --git a/src/lib/server/Logger.ts b/src/lib/server/logger.ts
similarity index 84%
rename from src/lib/server/Logger.ts
rename to src/lib/server/logger.ts
index f01a8f2068..7a0ea1d270 100644
--- a/src/lib/server/Logger.ts
+++ b/src/lib/server/logger.ts
@@ -3,7 +3,7 @@ import logtail from '@logtail/pino';
import pino from 'pino';
import pretty from 'pino-pretty';
-import { Env } from './Env';
+import { Env } from './env';
let stream: DestinationStream;
@@ -13,7 +13,7 @@ if (Env.LOGTAIL_SOURCE_TOKEN) {
sourceToken: Env.LOGTAIL_SOURCE_TOKEN,
options: { sendLogsToBetterStack: true },
}),
- { stream: pretty() }, // Prints logs to the console
+ { stream: pretty() }, // Prints logs to the console.
]);
} else {
stream = pretty({ colorize: true });