From 6ee2266c36fdf7c0ac60aad23e738ea69b3d6c96 Mon Sep 17 00:00:00 2001 From: cte Date: Mon, 12 May 2025 11:23:11 -0700 Subject: [PATCH] More progress --- src/app/(authenticated)/dashboard/layout.tsx | 4 +- .../OrgProfile.tsx | 19 +++ .../org/[[...organization-profile]]/page.tsx | 5 + .../[[...organization-profile]]/page.tsx | 23 --- src/app/(authenticated)/dashboard/page.tsx | 32 +--- .../user-profile/[[...user-profile]]/page.tsx | 22 --- .../user/[[...user-profile]]/UserProfile.tsx | 18 +++ .../user/[[...user-profile]]/page.tsx | 5 + src/app/(authenticated)/layout.tsx | 2 +- src/app/layout.tsx | 2 +- src/app/onboarding/create-org/CreateOrg.tsx | 17 ++ src/app/onboarding/create-org/page.tsx | 20 +-- src/app/onboarding/layout.tsx | 20 +++ src/app/onboarding/select-org/SelectOrg.tsx | 18 +++ src/app/onboarding/select-org/page.tsx | 20 +-- src/app/page.tsx | 18 +-- src/app/sign-in/[[...sign-in]]/SignIn.tsx | 22 +++ src/app/sign-in/[[...sign-in]]/page.tsx | 9 +- src/app/sign-up/[[...sign-up]]/SignUp.tsx | 22 +++ src/app/sign-up/[[...sign-up]]/page.tsx | 9 +- src/components/ActiveLink.tsx | 28 ---- src/components/ToggleMenuButton.test.tsx | 19 --- src/components/ToggleMenuButton.tsx | 36 ----- src/components/dashboard/DashboardHeader.tsx | 96 ++---------- src/components/dashboard/DashboardSection.tsx | 21 --- src/components/dashboard/TitleBar.tsx | 13 +- .../landing/CenteredFooter.test.tsx | 24 --- src/components/landing/CenteredFooter.tsx | 34 ---- src/components/landing/CenteredHero.tsx | 25 --- src/components/landing/CenteredMenu.tsx | 51 ------ src/components/landing/FeatureCard.tsx | 17 -- src/components/landing/Features.tsx | 148 ++++++++++++++++++ .../{templates => landing}/Hero.tsx | 13 +- src/components/landing/Section.tsx | 33 ---- src/components/landing/index.ts | 2 + src/components/layout/Footer.tsx | 52 ++++++ .../{ => layout}/LocaleSwitcher.tsx | 2 +- src/components/{templates => layout}/Logo.tsx | 2 +- src/components/layout/Navbar.tsx | 41 +++++ src/components/layout/Section.tsx | 9 ++ src/components/{ => layout}/ThemeProvider.tsx | 0 src/components/{ => layout}/ThemeSwitcher.tsx | 21 ++- src/components/layout/index.ts | 6 + src/components/templates/Features.tsx | 129 --------------- src/components/templates/Footer.tsx | 51 ------ src/components/templates/Navbar.tsx | 40 ----- src/components/ui/button.tsx | 2 +- src/components/ui/dropdown-menu.tsx | 12 +- src/db/index.ts | 2 +- src/hooks/useMenu.test.ts | 44 ------ src/hooks/useMenu.ts | 19 --- src/lib/server/{Env.ts => env.ts} | 0 src/lib/server/{Logger.ts => logger.ts} | 4 +- 53 files changed, 481 insertions(+), 822 deletions(-) create mode 100644 src/app/(authenticated)/dashboard/org/[[...organization-profile]]/OrgProfile.tsx create mode 100644 src/app/(authenticated)/dashboard/org/[[...organization-profile]]/page.tsx delete mode 100644 src/app/(authenticated)/dashboard/organization-profile/[[...organization-profile]]/page.tsx delete mode 100644 src/app/(authenticated)/dashboard/user-profile/[[...user-profile]]/page.tsx create mode 100644 src/app/(authenticated)/dashboard/user/[[...user-profile]]/UserProfile.tsx create mode 100644 src/app/(authenticated)/dashboard/user/[[...user-profile]]/page.tsx create mode 100644 src/app/onboarding/create-org/CreateOrg.tsx create mode 100644 src/app/onboarding/layout.tsx create mode 100644 src/app/onboarding/select-org/SelectOrg.tsx create mode 100644 src/app/sign-in/[[...sign-in]]/SignIn.tsx create mode 100644 src/app/sign-up/[[...sign-up]]/SignUp.tsx delete mode 100644 src/components/ActiveLink.tsx delete mode 100644 src/components/ToggleMenuButton.test.tsx delete mode 100644 src/components/ToggleMenuButton.tsx delete mode 100644 src/components/dashboard/DashboardSection.tsx delete mode 100644 src/components/landing/CenteredFooter.test.tsx delete mode 100644 src/components/landing/CenteredFooter.tsx delete mode 100644 src/components/landing/CenteredHero.tsx delete mode 100644 src/components/landing/CenteredMenu.tsx delete mode 100644 src/components/landing/FeatureCard.tsx create mode 100644 src/components/landing/Features.tsx rename src/components/{templates => landing}/Hero.tsx (56%) delete mode 100644 src/components/landing/Section.tsx create mode 100644 src/components/landing/index.ts create mode 100644 src/components/layout/Footer.tsx rename src/components/{ => layout}/LocaleSwitcher.tsx (100%) rename src/components/{templates => layout}/Logo.tsx (50%) create mode 100644 src/components/layout/Navbar.tsx create mode 100644 src/components/layout/Section.tsx rename src/components/{ => layout}/ThemeProvider.tsx (100%) rename src/components/{ => layout}/ThemeSwitcher.tsx (63%) create mode 100644 src/components/layout/index.ts delete mode 100644 src/components/templates/Features.tsx delete mode 100644 src/components/templates/Footer.tsx delete mode 100644 src/components/templates/Navbar.tsx delete mode 100644 src/hooks/useMenu.test.ts delete mode 100644 src/hooks/useMenu.ts rename src/lib/server/{Env.ts => env.ts} (100%) rename src/lib/server/{Logger.ts => logger.ts} (84%) diff --git a/src/app/(authenticated)/dashboard/layout.tsx b/src/app/(authenticated)/dashboard/layout.tsx index c295afd7f6..978b53eea6 100644 --- a/src/app/(authenticated)/dashboard/layout.tsx +++ b/src/app/(authenticated)/dashboard/layout.tsx @@ -16,12 +16,12 @@ export default function DashboardLayout({ return ( <>
-
+
-
+
{children}
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 && ( -
      - {children} -
    - )} -
      - {iconList} -
    -
    -
    - © Copyright {new Date().getFullYear()} {APP_NAME}. -
    -
      - {legalLinks} -
    -
    -
    -); 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 && ( - - )} -
    -
      - {rightMenu} -
    -
    -
    - ); -}; 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) => ( +
    +
    {children}
    +
    +); 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 });