diff --git a/apps/analytics-web/app/_components/users/UsersTable/UsersTable.tsx b/apps/analytics-web/app/_components/users/UsersTable/UsersTable.tsx index 138f9824..f6657297 100644 --- a/apps/analytics-web/app/_components/users/UsersTable/UsersTable.tsx +++ b/apps/analytics-web/app/_components/users/UsersTable/UsersTable.tsx @@ -3,7 +3,8 @@ import { columns } from './Columns'; import { clerkClient } from '@clerk/nextjs/server'; export default async function VerifiedUsersTable() { - const clerkUsers = await clerkClient.users.getUserList(); + const client = await clerkClient(); + const clerkUsers = await client.users.getUserList(); const users = clerkUsers.data.map((user) => { return { diff --git a/apps/analytics-web/app/api/clerk/route.ts b/apps/analytics-web/app/api/clerk/route.ts index 21dc7481..c039d6f0 100644 --- a/apps/analytics-web/app/api/clerk/route.ts +++ b/apps/analytics-web/app/api/clerk/route.ts @@ -21,7 +21,9 @@ export async function POST(request: NextRequest) { const { verified, userId } = parsedPayload.data; - await clerkClient.users.updateUserMetadata(userId, { + const client = await clerkClient(); + + await client.users.updateUserMetadata(userId, { publicMetadata: { verified, }, diff --git a/apps/analytics-web/app/api/event/route.test.ts b/apps/analytics-web/app/api/event/route.test.ts index 2722fb76..641daeaa 100644 --- a/apps/analytics-web/app/api/event/route.test.ts +++ b/apps/analytics-web/app/api/event/route.test.ts @@ -1,11 +1,11 @@ -import { describe, expect, vi, it, afterEach } from 'vitest'; import { testApiHandler } from 'next-test-api-route-handler'; +import { describe, expect, vi, it, afterEach } from 'vitest'; import * as appHandler from './route'; import { type analyticsEvent } from '@codaco/analytics'; -vi.mock('~/db/insertEvent', () => { +vi.mock('~/app/_actions/actions', () => { return { - default: (eventData: unknown) => ({ data: eventData, error: null }), + insertEvent: (eventData: unknown) => ({ data: eventData, error: null }), }; }); diff --git a/apps/analytics-web/middleware.ts b/apps/analytics-web/middleware.ts index 686b7ad9..046d2472 100644 --- a/apps/analytics-web/middleware.ts +++ b/apps/analytics-web/middleware.ts @@ -1,33 +1,24 @@ import { - authMiddleware, clerkClient, - redirectToSignIn, + clerkMiddleware, + createRouteMatcher, } from '@clerk/nextjs/server'; import { NextResponse } from 'next/server'; -// This example protects all routes including api/trpc routes -// Please edit this to allow other routes to be public as needed. -// See https://clerk.com/docs/references/nextjs/auth-middleware for more information about configuring your Middleware -export default authMiddleware({ - publicRoutes: ['/verification'], - ignoredRoutes: ['/api/event'], - async afterAuth(auth, req, _) { - // handle users who aren't authenticated - if (!auth.userId && !auth.isPublicRoute) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return redirectToSignIn({ returnBackUrl: req.url }); - } +const isPublicRoute = createRouteMatcher(['/verification(.*)']); - // handle users who aren't verified - if (auth.userId) { - const user = await clerkClient.users.getUser(auth.userId); - const isVerified = user?.publicMetadata?.verified; +export default clerkMiddleware(async (auth, req) => { + // Restrict admin route to users with specific role + if (!isPublicRoute(req)) { + const result = await auth.protect(); + const client = await clerkClient(); + const user = await client.users.getUser(result.userId); + const isVerified = user.publicMetadata.verified; - if (!isVerified && req.nextUrl.pathname !== '/verification') { - return NextResponse.redirect(new URL('/verification', req.nextUrl)); - } + if (!isVerified) { + return NextResponse.redirect(new URL('/verification', req.nextUrl)); } - }, + } }); // all routes except static files and /api/event diff --git a/apps/analytics-web/package.json b/apps/analytics-web/package.json index 21cd6cf4..1fa08e73 100644 --- a/apps/analytics-web/package.json +++ b/apps/analytics-web/package.json @@ -15,27 +15,27 @@ "seed": "pnpm run generate && pnpm run migrate && npx tsx scripts/seed.ts" }, "dependencies": { - "@clerk/nextjs": "^5.3.1", + "@clerk/nextjs": "^6.3.3", "@codaco/analytics": "workspace:*", "@codaco/ui": "workspace:*", - "@radix-ui/react-checkbox": "^1.0.4", - "@radix-ui/react-dialog": "^1.0.5", - "@radix-ui/react-dropdown-menu": "^2.0.6", - "@radix-ui/react-select": "^2.0.0", - "@radix-ui/react-slot": "^1.0.2", - "@radix-ui/react-switch": "^1.0.3", - "@radix-ui/react-tabs": "^1.0.4", - "@tanstack/react-table": "^8.10.7", - "@vercel/kv": "^2.0.0", - "@vercel/postgres": "^0.9.0", - "autoprefixer": "^10.4.17", + "@radix-ui/react-checkbox": "^1.1.2", + "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-dropdown-menu": "^2.1.2", + "@radix-ui/react-select": "^2.1.2", + "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-switch": "^1.1.1", + "@radix-ui/react-tabs": "^1.1.1", + "@tanstack/react-table": "^8.20.5", + "@vercel/kv": "^3.0.0", + "@vercel/postgres": "^0.10.0", + "autoprefixer": "^10.4.20", "class-variance-authority": "^0.7.0", - "clsx": "^2.0.0", - "dotenv": "^16.3.1", - "drizzle-orm": "^0.33.0", - "i18n-iso-countries": "^7.7.0", + "clsx": "^2.1.1", + "dotenv": "^16.4.5", + "drizzle-orm": "^0.36.1", + "i18n-iso-countries": "^7.13.0", "lucide-react": "^0.363.0", - "next": "14.2.16", + "next": "^14.2.18", "papaparse": "^5.4.1", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -50,18 +50,19 @@ "@faker-js/faker": "^8.2.0", "@testing-library/react": "^14.1.2", "@types/node": "^20.5.2", - "@types/papaparse": "^5.3.14", - "@types/react": "^18.2.64", - "@types/react-dom": "^18.2.18", - "@vitejs/plugin-react": "^4.2.1", - "drizzle-kit": "^0.24.0", - "eslint": "^8.57.0", - "jsdom": "^24.0.0", - "next-test-api-route-handler": "^4.0.3", + "@types/papaparse": "^5.3.15", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.3", + "drizzle-kit": "^0.28.0", + "eslint": "^8.57.1", + "jsdom": "^25.0.1", + "next-test-api-route-handler": "^4.0.14", "prettier": "^3.3.3", "tailwindcss": "^3.4.1", - "typescript": "^5.5.4", - "vitest": "^2.0.5" + "typescript": "^5.6.3", + "vite-tsconfig-paths": "^5.1.2", + "vitest": "^2.1.5" }, "eslintConfig": { "root": true, diff --git a/apps/analytics-web/vitest.config.ts b/apps/analytics-web/vitest.config.ts index 4ac6027d..1d482eaf 100644 --- a/apps/analytics-web/vitest.config.ts +++ b/apps/analytics-web/vitest.config.ts @@ -1,7 +1,9 @@ import { defineConfig } from 'vitest/config'; +import tsconfigPaths from 'vite-tsconfig-paths'; export default defineConfig({ test: { environment: 'node', }, + plugins: [tsconfigPaths()], }); diff --git a/apps/documentation/app/[locale]/[project]/[...docPath]/page.tsx b/apps/documentation/app/[locale]/[project]/[...docPath]/page.tsx index 6dc25610..65c4db1a 100644 --- a/apps/documentation/app/[locale]/[project]/[...docPath]/page.tsx +++ b/apps/documentation/app/[locale]/[project]/[...docPath]/page.tsx @@ -1,11 +1,11 @@ import { notFound } from 'next/navigation'; -import { unstable_setRequestLocale } from 'next-intl/server'; -import type { LocalesEnum } from '~/app/types'; +import { setRequestLocale } from 'next-intl/server'; +import type { Locale } from '~/app/types'; import Article from '~/components/article'; import { getDocsForRouteSegment, getDocumentForPath } from '~/lib/docs'; type PageParams = { - locale: LocalesEnum; + locale: Locale; project: string; docPath: string[]; }; @@ -38,7 +38,7 @@ export function generateStaticParams({ export default async function Page({ params }: { params: PageParams }) { const { locale, project, docPath } = params; // setting setRequestLocale to support next-intl for static rendering - unstable_setRequestLocale(locale); + setRequestLocale(locale); const document = await getDocumentForPath({ locale, diff --git a/apps/documentation/app/[locale]/[project]/page.tsx b/apps/documentation/app/[locale]/[project]/page.tsx index cc748ff3..1e2a41c3 100644 --- a/apps/documentation/app/[locale]/[project]/page.tsx +++ b/apps/documentation/app/[locale]/[project]/page.tsx @@ -1,16 +1,16 @@ import { notFound } from 'next/navigation'; -import { unstable_setRequestLocale } from 'next-intl/server'; +import { setRequestLocale } from 'next-intl/server'; import { getDocumentForPath } from '~/lib/docs'; import Article from '~/components/article'; -import type { LocalesEnum } from '~/app/types'; +import type { Locale } from '~/app/types'; -type PageProps = { params: { locale: LocalesEnum; project: string } }; +type PageProps = { params: { locale: Locale; project: string } }; export default async function Page({ params }: PageProps) { const { locale, project } = params; // setting setRequestLocale to support next-intl for static rendering - unstable_setRequestLocale(params.locale); + setRequestLocale(params.locale); const document = await getDocumentForPath({ locale, diff --git a/apps/documentation/app/[locale]/layout.tsx b/apps/documentation/app/[locale]/layout.tsx index 3ff38584..93dedaaa 100644 --- a/apps/documentation/app/[locale]/layout.tsx +++ b/apps/documentation/app/[locale]/layout.tsx @@ -2,19 +2,15 @@ import type { Metadata } from 'next'; import { Quicksand } from 'next/font/google'; import { notFound } from 'next/navigation'; import { NextIntlClientProvider } from 'next-intl'; -import { - getNow, - getTimeZone, - unstable_setRequestLocale, -} from 'next-intl/server'; -import type { LocalesEnum, Messages } from '~/app/types'; +import { getNow, getTimeZone, setRequestLocale } from 'next-intl/server'; +import type { Locale, Messages } from '~/app/types'; import { locales } from '~/app/types'; import AIAssistant from '~/components/ai-assistant'; import { LayoutComponent } from '~/components/Layout'; import { ThemeProvider } from '~/components/Providers/theme-provider'; import { GoogleAnalytics } from '@next/third-parties/google'; import { Analytics } from '@vercel/analytics/react'; -import { env } from '~/env.mjs'; +import { env } from '~/env'; const quicksand = Quicksand({ weight: ['300', '400', '500', '600', '700'], @@ -22,12 +18,20 @@ const quicksand = Quicksand({ display: 'swap', }); -export const metadata: Metadata = { - other: { - 'docsearch:language': 'en', - 'docsearch:version': '1.0.1', - }, -}; +export function generateMetadata({ + params: { locale }, +}: { + params: { locale: Locale }; +}) { + const metadata: Metadata = { + other: { + 'docsearch:language': locale, + 'docsearch:version': '1.0.1', + }, + }; + + return metadata; +} export function generateStaticParams() { return locales.map((locale) => ({ locale })); @@ -35,7 +39,7 @@ export function generateStaticParams() { type MainLayoutProps = { children: React.ReactNode; - params: { locale: LocalesEnum }; + params: { locale: Locale }; }; export default async function MainLayout({ @@ -47,7 +51,7 @@ export default async function MainLayout({ if (!isValidLocale) notFound(); // setting setRequestLocale to support next-intl for static rendering - unstable_setRequestLocale(locale); + setRequestLocale(locale); const now = await getNow({ locale }); const timeZone = await getTimeZone({ locale }); @@ -87,7 +91,7 @@ export default async function MainLayout({ - ; + ); } diff --git a/apps/documentation/app/[locale]/page.tsx b/apps/documentation/app/[locale]/page.tsx index 3a372dfa..d716e124 100644 --- a/apps/documentation/app/[locale]/page.tsx +++ b/apps/documentation/app/[locale]/page.tsx @@ -1,10 +1,10 @@ -import { unstable_setRequestLocale } from 'next-intl/server'; -import type { LocalesEnum } from '~/app/types'; +import { setRequestLocale } from 'next-intl/server'; +import type { Locale } from '~/app/types'; import { Hero } from '~/components/Hero'; -const Page = ({ params: { locale } }: { params: { locale: LocalesEnum } }) => { +const Page = ({ params: { locale } }: { params: { locale: Locale } }) => { // setting setRequestLocale to support next-intl for static rendering - unstable_setRequestLocale(locale); + setRequestLocale(locale); return ; }; diff --git a/apps/documentation/app/types.ts b/apps/documentation/app/types.ts index 94dc6e3b..1b84213e 100644 --- a/apps/documentation/app/types.ts +++ b/apps/documentation/app/types.ts @@ -6,9 +6,9 @@ export type ProjectsEnum = (typeof projects)[number]; export const locales = ['en'] as const; -const LocalesEnum = z.enum(locales); +const Locale = z.enum(locales); -export type LocalesEnum = (typeof locales)[number]; +export type Locale = (typeof locales)[number]; export const itemTypes = [ 'project', // Top level projects @@ -72,10 +72,7 @@ export type SidebarLocaleDefinition = z.infer< typeof SidebarLocaleDefinitionSchema >; -export const SideBarSchema = z.record( - LocalesEnum, - SidebarLocaleDefinitionSchema, -); +export const SideBarSchema = z.record(Locale, SidebarLocaleDefinitionSchema); export type TSideBar = z.infer; diff --git a/apps/documentation/components/DocSearchComponent.tsx b/apps/documentation/components/DocSearchComponent.tsx index 60702e72..94ae36f9 100644 --- a/apps/documentation/components/DocSearchComponent.tsx +++ b/apps/documentation/components/DocSearchComponent.tsx @@ -3,7 +3,7 @@ import { DocSearch } from '@docsearch/react'; import { useLocale, useTranslations } from 'next-intl'; import '@docsearch/css'; -import { env } from '~/env.mjs'; +import { env } from '~/env'; import { inputVariants } from '@codaco/ui'; import { Search } from 'lucide-react'; import { cn } from '~/lib/utils'; @@ -137,6 +137,7 @@ const DocSearchComponent = ({ insights={true} placeholder="Search documentation" searchParameters={{ + indexName: env.NEXT_PUBLIC_ALGOLIA_INDEX_NAME, filters: `lang:${locale}`, }} /> diff --git a/apps/documentation/components/ProjectSwitcher.tsx b/apps/documentation/components/ProjectSwitcher.tsx index 53821d8f..8c265f49 100644 --- a/apps/documentation/components/ProjectSwitcher.tsx +++ b/apps/documentation/components/ProjectSwitcher.tsx @@ -12,7 +12,7 @@ import { import { useRouter } from '~/navigation'; import { useLocale, useTranslations } from 'next-intl'; import { usePathname } from 'next/navigation'; -import { type LocalesEnum, type ProjectsEnum, projects } from '~/app/types'; +import { type Locale, type ProjectsEnum, projects } from '~/app/types'; import { forwardRef } from 'react'; import { cn } from '~/lib/utils'; @@ -71,7 +71,7 @@ export default function ProjectSwitcher() { const router = useRouter(); const pathname = usePathname(); const project = pathname.split('/')[2]! as ProjectsEnum; - const locale = useLocale() as LocalesEnum; + const locale = useLocale() as Locale; return (