diff --git a/.changeset/config.json b/.changeset/config.json index f26daf14..0bb16124 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -1,11 +1,11 @@ { - "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", - "changelog": "@changesets/cli/changelog", - "commit": false, - "fixed": [], - "linked": [], - "access": "public", - "baseBranch": "main", - "updateInternalDependencies": "patch", - "ignore": [] + "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] } diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml index 13c7b2f9..4cb8b854 100644 --- a/.github/workflows/check-links.yml +++ b/.github/workflows/check-links.yml @@ -1,11 +1,14 @@ -name: Check for broken links - -on: deployment_status +name: Check for Broken Links +on: + deployment_status jobs: linkinator: runs-on: ubuntu-latest - if: ${{ startsWith(github.event.deployment_status.environment_url, 'https://documentation') && github.event.deployment_status.state == 'success' }} + # Runs if the deployment environment starts with 'preview' and the deployment status is 'success', and if the url does not contain 'analytics' + if: ${{ startsWith(github.event.deployment_status.environment, 'preview') && github.event.deployment_status.state == 'success' }} && !contains(github.event.deployment_status.environment_url, 'analytics') steps: - - name: Run linkinator against deployment preview URL - run: npx --yes linkinator ${{ github.event.deployment_status.environment_url}} -r + - name: Checkout the repository + uses: actions/checkout@v4 + - name: Run dead-link-checker + run: npx @jthrilly/dead-link-checker ${{ github.event.deployment_status.environment_url }} -v --yes diff --git a/.github/workflows/lint-and-format.yml b/.github/workflows/lint-and-format.yml new file mode 100644 index 00000000..09e74fde --- /dev/null +++ b/.github/workflows/lint-and-format.yml @@ -0,0 +1,28 @@ +name: Lint and Format + +on: + pull_request: + push: + branches: + - main + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Install pnpm + run: npm install -g pnpm + - name: Cache pnpm + uses: actions/cache@v3 + with: + path: ~/.pnpm-store + key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} + - name: Install dependencies + run: pnpm install + - name: Run Biome + run: pnpm biome check . \ No newline at end of file diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 00000000..aae19794 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,27 @@ +name: Dependency Update Tests + +on: + pull_request: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Install pnpm + run: npm install -g pnpm + - name: Cache pnpm + uses: actions/cache@v3 + with: + path: ~/.pnpm-store + key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} + - name: Install dependencies + run: pnpm install + - name: Run All Tests + run: pnpm test diff --git a/.gitignore b/.gitignore index f922be27..0cb646b6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,15 +6,13 @@ node_modules .pnp.js .yarn/install-state.gz -# testing -/coverage # next.js -/.next/ -/out/ +**/.next/ +**/out/ # production -/build +**/build # misc .DS_Store @@ -30,7 +28,7 @@ yarn-error.log* .env # vercel -.vercel +**/.vercel # typescript *.tsbuildinfo diff --git a/.vscode/settings.json b/.vscode/settings.json index ad6d6202..fe285093 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,16 +1,16 @@ { - "eslint.workingDirectories": [ - { - "mode": "auto" - } - ], - "typescript.tsdk": "./node_modules/typescript/lib", - "typescript.enablePromptUseWorkspaceTsdk": true, - "cSpell.enableFiletypes": [ - "mdx" - ], - "cSpell.words": [ - "netcanvas", - "Tipbox" - ] -} \ No newline at end of file + "typescript.tsdk": "./node_modules/typescript/lib", + "typescript.enablePromptUseWorkspaceTsdk": true, + "cSpell.words": ["netcanvas", "Tipbox"], + "editor.defaultFormatter": "biomejs.biome", + "[typescriptreact]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[typescript]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "editor.codeActionsOnSave": { + "source.organizeImports": "always", + "source.fixAll": "always" + } +} diff --git a/apps/analytics-web/app/_actions/actions.ts b/apps/analytics-web/app/_actions/actions.ts index 37fac63e..aeff3e93 100644 --- a/apps/analytics-web/app/_actions/actions.ts +++ b/apps/analytics-web/app/_actions/actions.ts @@ -1,36 +1,31 @@ -'use server'; +"use server"; -import { type EventInsertType, db } from '~/db/db'; -import { eventsTable } from '~/db/schema'; +import { type EventInsertType, db } from "~/db/db"; +import { eventsTable } from "~/db/schema"; export async function getEvents() { - try { - const events = await db.query.eventsTable.findMany({ - orderBy: (events, { desc }) => [desc(events.timestamp)], - }); + try { + const events = await db.query.eventsTable.findMany({ + orderBy: (events, { desc }) => [desc(events.timestamp)], + }); - return events; - } catch (error) { - // eslint-disable-next-line no-console - console.error('Error getting events', error); - return []; - } + return events; + } catch (error) { + console.error("Error getting events", error); + return []; + } } type Events = Awaited>; export type Event = Events[0]; export async function insertEvent(event: EventInsertType) { - try { - const insertedEvent = await db - .insert(eventsTable) - .values(event) - .returning(); + try { + const insertedEvent = await db.insert(eventsTable).values(event).returning(); - return { data: insertedEvent, error: null }; - } catch (error) { - // eslint-disable-next-line no-console - console.error('Error inserting events', error); - return { data: null, error: 'Error inserting events' }; - } + return { data: insertedEvent, error: null }; + } catch (error) { + console.error("Error inserting events", error); + return { data: null, error: "Error inserting events" }; + } } diff --git a/apps/analytics-web/app/_components/analytics/AnalyticsView.tsx b/apps/analytics-web/app/_components/analytics/AnalyticsView.tsx index 4d5032f9..9f985309 100644 --- a/apps/analytics-web/app/_components/analytics/AnalyticsView.tsx +++ b/apps/analytics-web/app/_components/analytics/AnalyticsView.tsx @@ -1,38 +1,38 @@ -import { getEvents } from '~/app/_actions/actions'; -import { Card, CardContent, CardHeader } from '~/components/ui/card'; -import EventsTable from './EventsTable/EventsTable'; -import RegionsTable from './RegionsTable/RegionsTable'; -import TotalAppsCard from './cards/TotalAppsCard'; -import TotalDataExported from './cards/TotalDataExported'; -import TotalErrorsCard from './cards/TotalErrorsCard'; -import TotalInterviewsCompletedCard from './cards/TotalInterviewsCompletedCard'; -import TotalInterviewsStartedCard from './cards/TotalInterviewsStartedCard'; -import TotalProtocolsInstalledCard from './cards/TotalProtocolsInstalledCard'; +import { getEvents } from "~/app/_actions/actions"; +import { Card, CardContent, CardHeader } from "~/components/ui/card"; +import EventsTable from "./EventsTable/EventsTable"; +import RegionsTable from "./RegionsTable/RegionsTable"; +import TotalAppsCard from "./cards/TotalAppsCard"; +import TotalDataExported from "./cards/TotalDataExported"; +import TotalErrorsCard from "./cards/TotalErrorsCard"; +import TotalInterviewsCompletedCard from "./cards/TotalInterviewsCompletedCard"; +import TotalInterviewsStartedCard from "./cards/TotalInterviewsStartedCard"; +import TotalProtocolsInstalledCard from "./cards/TotalProtocolsInstalledCard"; export default async function AnalyticsView() { - const events = await getEvents(); + const events = await getEvents(); - return ( -
-
- - - - - - -
-
-
- -
- - Regions - - - - -
-
- ); + return ( +
+
+ + + + + + +
+
+
+ +
+ + Regions + + + + +
+
+ ); } diff --git a/apps/analytics-web/app/_components/analytics/EventsTable/Columns.tsx b/apps/analytics-web/app/_components/analytics/EventsTable/Columns.tsx index 26d2be38..d87fba90 100644 --- a/apps/analytics-web/app/_components/analytics/EventsTable/Columns.tsx +++ b/apps/analytics-web/app/_components/analytics/EventsTable/Columns.tsx @@ -1,90 +1,73 @@ -'use client'; +"use client"; -import { type ColumnDef } from '@tanstack/react-table'; -import { type Dispatch, type SetStateAction } from 'react'; -import { DataTableColumnHeader } from '~/components/DataTable/column-header'; -import { MetadataDialog } from '~/components/MetadataDialog'; -import { type EventType } from './EventsTable'; -import { StackTraceDialog } from './StackTraceDialog'; -import TableFilter from './TableFilter'; -import { type Event } from '~/app/_actions/actions'; +import type { ColumnDef } from "@tanstack/react-table"; +import type { Dispatch, SetStateAction } from "react"; +import type { Event } from "~/app/_actions/actions"; +import { DataTableColumnHeader } from "~/components/DataTable/column-header"; +import { MetadataDialog } from "~/components/MetadataDialog"; +import type { EventType } from "./EventsTable"; +import { StackTraceDialog } from "./StackTraceDialog"; +import TableFilter from "./TableFilter"; -export const getColumns = ( - eventTypes: EventType[], - setEventTypes: Dispatch>, -) => { - const columns: ColumnDef[] = [ - { - accessorKey: 'type', - header: ({ column }) => ( -
- - -
- ), - }, - { - accessorKey: 'timestamp', - header: ({ column }) => ( - - ), - cell: ({ row }) => { - return ( -
- {row.original.timestamp.toUTCString()} -
- ); - }, - }, - { - accessorKey: 'installationId', - header: ({ column }) => ( - - ), - cell: ({ row }) => { - return
{row.original.installationId}
; - }, - }, - { - accessorKey: 'name', - header: ({ column }) => ( - - ), - }, - { - accessorKey: 'message', - header: ({ column }) => ( - - ), - }, - { - accessorKey: 'cause', - header: ({ column }) => ( - - ), - }, - { - accessorKey: 'stack', - header: '', - cell: ({ row }) => - row.original.stack && ( -
- -
- ), - }, - { - accessorKey: 'metadata', - header: '', - cell: ({ row }) => { - return ( -
- -
- ); - }, - }, - ]; +export const getColumns = (eventTypes: EventType[], setEventTypes: Dispatch>) => { + const columns: ColumnDef[] = [ + { + accessorKey: "type", + header: ({ column }) => ( +
+ + +
+ ), + }, + { + accessorKey: "timestamp", + header: ({ column }) => , + cell: ({ row }) => { + return
{row.original.timestamp.toUTCString()}
; + }, + }, + { + accessorKey: "installationId", + header: ({ column }) => , + cell: ({ row }) => { + return
{row.original.installationId}
; + }, + }, + { + accessorKey: "name", + header: ({ column }) => , + }, + { + accessorKey: "message", + header: ({ column }) => , + }, + { + accessorKey: "cause", + header: ({ column }) => , + }, + { + accessorKey: "stack", + header: "", + cell: ({ row }) => + row.original.stack && ( +
+ +
+ ), + }, + { + accessorKey: "metadata", + header: "", + cell: ({ row }) => { + return ( +
+ +
+ ); + }, + }, + ]; - return columns; + return columns; }; diff --git a/apps/analytics-web/app/_components/analytics/EventsTable/EventsTable.tsx b/apps/analytics-web/app/_components/analytics/EventsTable/EventsTable.tsx index 9a2b0edd..dfa01d48 100644 --- a/apps/analytics-web/app/_components/analytics/EventsTable/EventsTable.tsx +++ b/apps/analytics-web/app/_components/analytics/EventsTable/EventsTable.tsx @@ -1,67 +1,59 @@ -'use client'; +"use client"; -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { getEvents, type Event } from '~/app/_actions/actions'; -import { DataTable } from '~/components/DataTable/data-table'; -import ExportButton from '~/components/ExportButton'; -import { getColumns } from './Columns'; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { type Event, getEvents } from "~/app/_actions/actions"; +import { DataTable } from "~/components/DataTable/data-table"; +import ExportButton from "~/components/ExportButton"; +import { getColumns } from "./Columns"; export type EventType = { - text: string; - isSelected: boolean; + text: string; + isSelected: boolean; }; export default function EventsTable() { - const [events, setEvents] = useState([]); - const [eventTypes, setEventTypes] = useState([]); - - const fetchEvents = useCallback(async () => { - const data = await getEvents(); - setEvents(data); - }, []); - - useEffect(() => { - void fetchEvents(); - }, [fetchEvents]); - - useEffect(() => { - const eventTypesMap = new Map(); - events.forEach((event) => - eventTypesMap.set(event.type, { text: event.type, isSelected: true }), - ); - - setEventTypes([...Array.from(eventTypesMap.values())]); - }, [events]); - - const filteredEvents = useMemo(() => { - const filters = eventTypes - .filter((type) => type.isSelected) - .map((type) => type.text); - - return events.filter((event) => filters.includes(event.type)); - }, [eventTypes, events]); - - return ( - <> -
-
-

Events

- {!events.length &&

Loading...

} -
- {!!events.length && ( - - )} -
- -
- {!!events.length && ( - - )} -
- - ); + const [events, setEvents] = useState([]); + const [eventTypes, setEventTypes] = useState([]); + + const fetchEvents = useCallback(async () => { + const data = await getEvents(); + setEvents(data); + }, []); + + useEffect(() => { + void fetchEvents(); + }, [fetchEvents]); + + useEffect(() => { + const eventTypesMap = new Map(); + for (const event of events) { + eventTypesMap.set(event.type, { text: event.type, isSelected: true }); + } + + setEventTypes([...Array.from(eventTypesMap.values())]); + }, [events]); + + const filteredEvents = useMemo(() => { + const filters = eventTypes.filter((type) => type.isSelected).map((type) => type.text); + + return events.filter((event) => filters.includes(event.type)); + }, [eventTypes, events]); + + return ( + <> +
+
+

Events

+ {!events.length &&

Loading...

} +
+ {!!events.length && } +
+ +
+ {!!events.length && ( + + )} +
+ + ); } diff --git a/apps/analytics-web/app/_components/analytics/EventsTable/StackTraceDialog.tsx b/apps/analytics-web/app/_components/analytics/EventsTable/StackTraceDialog.tsx index 08f36c5d..a7c63c58 100644 --- a/apps/analytics-web/app/_components/analytics/EventsTable/StackTraceDialog.tsx +++ b/apps/analytics-web/app/_components/analytics/EventsTable/StackTraceDialog.tsx @@ -1,13 +1,8 @@ -import { type Event } from '~/app/_actions/actions'; -import { DialogButton } from '~/components/DialogButton'; +import type { Event } from "~/app/_actions/actions"; +import { DialogButton } from "~/components/DialogButton"; export function StackTraceDialog({ error }: { error: Event }) { - return ( - - ); + return ( + + ); } diff --git a/apps/analytics-web/app/_components/analytics/EventsTable/TableFilter.tsx b/apps/analytics-web/app/_components/analytics/EventsTable/TableFilter.tsx index f027d660..80148497 100644 --- a/apps/analytics-web/app/_components/analytics/EventsTable/TableFilter.tsx +++ b/apps/analytics-web/app/_components/analytics/EventsTable/TableFilter.tsx @@ -1,88 +1,82 @@ -'use client'; +"use client"; -import { useState, type Dispatch, type SetStateAction } from 'react'; -import { Button } from '~/components/ui/button'; -import { Checkbox } from '~/components/ui/checkbox'; +import { type Dispatch, type SetStateAction, useState } from "react"; +import { Button } from "~/components/ui/button"; +import { Checkbox } from "~/components/ui/checkbox"; import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '~/components/ui/dropdown-menu'; -import { type EventType } from './EventsTable'; + DropdownMenu, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "~/components/ui/dropdown-menu"; +import type { EventType } from "./EventsTable"; type TableFilterProps = { - eventTypes: EventType[]; - setEventTypes: Dispatch>; + eventTypes: EventType[]; + setEventTypes: Dispatch>; }; const TableFilter = ({ eventTypes, setEventTypes }: TableFilterProps) => { - const [options, setOptions] = useState(eventTypes); + const [options, setOptions] = useState(eventTypes); - const toggleOption = (option: string) => { - setOptions((prevState) => - prevState.map((t) => - t.text === option ? { ...t, isSelected: !t.isSelected } : t, - ), - ); - }; + const toggleOption = (option: string) => { + setOptions((prevState) => prevState.map((t) => (t.text === option ? { ...t, isSelected: !t.isSelected } : t))); + }; - const toggleAllOptions = (isSelected: boolean) => { - setOptions((prevState) => prevState.map((t) => ({ ...t, isSelected }))); - }; + const toggleAllOptions = (isSelected: boolean) => { + setOptions((prevState) => prevState.map((t) => ({ ...t, isSelected }))); + }; - const isAllSelected = options.every((option) => option.isSelected); + const isAllSelected = options.every((option) => option.isSelected); - return ( - - - - - - Select events - + return ( + + + + + + Select events + -
- - +
+ + - {options.map((option) => ( - - ))} + {options.map((option) => ( + + ))} - -
- - - ); + +
+
+
+ ); }; export default TableFilter; diff --git a/apps/analytics-web/app/_components/analytics/RegionsTable/Columns.tsx b/apps/analytics-web/app/_components/analytics/RegionsTable/Columns.tsx index 8c30a90c..944aea00 100644 --- a/apps/analytics-web/app/_components/analytics/RegionsTable/Columns.tsx +++ b/apps/analytics-web/app/_components/analytics/RegionsTable/Columns.tsx @@ -1,13 +1,13 @@ -'use client'; -import { type ColumnDef } from '@tanstack/react-table'; -import { type RegionTotal } from '~/utils/getRegionsTotals'; +"use client"; +import type { ColumnDef } from "@tanstack/react-table"; +import type { RegionTotal } from "~/utils/getRegionsTotals"; export const columns: ColumnDef[] = [ - { - accessorKey: 'country', - header: 'Country', - }, - { - accessorKey: 'total', - header: 'Total', - }, + { + accessorKey: "country", + header: "Country", + }, + { + accessorKey: "total", + header: "Total", + }, ]; diff --git a/apps/analytics-web/app/_components/analytics/RegionsTable/RegionsTable.tsx b/apps/analytics-web/app/_components/analytics/RegionsTable/RegionsTable.tsx index 552c3169..f1d2cfbf 100644 --- a/apps/analytics-web/app/_components/analytics/RegionsTable/RegionsTable.tsx +++ b/apps/analytics-web/app/_components/analytics/RegionsTable/RegionsTable.tsx @@ -1,9 +1,9 @@ -import { DataTable } from '~/components/DataTable/data-table'; -import getRegionsTotals from '~/utils/getRegionsTotals'; -import { columns } from './Columns'; -import { type Event } from '~/app/_actions/actions'; +import type { Event } from "~/app/_actions/actions"; +import { DataTable } from "~/components/DataTable/data-table"; +import getRegionsTotals from "~/utils/getRegionsTotals"; +import { columns } from "./Columns"; export default function ErrorsTable({ events }: { events: Event[] }) { - const regionsTotals = getRegionsTotals(events); - return ; + const regionsTotals = getRegionsTotals(events); + return ; } diff --git a/apps/analytics-web/app/_components/analytics/cards/TotalAppsCard.tsx b/apps/analytics-web/app/_components/analytics/cards/TotalAppsCard.tsx index 4cc3d929..9575ba9e 100644 --- a/apps/analytics-web/app/_components/analytics/cards/TotalAppsCard.tsx +++ b/apps/analytics-web/app/_components/analytics/cards/TotalAppsCard.tsx @@ -1,16 +1,16 @@ -import { getTotalAppsSetup } from '~/utils/getTotalAppsSetup'; -import { SummaryCard } from '~/components/SummaryCard'; -import { type Event } from '~/app/_actions/actions'; +import type { Event } from "~/app/_actions/actions"; +import { SummaryCard } from "~/components/SummaryCard"; +import { getTotalAppsSetup } from "~/utils/getTotalAppsSetup"; const TotalAppsCard = ({ events }: { events: Event[] }) => { - const totalAppsSetup = getTotalAppsSetup(events); - return ( - - ); + const totalAppsSetup = getTotalAppsSetup(events); + return ( + + ); }; export default TotalAppsCard; diff --git a/apps/analytics-web/app/_components/analytics/cards/TotalDataExported.tsx b/apps/analytics-web/app/_components/analytics/cards/TotalDataExported.tsx index d9cda49a..1bab7472 100644 --- a/apps/analytics-web/app/_components/analytics/cards/TotalDataExported.tsx +++ b/apps/analytics-web/app/_components/analytics/cards/TotalDataExported.tsx @@ -1,16 +1,16 @@ -import { SummaryCard } from '~/components/SummaryCard'; -import { type Event } from '~/app/_actions/actions'; -import { getTotalDataExported } from '~/utils/getTotalDataExported'; +import type { Event } from "~/app/_actions/actions"; +import { SummaryCard } from "~/components/SummaryCard"; +import { getTotalDataExported } from "~/utils/getTotalDataExported"; const TotalDataExported = ({ events }: { events: Event[] }) => { - const totalDataExported = getTotalDataExported(events); - return ( - - ); + const totalDataExported = getTotalDataExported(events); + return ( + + ); }; export default TotalDataExported; diff --git a/apps/analytics-web/app/_components/analytics/cards/TotalErrorsCard.tsx b/apps/analytics-web/app/_components/analytics/cards/TotalErrorsCard.tsx index a5beaef3..dac7ed8c 100644 --- a/apps/analytics-web/app/_components/analytics/cards/TotalErrorsCard.tsx +++ b/apps/analytics-web/app/_components/analytics/cards/TotalErrorsCard.tsx @@ -1,16 +1,16 @@ -import { type Event } from '~/app/_actions/actions'; -import { SummaryCard } from '~/components/SummaryCard'; -import { getTotalErrors } from '~/utils/getTotalErrors'; +import type { Event } from "~/app/_actions/actions"; +import { SummaryCard } from "~/components/SummaryCard"; +import { getTotalErrors } from "~/utils/getTotalErrors"; const TotalErrorsCard = ({ events }: { events: Event[] }) => { - const totalErrors = getTotalErrors(events); - return ( - - ); + const totalErrors = getTotalErrors(events); + return ( + + ); }; export default TotalErrorsCard; diff --git a/apps/analytics-web/app/_components/analytics/cards/TotalInterviewsCompletedCard.tsx b/apps/analytics-web/app/_components/analytics/cards/TotalInterviewsCompletedCard.tsx index 3ceaae6c..86ec1aa9 100644 --- a/apps/analytics-web/app/_components/analytics/cards/TotalInterviewsCompletedCard.tsx +++ b/apps/analytics-web/app/_components/analytics/cards/TotalInterviewsCompletedCard.tsx @@ -1,16 +1,16 @@ -import { getTotalInterviewsCompleted } from '~/utils/getTotalInterviewsCompleted'; -import { SummaryCard } from '~/components/SummaryCard'; -import { type Event } from '~/app/_actions/actions'; +import type { Event } from "~/app/_actions/actions"; +import { SummaryCard } from "~/components/SummaryCard"; +import { getTotalInterviewsCompleted } from "~/utils/getTotalInterviewsCompleted"; const TotalAppsCard = ({ events }: { events: Event[] }) => { - const totalInterviewsCompleted = getTotalInterviewsCompleted(events); - return ( - - ); + const totalInterviewsCompleted = getTotalInterviewsCompleted(events); + return ( + + ); }; export default TotalAppsCard; diff --git a/apps/analytics-web/app/_components/analytics/cards/TotalInterviewsStartedCard.tsx b/apps/analytics-web/app/_components/analytics/cards/TotalInterviewsStartedCard.tsx index c349f7d3..cb6cedb6 100644 --- a/apps/analytics-web/app/_components/analytics/cards/TotalInterviewsStartedCard.tsx +++ b/apps/analytics-web/app/_components/analytics/cards/TotalInterviewsStartedCard.tsx @@ -1,16 +1,16 @@ -import { getTotalInterviewsStarted } from '~/utils/getTotalInterviewsStarted'; -import { SummaryCard } from '~/components/SummaryCard'; -import { type Event } from '~/app/_actions/actions'; +import type { Event } from "~/app/_actions/actions"; +import { SummaryCard } from "~/components/SummaryCard"; +import { getTotalInterviewsStarted } from "~/utils/getTotalInterviewsStarted"; const TotalInterviewsStartedCard = ({ events }: { events: Event[] }) => { - const totalInterviewsStarted = getTotalInterviewsStarted(events); - return ( - - ); + const totalInterviewsStarted = getTotalInterviewsStarted(events); + return ( + + ); }; export default TotalInterviewsStartedCard; diff --git a/apps/analytics-web/app/_components/analytics/cards/TotalProtocolsInstalledCard.tsx b/apps/analytics-web/app/_components/analytics/cards/TotalProtocolsInstalledCard.tsx index 3508b7b5..894682b0 100644 --- a/apps/analytics-web/app/_components/analytics/cards/TotalProtocolsInstalledCard.tsx +++ b/apps/analytics-web/app/_components/analytics/cards/TotalProtocolsInstalledCard.tsx @@ -1,16 +1,16 @@ -import { getTotalProtocolsInstalled } from '~/utils/getTotalProtocolsInstalled'; -import { SummaryCard } from '~/components/SummaryCard'; -import { type Event } from '~/app/_actions/actions'; +import type { Event } from "~/app/_actions/actions"; +import { SummaryCard } from "~/components/SummaryCard"; +import { getTotalProtocolsInstalled } from "~/utils/getTotalProtocolsInstalled"; const TotalProtocolsInstalledCard = ({ events }: { events: Event[] }) => { - const totalProtocolsInstalled = getTotalProtocolsInstalled(events); - return ( - - ); + const totalProtocolsInstalled = getTotalProtocolsInstalled(events); + return ( + + ); }; export default TotalProtocolsInstalledCard; diff --git a/apps/analytics-web/app/_components/users/UserManagementDialog.tsx b/apps/analytics-web/app/_components/users/UserManagementDialog.tsx index a55c9c38..8ae72e43 100644 --- a/apps/analytics-web/app/_components/users/UserManagementDialog.tsx +++ b/apps/analytics-web/app/_components/users/UserManagementDialog.tsx @@ -1,30 +1,30 @@ -import { Button } from '~/components/ui/button'; +import { Users as UsersIcon } from "lucide-react"; +import { Button } from "~/components/ui/button"; import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, -} from '~/components/ui/dialog'; -import { Users as UsersIcon } from 'lucide-react'; -import VerifiedUsersTable from './UsersTable/UsersTable'; + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "~/components/ui/dialog"; +import VerifiedUsersTable from "./UsersTable/UsersTable"; export default function UserManagementDialog() { - return ( - - - - - - - User Management - - View and manage verified users - - - - ); + return ( + + + + + + + User Management + + View and manage verified users + + + + ); } diff --git a/apps/analytics-web/app/_components/users/UsersTable/Columns.tsx b/apps/analytics-web/app/_components/users/UsersTable/Columns.tsx index 5522ac37..08c95ec2 100644 --- a/apps/analytics-web/app/_components/users/UsersTable/Columns.tsx +++ b/apps/analytics-web/app/_components/users/UsersTable/Columns.tsx @@ -1,40 +1,35 @@ -'use client'; -import { type ColumnDef } from '@tanstack/react-table'; -import VerifyUserSwitch from './VerifyUserSwitch'; +"use client"; +import type { ColumnDef } from "@tanstack/react-table"; +import VerifyUserSwitch from "./VerifyUserSwitch"; type UserColumn = ColumnDef< - { - id: string; - fullName: string; - username: string | null; - verified: boolean; - }, - unknown + { + id: string; + fullName: string; + username: string | null; + verified: boolean; + }, + unknown >; export const columns: UserColumn[] = [ - { - accessorKey: 'user', - header: 'User', - cell: ({ row }) => { - return ( -
-
{row.original.fullName}
-
{row.original.username}
-
- ); - }, - }, - { - accessorKey: 'verified', - header: 'Verified', - cell: ({ row }) => { - return ( - - ); - }, - }, + { + accessorKey: "user", + header: "User", + cell: ({ row }) => { + return ( +
+
{row.original.fullName}
+
{row.original.username}
+
+ ); + }, + }, + { + accessorKey: "verified", + header: "Verified", + cell: ({ row }) => { + return ; + }, + }, ]; diff --git a/apps/analytics-web/app/_components/users/UsersTable/UsersTable.tsx b/apps/analytics-web/app/_components/users/UsersTable/UsersTable.tsx index f6657297..1bc6d512 100644 --- a/apps/analytics-web/app/_components/users/UsersTable/UsersTable.tsx +++ b/apps/analytics-web/app/_components/users/UsersTable/UsersTable.tsx @@ -1,19 +1,19 @@ -import { DataTable } from '~/components/DataTable/data-table'; -import { columns } from './Columns'; -import { clerkClient } from '@clerk/nextjs/server'; +import { clerkClient } from "@clerk/nextjs/server"; +import { DataTable } from "~/components/DataTable/data-table"; +import { columns } from "./Columns"; export default async function VerifiedUsersTable() { - const client = await clerkClient(); - const clerkUsers = await client.users.getUserList(); + const client = await clerkClient(); + const clerkUsers = await client.users.getUserList(); - const users = clerkUsers.data.map((user) => { - return { - id: user.id, - fullName: user.firstName + ' ' + user.lastName, - username: user.username, - verified: !!user.publicMetadata.verified, - }; - }); + const users = clerkUsers.data.map((user) => { + return { + id: user.id, + fullName: `${user.firstName} ${user.lastName}`, + username: user.username, + verified: !!user.publicMetadata.verified, + }; + }); - return ; + return ; } diff --git a/apps/analytics-web/app/_components/users/UsersTable/VerifyUserSwitch.tsx b/apps/analytics-web/app/_components/users/UsersTable/VerifyUserSwitch.tsx index 8d19b4bb..fdb16bc1 100644 --- a/apps/analytics-web/app/_components/users/UsersTable/VerifyUserSwitch.tsx +++ b/apps/analytics-web/app/_components/users/UsersTable/VerifyUserSwitch.tsx @@ -1,43 +1,38 @@ -import { useState } from 'react'; -import { Switch } from '~/components/ui/switch'; +import { useState } from "react"; +import { Switch } from "~/components/ui/switch"; type VerifyUserSwitchProps = { - id: string; - verified: boolean; + id: string; + verified: boolean; }; -export default function VerifyUserSwitch({ - id, - verified: initialVerified, -}: VerifyUserSwitchProps) { - const [localVerified, setLocalVerified] = useState(initialVerified); +export default function VerifyUserSwitch({ id, verified: initialVerified }: VerifyUserSwitchProps) { + const [localVerified, setLocalVerified] = useState(initialVerified); - const updateMetadata = async () => { - try { - const response = await fetch('/api/clerk', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ userId: id, verified: !localVerified }), - }); + const updateMetadata = async () => { + try { + const response = await fetch("/api/clerk", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ userId: id, verified: !localVerified }), + }); - if (!response.ok) { - setLocalVerified(!localVerified); - // eslint-disable-next-line no-console - console.error('Database update failed.'); - } - } catch (error) { - setLocalVerified; - // eslint-disable-next-line no-console - console.error('Error updating database:', error); - } - }; + if (!response.ok) { + setLocalVerified(!localVerified); + console.error("Database update failed."); + } + } catch (error) { + setLocalVerified; + console.error("Error updating database:", error); + } + }; - const handleToggle = async () => { - setLocalVerified(!localVerified); - await updateMetadata(); - }; + const handleToggle = async () => { + setLocalVerified(!localVerified); + await updateMetadata(); + }; - return ; + return ; } diff --git a/apps/analytics-web/app/api/clerk/route.ts b/apps/analytics-web/app/api/clerk/route.ts index c039d6f0..38bd1768 100644 --- a/apps/analytics-web/app/api/clerk/route.ts +++ b/apps/analytics-web/app/api/clerk/route.ts @@ -1,33 +1,30 @@ -import { clerkClient } from '@clerk/nextjs/server'; -import { type NextRequest, NextResponse } from 'next/server'; -import z from 'zod'; +import { clerkClient } from "@clerk/nextjs/server"; +import { type NextRequest, NextResponse } from "next/server"; +import z from "zod"; const clerkPayloadSchema = z.object({ - verified: z.boolean(), - userId: z.string(), + verified: z.boolean(), + userId: z.string(), }); export async function POST(request: NextRequest) { - const payload: unknown = await request.json(); + const payload: unknown = await request.json(); - const parsedPayload = clerkPayloadSchema.safeParse(payload); + const parsedPayload = clerkPayloadSchema.safeParse(payload); - if (!parsedPayload.success) { - return NextResponse.json( - { error: 'Invalid clerk payload' }, - { status: 400 }, - ); - } + if (!parsedPayload.success) { + return NextResponse.json({ error: "Invalid clerk payload" }, { status: 400 }); + } - const { verified, userId } = parsedPayload.data; + const { verified, userId } = parsedPayload.data; - const client = await clerkClient(); + const client = await clerkClient(); - await client.users.updateUserMetadata(userId, { - publicMetadata: { - verified, - }, - }); + await client.users.updateUserMetadata(userId, { + publicMetadata: { + verified, + }, + }); - return NextResponse.json({ success: true }); + return NextResponse.json({ success: true }); } diff --git a/apps/analytics-web/app/api/event/route.test.ts b/apps/analytics-web/app/api/event/route.test.ts index 641daeaa..e0d9c5b6 100644 --- a/apps/analytics-web/app/api/event/route.test.ts +++ b/apps/analytics-web/app/api/event/route.test.ts @@ -1,84 +1,84 @@ -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'; +import type { analyticsEvent } from "@codaco/analytics/src"; +import { testApiHandler } from "next-test-api-route-handler"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import * as appHandler from "./route"; -vi.mock('~/app/_actions/actions', () => { - return { - insertEvent: (eventData: unknown) => ({ data: eventData, error: null }), - }; +vi.mock("~/app/_actions/actions", () => { + return { + insertEvent: (eventData: unknown) => ({ data: eventData, error: null }), + }; }); -describe('/api/event', () => { - afterEach(() => { - vi.resetAllMocks(); - }); - it('should insert a valid event to the database', async () => { - const eventData: analyticsEvent = { - type: 'AppSetup', - metadata: { - details: 'testing details', - path: 'testing path', - }, - countryISOCode: 'US', - installationId: '21321546453213123', - timestamp: new Date().toString(), - }; +describe("/api/event", () => { + afterEach(() => { + vi.resetAllMocks(); + }); + it("should insert a valid event to the database", async () => { + const eventData: analyticsEvent = { + type: "AppSetup", + metadata: { + details: "testing details", + path: "testing path", + }, + countryISOCode: "US", + installationId: "21321546453213123", + timestamp: new Date().toString(), + }; - await testApiHandler({ - appHandler, - test: async ({ fetch }) => { - const response = await fetch({ - method: 'POST', - body: JSON.stringify(eventData), - }); - expect(response.status).toBe(200); - expect(await response.json()).toEqual({ event: eventData }); - }, - }); - }); + await testApiHandler({ + appHandler, + test: async ({ fetch }) => { + const response = await fetch({ + method: "POST", + body: JSON.stringify(eventData), + }); + expect(response.status).toBe(200); + expect(await response.json()).toEqual({ event: eventData }); + }, + }); + }); - it('should insert a valid error into the database', async () => { - const eventData: analyticsEvent = { - type: 'Error', - name: 'TestError', - message: 'Test message', - stack: 'Test stack', - metadata: { - details: 'testing details', - path: 'testing path', - }, - countryISOCode: 'US', - installationId: '21321546453213123', - timestamp: new Date().toString(), - }; + it("should insert a valid error into the database", async () => { + const eventData: analyticsEvent = { + type: "Error", + name: "TestError", + message: "Test message", + stack: "Test stack", + metadata: { + details: "testing details", + path: "testing path", + }, + countryISOCode: "US", + installationId: "21321546453213123", + timestamp: new Date().toString(), + }; - await testApiHandler({ - appHandler, - test: async ({ fetch }) => { - const response = await fetch({ - method: 'POST', - body: JSON.stringify(eventData), - }); - expect(response.status).toBe(200); - }, - }); - }); + await testApiHandler({ + appHandler, + test: async ({ fetch }) => { + const response = await fetch({ + method: "POST", + body: JSON.stringify(eventData), + }); + expect(response.status).toBe(200); + }, + }); + }); - it('should return 400 if event is invalid', async () => { - const eventData = { - type: 'InvalidEvent', - }; + it("should return 400 if event is invalid", async () => { + const eventData = { + type: "InvalidEvent", + }; - await testApiHandler({ - appHandler, - test: async ({ fetch }) => { - const response = await fetch({ - method: 'POST', - body: JSON.stringify(eventData), - }); - expect(response.status).toBe(400); - }, - }); - }); + await testApiHandler({ + appHandler, + test: async ({ fetch }) => { + const response = await fetch({ + method: "POST", + body: JSON.stringify(eventData), + }); + expect(response.status).toBe(400); + }, + }); + }); }); diff --git a/apps/analytics-web/app/api/event/route.ts b/apps/analytics-web/app/api/event/route.ts index f68a7c7a..ca95d609 100644 --- a/apps/analytics-web/app/api/event/route.ts +++ b/apps/analytics-web/app/api/event/route.ts @@ -1,47 +1,41 @@ -import { type NextRequest, NextResponse } from 'next/server'; -import { AnalyticsEventSchema } from '@codaco/analytics'; -import { insertEvent } from '~/app/_actions/actions'; +import { AnalyticsEventSchema } from "@codaco/analytics/src"; +import { type NextRequest, NextResponse } from "next/server"; +import { insertEvent } from "~/app/_actions/actions"; // Allow CORS requests from anywhere. const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'POST, OPTIONS', - 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "POST, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type, Authorization", }; -export const runtime = 'edge'; +export const runtime = "edge"; export async function POST(request: NextRequest) { - const event = (await request.json()) as unknown; - const parsedEvent = AnalyticsEventSchema.safeParse(event); - - if (!parsedEvent.success) { - return NextResponse.json( - { error: 'Invalid event' }, - { status: 400, headers: corsHeaders }, - ); - } - - const formattedEvent = { - ...parsedEvent.data, - timestamp: new Date(parsedEvent.data.timestamp), // Convert back into a date object - }; - - const result = await insertEvent(formattedEvent); - - if (result.error) { - return NextResponse.json( - { error: 'Error inserting events' }, - { status: 500, headers: corsHeaders }, - ); - } - - return NextResponse.json({ event }, { status: 200, headers: corsHeaders }); + const event = (await request.json()) as unknown; + const parsedEvent = AnalyticsEventSchema.safeParse(event); + + if (!parsedEvent.success) { + return NextResponse.json({ error: "Invalid event" }, { status: 400, headers: corsHeaders }); + } + + const formattedEvent = { + ...parsedEvent.data, + timestamp: new Date(parsedEvent.data.timestamp), // Convert back into a date object + }; + + const result = await insertEvent(formattedEvent); + + if (result.error) { + return NextResponse.json({ error: "Error inserting events" }, { status: 500, headers: corsHeaders }); + } + + return NextResponse.json({ event }, { status: 200, headers: corsHeaders }); } export function OPTIONS() { - return new NextResponse(null, { - status: 200, - headers: corsHeaders, - }); + return new NextResponse(null, { + status: 200, + headers: corsHeaders, + }); } diff --git a/apps/analytics-web/app/globals.css b/apps/analytics-web/app/globals.css index 59d83e7b..89389cbd 100644 --- a/apps/analytics-web/app/globals.css +++ b/apps/analytics-web/app/globals.css @@ -3,61 +3,60 @@ @tailwind utilities; @layer base { - :root { - /* Semantic slots */ - --background: var(--platinum); - --foreground: var(--cyber-grape); + :root { + /* Semantic slots */ + --background: var(--platinum); + --foreground: var(--cyber-grape); - --primary: var(--cyber-grape); - --primary-foreground: var(--white); + --primary: var(--cyber-grape); + --primary-foreground: var(--white); - --secondary: var(--neon-coral); - --secondary-foreground: var(--white); + --secondary: var(--neon-coral); + --secondary-foreground: var(--white); - --muted: var(--platinum); - --muted-foreground: var(--cyber-grape-hue) - calc(var(--cyber-grape-saturation) - 10%) - calc(var(--cyber-grape-lightness) - 10%); + --muted: var(--platinum); + --muted-foreground: var(--cyber-grape-hue) calc(var(--cyber-grape-saturation) - 10%) + calc(var(--cyber-grape-lightness) - 10%); - --accent: var(--slate-blue); - --accent-foreground: var(--white); + --accent: var(--slate-blue); + --accent-foreground: var(--white); - --destructive: var(--tomato); - --destructive-foreground: var(--white); + --destructive: var(--tomato); + --destructive-foreground: var(--white); - --warning: var(--neon-carrot); - --warning-foreground: var(--white); + --warning: var(--neon-carrot); + --warning-foreground: var(--white); - --info: var(--cerulean-blue); - --info-foreground: var(--white); + --info: var(--cerulean-blue); + --info-foreground: var(--white); - --success: var(--sea-green); - --success-foreground: var(--white); + --success: var(--sea-green); + --success-foreground: var(--white); - --border: var(--platinum--dark); + --border: var(--platinum--dark); - --link: var(--neon-coral); + --link: var(--neon-coral); - --card: 0 0% 100%; + --card: 0 0% 100%; - --panel: var(--platinum); + --panel: var(--platinum); - --input: 0 0% 100%; - --input-foreground: var(--cyber-grape); + --input: 0 0% 100%; + --input-foreground: var(--cyber-grape); - --popover: var(--white); - --popover-foreground: var(--foreground); - } + --popover: var(--white); + --popover-foreground: var(--foreground); + } } @layer base { - * { - @apply border-border; - } - body { - @apply bg-background text-foreground; - } - code { - @apply text-sm; - } + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } + code { + @apply text-sm; + } } diff --git a/apps/analytics-web/app/layout.tsx b/apps/analytics-web/app/layout.tsx index a245eb8d..fc89e773 100644 --- a/apps/analytics-web/app/layout.tsx +++ b/apps/analytics-web/app/layout.tsx @@ -1,26 +1,26 @@ -import type { Metadata } from 'next'; -import { Inter } from 'next/font/google'; -import '@codaco/tailwind-config/globals.css'; -import './globals.css'; -import { ClerkProvider } from '@clerk/nextjs'; +import { ClerkProvider } from "@clerk/nextjs"; +import "@codaco/tailwind-config/globals.css"; +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import "./globals.css"; -const inter = Inter({ subsets: ['latin'] }); +const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { - title: 'Fresco Analytics ', - description: 'This is the analytics dashboard for Fresco.', + title: "Fresco Analytics ", + description: "This is the analytics dashboard for Fresco.", }; export default function RootLayout({ - children, + children, }: { - children: React.ReactNode; + children: React.ReactNode; }) { - return ( - - - {children} - - - ); + return ( + + + {children} + + + ); } diff --git a/apps/analytics-web/app/page.tsx b/apps/analytics-web/app/page.tsx index 47652f4b..d326ff67 100644 --- a/apps/analytics-web/app/page.tsx +++ b/apps/analytics-web/app/page.tsx @@ -1,21 +1,21 @@ -import { UserButton } from '@clerk/nextjs'; -import AnalyticsView from '~/app/_components/analytics/AnalyticsView'; -import UserManagementDialog from './_components/users/UserManagementDialog'; +import { UserButton } from "@clerk/nextjs"; +import AnalyticsView from "~/app/_components/analytics/AnalyticsView"; +import UserManagementDialog from "./_components/users/UserManagementDialog"; export default function DashboardPage() { - return ( -
-
-
-

Dashboard

-
- - -
-
+ return ( +
+
+
+

Dashboard

+
+ + +
+
- -
-
- ); + +
+
+ ); } diff --git a/apps/analytics-web/app/verification/page.tsx b/apps/analytics-web/app/verification/page.tsx index c7a32d18..653c3c25 100644 --- a/apps/analytics-web/app/verification/page.tsx +++ b/apps/analytics-web/app/verification/page.tsx @@ -1,22 +1,22 @@ -'use client'; -import { UserButton, useUser } from '@clerk/nextjs'; -import { redirect } from 'next/navigation'; +"use client"; +import { UserButton, useUser } from "@clerk/nextjs"; +import { redirect } from "next/navigation"; export default function Verification() { - const { user } = useUser(); - const isVerified = user?.publicMetadata?.verified; - if (isVerified) { - redirect('/'); - } + const { user } = useUser(); + const isVerified = user?.publicMetadata?.verified; + if (isVerified) { + redirect("/"); + } - return ( -
-
-

User Unverified

-

Please contact the site administrator to be verified.

-
+ return ( +
+
+

User Unverified

+

Please contact the site administrator to be verified.

+
- -
- ); + +
+ ); } diff --git a/apps/analytics-web/components.json b/apps/analytics-web/components.json index eb7a42e3..ee2d7424 100644 --- a/apps/analytics-web/components.json +++ b/apps/analytics-web/components.json @@ -1,16 +1,16 @@ { - "$schema": "https://ui.shadcn.com/schema.json", - "style": "default", - "rsc": true, - "tsx": true, - "tailwind": { - "config": "tailwind.config.ts", - "css": "app/globals.css", - "baseColor": "slate", - "cssVariables": true - }, - "aliases": { - "components": "~/components", - "utils": "~/utils/shadcn" - } + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "slate", + "cssVariables": true + }, + "aliases": { + "components": "~/components", + "utils": "~/utils/shadcn" + } } diff --git a/apps/analytics-web/components/DataTable/column-header.tsx b/apps/analytics-web/components/DataTable/column-header.tsx index 0aaa345d..0d6d74b7 100644 --- a/apps/analytics-web/components/DataTable/column-header.tsx +++ b/apps/analytics-web/components/DataTable/column-header.tsx @@ -1,63 +1,55 @@ -import { ArrowDown, ArrowUp, ArrowUpDown } from 'lucide-react'; -import { type Column } from '@tanstack/react-table'; +import type { Column } from "@tanstack/react-table"; +import { ArrowDown, ArrowUp, ArrowUpDown } from "lucide-react"; -import { Button } from '~/components/ui/button'; +import { Button } from "~/components/ui/button"; import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '~/components/ui/dropdown-menu'; -import { cn } from '~/utils/shadcn'; + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "~/components/ui/dropdown-menu"; +import { cn } from "~/utils/shadcn"; type DataTableColumnHeaderProps = { - column: Column; - title?: string; + column: Column; + title?: string; } & React.HTMLAttributes; export function DataTableColumnHeader({ - column, - title, - className, + column, + title, + className, }: DataTableColumnHeaderProps) { - if (!column.getCanSort()) { - return
{title}
; - } + if (!column.getCanSort()) { + return
{title}
; + } - return ( -
- - - - - - column.toggleSorting(false)}> - - Asc - - column.toggleSorting(true)}> - - Desc - - - -
- ); + return ( +
+ + + + + + column.toggleSorting(false)}> + + Asc + + column.toggleSorting(true)}> + + Desc + + + +
+ ); } diff --git a/apps/analytics-web/components/DataTable/data-table-pagination.tsx b/apps/analytics-web/components/DataTable/data-table-pagination.tsx index 6c779cb7..78aea09a 100644 --- a/apps/analytics-web/components/DataTable/data-table-pagination.tsx +++ b/apps/analytics-web/components/DataTable/data-table-pagination.tsx @@ -1,98 +1,84 @@ -import { - ChevronLeft, - ChevronRight, - ChevronsLeft, - ChevronsRight, -} from 'lucide-react'; +import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from "lucide-react"; -import { type Table } from '@tanstack/react-table'; +import type { Table } from "@tanstack/react-table"; -import { Button } from '~/components/ui/button'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '~/components/ui/select'; +import { Button } from "~/components/ui/button"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select"; type DataTablePaginationProps = { - table: Table; + table: Table; }; -export function DataTablePagination({ - table, -}: DataTablePaginationProps) { - return ( -
- {/*
+export function DataTablePagination({ table }: DataTablePaginationProps) { + return ( +
+ {/*
{table.getFilteredSelectedRowModel().rows.length} of{" "} {table.getFilteredRowModel().rows.length} row(s) selected.
*/} -
-
-

Rows per page

- -
-
- Page {table.getState().pagination.pageIndex + 1} of{' '} - {table.getPageCount()} -
-
- - - - -
-
-
- ); +
+
+

Rows per page

+ +
+
+ Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()} +
+
+ + + + +
+
+
+ ); } diff --git a/apps/analytics-web/components/DataTable/data-table.tsx b/apps/analytics-web/components/DataTable/data-table.tsx index 42ef0572..faa590fa 100644 --- a/apps/analytics-web/components/DataTable/data-table.tsx +++ b/apps/analytics-web/components/DataTable/data-table.tsx @@ -1,96 +1,75 @@ -'use client'; +"use client"; import { - type ColumnDef, - type SortingState, - flexRender, - getCoreRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable, -} from '@tanstack/react-table'; + type ColumnDef, + type SortingState, + flexRender, + getCoreRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from '~/components/ui/table'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table"; type DataTableProps = { - columns: ColumnDef[]; - data: TData[]; - pagination?: boolean; + columns: ColumnDef[]; + data: TData[]; + pagination?: boolean; }; -import { DataTablePagination } from '~/components/DataTable/data-table-pagination'; -import { useState } from 'react'; +import { useState } from "react"; +import { DataTablePagination } from "~/components/DataTable/data-table-pagination"; -export function DataTable({ - columns, - data, - pagination, -}: DataTableProps) { - const [sorting, setSorting] = useState([]); - const table = useReactTable({ - data, - columns, - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - onSortingChange: setSorting, - getSortedRowModel: getSortedRowModel(), - state: { - sorting, - }, - }); +export function DataTable({ columns, data, pagination }: DataTableProps) { + const [sorting, setSorting] = useState([]); + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + onSortingChange: setSorting, + getSortedRowModel: getSortedRowModel(), + state: { + sorting, + }, + }); - return ( -
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - return ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext(), - )} - - ); - })} - - ))} - - - {table.getRowModel().rows?.length ? ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} - - )) - ) : ( - - - No results. - - - )} - -
- {pagination && } -
- ); + return ( +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + {flexRender(cell.column.columnDef.cell, cell.getContext())} + ))} + + )) + ) : ( + + + No results. + + + )} + +
+ {pagination && } +
+ ); } diff --git a/apps/analytics-web/components/DialogButton.tsx b/apps/analytics-web/components/DialogButton.tsx index daeebb90..e7b2c603 100644 --- a/apps/analytics-web/components/DialogButton.tsx +++ b/apps/analytics-web/components/DialogButton.tsx @@ -1,41 +1,36 @@ -import { type ReactNode } from 'react'; -import { Button } from '~/components/ui/button'; +import type { ReactNode } from "react"; +import { Button } from "~/components/ui/button"; import { - Dialog, - DialogContent, - DialogHeader, - DialogDescription, - DialogTitle, - DialogTrigger, -} from '~/components/ui/dialog'; + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "~/components/ui/dialog"; type DialogButtonProps = { - buttonLabel: string; - title: string; - description: string; - content: ReactNode; + buttonLabel: string; + title: string; + description: string | null; + content: ReactNode; }; -export function DialogButton({ - buttonLabel, - title, - description, - content, -}: DialogButtonProps) { - return ( - - - - - - - {title} - - {description} -
- {content} -
-
-
- ); +export function DialogButton({ buttonLabel, title, description, content }: DialogButtonProps) { + return ( + + + + + + + {title} + + {description} +
+ {content} +
+
+
+ ); } diff --git a/apps/analytics-web/components/ExportButton.tsx b/apps/analytics-web/components/ExportButton.tsx index 5a4da9e9..b834a9d0 100644 --- a/apps/analytics-web/components/ExportButton.tsx +++ b/apps/analytics-web/components/ExportButton.tsx @@ -1,38 +1,38 @@ -'use client'; +"use client"; -import { Download } from 'lucide-react'; -import Papa from 'papaparse'; -import { type Event } from '~/app/_actions/actions'; -import { Button } from '~/components/ui/button'; +import { Download } from "lucide-react"; +import Papa from "papaparse"; +import type { Event } from "~/app/_actions/actions"; +import { Button } from "~/components/ui/button"; type ExportButtonProps = { - data: Event[]; - filename: string; + data: Event[]; + filename: string; }; const ExportButton: React.FC = ({ data, filename }) => { - const handleExportCSV = () => { - const csvData = Papa.unparse(data); + const handleExportCSV = () => { + const csvData = Papa.unparse(data); - const blob = new Blob([csvData], { type: 'text/csv;charset=utf-8;' }); + const blob = new Blob([csvData], { type: "text/csv;charset=utf-8;" }); - const link = document.createElement('a'); - if (link.download !== undefined) { - const url = URL.createObjectURL(blob); - link.setAttribute('href', url); - link.setAttribute('download', filename); - link.style.visibility = 'hidden'; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - } - }; + const link = document.createElement("a"); + if (link.download !== undefined) { + const url = URL.createObjectURL(blob); + link.setAttribute("href", url); + link.setAttribute("download", filename); + link.style.visibility = "hidden"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } + }; - return ( - - ); + return ( + + ); }; export default ExportButton; diff --git a/apps/analytics-web/components/MetadataDialog.tsx b/apps/analytics-web/components/MetadataDialog.tsx index 23ddd03d..da005b5d 100644 --- a/apps/analytics-web/components/MetadataDialog.tsx +++ b/apps/analytics-web/components/MetadataDialog.tsx @@ -1,13 +1,13 @@ -import { type Event } from '~/app/_actions/actions'; -import { DialogButton } from '~/components/DialogButton'; +import type { Event } from "~/app/_actions/actions"; +import { DialogButton } from "~/components/DialogButton"; export function MetadataDialog({ event }: { event: Event }) { - return ( - - ); + return ( + + ); } diff --git a/apps/analytics-web/components/SummaryCard.tsx b/apps/analytics-web/components/SummaryCard.tsx index 9e151d9c..8816729c 100644 --- a/apps/analytics-web/components/SummaryCard.tsx +++ b/apps/analytics-web/components/SummaryCard.tsx @@ -1,27 +1,21 @@ -import { Card, CardHeader, CardTitle, CardContent } from '~/components/ui/card'; +import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; type SummaryCardProps = { - title: string; - value: number; - description?: string; + title: string; + value: number; + description?: string; }; -export const SummaryCard = ({ - title, - value, - description, -}: SummaryCardProps) => { - return ( - - - {title} - - -
{value}
- {description && ( -

{description}

- )} -
-
- ); +export const SummaryCard = ({ title, value, description }: SummaryCardProps) => { + return ( + + + {title} + + +
{value}
+ {description &&

{description}

} +
+
+ ); }; diff --git a/apps/analytics-web/components/ui/badge.tsx b/apps/analytics-web/components/ui/badge.tsx index f0950752..25a103fe 100644 --- a/apps/analytics-web/components/ui/badge.tsx +++ b/apps/analytics-web/components/ui/badge.tsx @@ -1,36 +1,29 @@ -import * as React from 'react'; -import { cva, type VariantProps } from 'class-variance-authority'; +import { type VariantProps, cva } from "class-variance-authority"; +import type * as React from "react"; -import { cn } from '~/utils/shadcn'; +import { cn } from "~/utils/shadcn"; const badgeVariants = cva( - 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', - { - variants: { - variant: { - default: - 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80', - secondary: - 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80', - destructive: - 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80', - outline: 'text-foreground', - }, - }, - defaultVariants: { - variant: 'default', - }, - }, + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + }, ); -export type BadgeProps = object & - React.HTMLAttributes & - VariantProps; +export type BadgeProps = object & React.HTMLAttributes & VariantProps; function Badge({ className, variant, ...props }: BadgeProps) { - return ( -
- ); + return
; } export { Badge, badgeVariants }; diff --git a/apps/analytics-web/components/ui/button.tsx b/apps/analytics-web/components/ui/button.tsx index f64f15c1..b21dc584 100644 --- a/apps/analytics-web/components/ui/button.tsx +++ b/apps/analytics-web/components/ui/button.tsx @@ -1,55 +1,46 @@ -import * as React from 'react'; -import { Slot } from '@radix-ui/react-slot'; -import { cva, type VariantProps } from 'class-variance-authority'; +import { Slot } from "@radix-ui/react-slot"; +import { type VariantProps, cva } from "class-variance-authority"; +import * as React from "react"; -import { cn } from '~/utils/shadcn'; +import { cn } from "~/utils/shadcn"; const buttonVariants = cva( - 'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', - { - variants: { - variant: { - default: 'bg-primary text-primary-foreground hover:bg-primary/90', - destructive: - 'bg-destructive text-destructive-foreground hover:bg-destructive/90', - outline: - 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', - secondary: - 'bg-secondary text-secondary-foreground hover:bg-secondary/80', - ghost: 'hover:bg-accent hover:text-accent-foreground', - link: 'text-primary underline-offset-4 hover:underline', - }, - size: { - default: 'h-10 px-4 py-2', - sm: 'h-9 rounded-md px-3', - lg: 'h-11 rounded-md px-8', - icon: 'h-10 w-10', - }, - }, - defaultVariants: { - variant: 'default', - size: 'default', - }, - }, + "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, ); export type ButtonProps = { - asChild?: boolean; + asChild?: boolean; } & React.ButtonHTMLAttributes & - VariantProps; + VariantProps; const Button = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : 'button'; - return ( - - ); - }, + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ; + }, ); -Button.displayName = 'Button'; +Button.displayName = "Button"; export { Button, buttonVariants }; diff --git a/apps/analytics-web/components/ui/card.tsx b/apps/analytics-web/components/ui/card.tsx index 1a97fad9..b4d70925 100644 --- a/apps/analytics-web/components/ui/card.tsx +++ b/apps/analytics-web/components/ui/card.tsx @@ -1,88 +1,45 @@ -import * as React from 'react'; +import * as React from "react"; -import { cn } from '~/utils/shadcn'; +import { cn } from "~/utils/shadcn"; -const Card = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
+const Card = React.forwardRef>(({ className, ...props }, ref) => ( +
)); -Card.displayName = 'Card'; - -const CardHeader = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -CardHeader.displayName = 'CardHeader'; - -const CardTitle = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, children, ...props }, ref) => ( -

- {children} -

-)); -CardTitle.displayName = 'CardTitle'; - -const CardDescription = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

-)); -CardDescription.displayName = 'CardDescription'; - -const CardContent = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

-)); -CardContent.displayName = 'CardContent'; - -const CardFooter = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -CardFooter.displayName = 'CardFooter'; - -export { - Card, - CardHeader, - CardFooter, - CardTitle, - CardDescription, - CardContent, -}; +Card.displayName = "Card"; + +const CardHeader = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ), +); +CardHeader.displayName = "CardHeader"; + +const CardTitle = React.forwardRef>( + ({ className, children, ...props }, ref) => ( +

+ {children} +

+ ), +); +CardTitle.displayName = "CardTitle"; + +const CardDescription = React.forwardRef>( + ({ className, ...props }, ref) => ( +

+ ), +); +CardDescription.displayName = "CardDescription"; + +const CardContent = React.forwardRef>( + ({ className, ...props }, ref) =>

, +); +CardContent.displayName = "CardContent"; + +const CardFooter = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ), +); +CardFooter.displayName = "CardFooter"; + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }; diff --git a/apps/analytics-web/components/ui/checkbox.tsx b/apps/analytics-web/components/ui/checkbox.tsx index 917b7100..0503d0f6 100644 --- a/apps/analytics-web/components/ui/checkbox.tsx +++ b/apps/analytics-web/components/ui/checkbox.tsx @@ -1,29 +1,27 @@ -'use client'; +"use client"; -import * as React from 'react'; -import * as CheckboxPrimitive from '@radix-ui/react-checkbox'; -import { Check } from 'lucide-react'; +import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; +import { Check } from "lucide-react"; +import * as React from "react"; -import { cn } from '~/utils/shadcn'; +import { cn } from "~/utils/shadcn"; const Checkbox = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - - - - - + + + + + )); Checkbox.displayName = CheckboxPrimitive.Root.displayName; diff --git a/apps/analytics-web/components/ui/dialog.tsx b/apps/analytics-web/components/ui/dialog.tsx index 44dab426..5b3ffed9 100644 --- a/apps/analytics-web/components/ui/dialog.tsx +++ b/apps/analytics-web/components/ui/dialog.tsx @@ -1,10 +1,10 @@ -'use client'; +"use client"; -import * as React from 'react'; -import * as DialogPrimitive from '@radix-ui/react-dialog'; -import { X } from 'lucide-react'; +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { X } from "lucide-react"; +import * as React from "react"; -import { cn } from '~/utils/shadcn'; +import { cn } from "~/utils/shadcn"; const Dialog = DialogPrimitive.Root; @@ -15,108 +15,83 @@ const DialogPortal = DialogPrimitive.Portal; const DialogClose = DialogPrimitive.Close; const DialogOverlay = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )); DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; const DialogContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( - - - - {children} - - - Close - - - + + + + {children} + + + Close + + + )); DialogContent.displayName = DialogPrimitive.Content.displayName; -const DialogHeader = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
+const DialogHeader = ({ className, ...props }: React.HTMLAttributes) => ( +
); -DialogHeader.displayName = 'DialogHeader'; +DialogHeader.displayName = "DialogHeader"; -const DialogFooter = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
+const DialogFooter = ({ className, ...props }: React.HTMLAttributes) => ( +
); -DialogFooter.displayName = 'DialogFooter'; +DialogFooter.displayName = "DialogFooter"; const DialogTitle = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )); DialogTitle.displayName = DialogPrimitive.Title.displayName; const DialogDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )); DialogDescription.displayName = DialogPrimitive.Description.displayName; export { - Dialog, - DialogPortal, - DialogOverlay, - DialogClose, - DialogTrigger, - DialogContent, - DialogHeader, - DialogFooter, - DialogTitle, - DialogDescription, + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, }; diff --git a/apps/analytics-web/components/ui/dropdown-menu.tsx b/apps/analytics-web/components/ui/dropdown-menu.tsx index 6f2f4ac6..bcbe2113 100644 --- a/apps/analytics-web/components/ui/dropdown-menu.tsx +++ b/apps/analytics-web/components/ui/dropdown-menu.tsx @@ -1,10 +1,10 @@ -'use client'; +"use client"; -import * as React from 'react'; -import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; -import { Check, ChevronRight, Circle } from 'lucide-react'; +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; +import { Check, ChevronRight, Circle } from "lucide-react"; +import * as React from "react"; -import { cn } from '~/utils/shadcn'; +import { cn } from "~/utils/shadcn"; const DropdownMenu = DropdownMenuPrimitive.Root; @@ -19,182 +19,163 @@ const DropdownMenuSub = DropdownMenuPrimitive.Sub; const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; const DropdownMenuSubTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean; - } + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } >(({ className, inset, children, ...props }, ref) => ( - - {children} - - + + {children} + + )); -DropdownMenuSubTrigger.displayName = - DropdownMenuPrimitive.SubTrigger.displayName; +DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName; const DropdownMenuSubContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )); -DropdownMenuSubContent.displayName = - DropdownMenuPrimitive.SubContent.displayName; +DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName; const DropdownMenuContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, sideOffset = 4, ...props }, ref) => ( - - - + + + )); DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; const DropdownMenuItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean; - } + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } >(({ className, inset, ...props }, ref) => ( - + )); DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; const DropdownMenuCheckboxItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, children, checked, ...props }, ref) => ( - - - - - - - {children} - + + + + + + + {children} + )); -DropdownMenuCheckboxItem.displayName = - DropdownMenuPrimitive.CheckboxItem.displayName; +DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName; const DropdownMenuRadioItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( - - - - - - - {children} - + + + + + + + {children} + )); DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; const DropdownMenuLabel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean; - } + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } >(({ className, inset, ...props }, ref) => ( - + )); DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; const DropdownMenuSeparator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )); DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; -const DropdownMenuShortcut = ({ - className, - ...props -}: React.HTMLAttributes) => { - return ( - - ); +const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => { + return ; }; -DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'; +DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; export { - DropdownMenu, - DropdownMenuTrigger, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuCheckboxItem, - DropdownMenuRadioItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuShortcut, - DropdownMenuGroup, - DropdownMenuPortal, - DropdownMenuSub, - DropdownMenuSubContent, - DropdownMenuSubTrigger, - DropdownMenuRadioGroup, + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, }; diff --git a/apps/analytics-web/components/ui/select.tsx b/apps/analytics-web/components/ui/select.tsx index 3115f71c..e433e3b2 100644 --- a/apps/analytics-web/components/ui/select.tsx +++ b/apps/analytics-web/components/ui/select.tsx @@ -1,10 +1,10 @@ -'use client'; +"use client"; -import * as React from 'react'; -import * as SelectPrimitive from '@radix-ui/react-select'; -import { Check, ChevronDown } from 'lucide-react'; +import * as SelectPrimitive from "@radix-ui/react-select"; +import { Check, ChevronDown } from "lucide-react"; +import * as React from "react"; -import { cn } from '~/utils/shadcn'; +import { cn } from "~/utils/shadcn"; const Select = SelectPrimitive.Root; @@ -13,109 +13,92 @@ const SelectGroup = SelectPrimitive.Group; const SelectValue = SelectPrimitive.Value; const SelectTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( - - {children} - - - - + + {children} + + + + )); SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; const SelectContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, position = 'popper', ...props }, ref) => ( - - - - {children} - - - + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + {children} + + + )); SelectContent.displayName = SelectPrimitive.Content.displayName; const SelectLabel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )); SelectLabel.displayName = SelectPrimitive.Label.displayName; const SelectItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( - - - - - - + + + + + + - {children} - + {children} + )); SelectItem.displayName = SelectPrimitive.Item.displayName; const SelectSeparator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )); SelectSeparator.displayName = SelectPrimitive.Separator.displayName; -export { - Select, - SelectGroup, - SelectValue, - SelectTrigger, - SelectContent, - SelectLabel, - SelectItem, - SelectSeparator, -}; +export { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectLabel, SelectItem, SelectSeparator }; diff --git a/apps/analytics-web/components/ui/switch.tsx b/apps/analytics-web/components/ui/switch.tsx index 434dc624..a0907779 100644 --- a/apps/analytics-web/components/ui/switch.tsx +++ b/apps/analytics-web/components/ui/switch.tsx @@ -1,28 +1,28 @@ -'use client'; +"use client"; -import * as React from 'react'; -import * as SwitchPrimitives from '@radix-ui/react-switch'; +import * as SwitchPrimitives from "@radix-ui/react-switch"; +import * as React from "react"; -import { cn } from '~/utils/shadcn'; +import { cn } from "~/utils/shadcn"; const Switch = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - - - + + + )); Switch.displayName = SwitchPrimitives.Root.displayName; diff --git a/apps/analytics-web/components/ui/table.tsx b/apps/analytics-web/components/ui/table.tsx index 94dcb2b7..6fc8df8e 100644 --- a/apps/analytics-web/components/ui/table.tsx +++ b/apps/analytics-web/components/ui/table.tsx @@ -1,114 +1,72 @@ -import * as React from 'react'; +import * as React from "react"; -import { cn } from '~/utils/shadcn'; +import { cn } from "~/utils/shadcn"; -const Table = React.forwardRef< - HTMLTableElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
- - -)); -Table.displayName = 'Table'; +const Table = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+
+ + ), +); +Table.displayName = "Table"; -const TableHeader = React.forwardRef< - HTMLTableSectionElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( - -)); -TableHeader.displayName = 'TableHeader'; +const TableHeader = React.forwardRef>( + ({ className, ...props }, ref) => , +); +TableHeader.displayName = "TableHeader"; -const TableBody = React.forwardRef< - HTMLTableSectionElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( - -)); -TableBody.displayName = 'TableBody'; +const TableBody = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ), +); +TableBody.displayName = "TableBody"; -const TableFooter = React.forwardRef< - HTMLTableSectionElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( - -)); -TableFooter.displayName = 'TableFooter'; +const TableFooter = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ), +); +TableFooter.displayName = "TableFooter"; -const TableRow = React.forwardRef< - HTMLTableRowElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( - -)); -TableRow.displayName = 'TableRow'; +const TableRow = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ), +); +TableRow.displayName = "TableRow"; -const TableHead = React.forwardRef< - HTMLTableCellElement, - React.ThHTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -TableHead.displayName = 'TableHead'; +const TableHead = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ), +); +TableHead.displayName = "TableHead"; -const TableCell = React.forwardRef< - HTMLTableCellElement, - React.TdHTMLAttributes ->(({ className, ...props }, ref) => ( - -)); -TableCell.displayName = 'TableCell'; +const TableCell = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ), +); +TableCell.displayName = "TableCell"; -const TableCaption = React.forwardRef< - HTMLTableCaptionElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -TableCaption.displayName = 'TableCaption'; +const TableCaption = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ), +); +TableCaption.displayName = "TableCaption"; -export { - Table, - TableHeader, - TableBody, - TableFooter, - TableHead, - TableRow, - TableCell, - TableCaption, -}; +export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }; diff --git a/apps/analytics-web/components/ui/tabs.tsx b/apps/analytics-web/components/ui/tabs.tsx index 2fd908d6..e01c9cbc 100644 --- a/apps/analytics-web/components/ui/tabs.tsx +++ b/apps/analytics-web/components/ui/tabs.tsx @@ -1,54 +1,54 @@ -'use client'; +"use client"; -import * as React from 'react'; -import * as TabsPrimitive from '@radix-ui/react-tabs'; +import * as TabsPrimitive from "@radix-ui/react-tabs"; +import * as React from "react"; -import { cn } from '~/utils/shadcn'; +import { cn } from "~/utils/shadcn"; const Tabs = TabsPrimitive.Root; const TabsList = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )); TabsList.displayName = TabsPrimitive.List.displayName; const TabsTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )); TabsTrigger.displayName = TabsPrimitive.Trigger.displayName; const TabsContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )); TabsContent.displayName = TabsPrimitive.Content.displayName; diff --git a/apps/analytics-web/db/db.ts b/apps/analytics-web/db/db.ts index 1b4eece0..786b0b5d 100644 --- a/apps/analytics-web/db/db.ts +++ b/apps/analytics-web/db/db.ts @@ -1,7 +1,7 @@ -import { sql } from '@vercel/postgres'; -import { drizzle } from 'drizzle-orm/vercel-postgres'; -import * as schema from './schema'; -import { type eventsTable } from './schema'; +import { sql } from "@vercel/postgres"; +import { drizzle } from "drizzle-orm/vercel-postgres"; +import * as schema from "./schema"; +import type { eventsTable } from "./schema"; // Use this object to send drizzle queries to your DB export const db = drizzle(sql, { schema }); diff --git a/apps/analytics-web/db/schema.ts b/apps/analytics-web/db/schema.ts index 4d04ef12..61714a73 100644 --- a/apps/analytics-web/db/schema.ts +++ b/apps/analytics-web/db/schema.ts @@ -1,29 +1,22 @@ -import { - json, - pgTable, - serial, - text, - uniqueIndex, - timestamp, -} from 'drizzle-orm/pg-core'; +import { json, pgTable, serial, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core"; export const eventsTable = pgTable( - 'events', - { - id: serial('id').primaryKey(), - type: text('type').notNull(), // Todo: make this use pgEnum with the eventTypes array. - installationId: text('installationId').notNull(), - timestamp: timestamp('timestamp').notNull(), - countryISOCode: text('countryISOCode').notNull(), - message: text('message'), - name: text('name'), - stack: text('stack'), - cause: text('cause'), - metadata: json('metadata'), - }, - (events) => { - return { - uniqueIdx: uniqueIndex('unique_idx').on(events.id), - }; - }, + "events", + { + id: serial("id").primaryKey(), + type: text("type").notNull(), // Todo: make this use pgEnum with the eventTypes array. + installationId: text("installationId").notNull(), + timestamp: timestamp("timestamp").notNull(), + countryISOCode: text("countryISOCode").notNull(), + message: text("message"), + name: text("name"), + stack: text("stack"), + cause: text("cause"), + metadata: json("metadata"), + }, + (events) => { + return { + uniqueIdx: uniqueIndex("unique_idx").on(events.id), + }; + }, ); diff --git a/apps/analytics-web/drizzle.config.ts b/apps/analytics-web/drizzle.config.ts index 677ca2be..fb707ccc 100644 --- a/apps/analytics-web/drizzle.config.ts +++ b/apps/analytics-web/drizzle.config.ts @@ -1,16 +1,17 @@ -import dotenv from 'dotenv'; +import dotenv from "dotenv"; dotenv.config(); -import { defineConfig } from 'drizzle-kit'; +import { defineConfig } from "drizzle-kit"; export default defineConfig({ - schema: './db/schema.ts', - out: './drizzle', - driver: 'pg', - dbCredentials: { - // eslint-disable-next-line no-process-env, - connectionString: process.env.POSTGRES_URL!, - }, - verbose: true, - strict: true, + dialect: "postgresql", + schema: "./db/schema.ts", + out: "./drizzle", + driver: "pglite", + dbCredentials: { + // biome-ignore lint/style/noNonNullAssertion: env variable must be defined + url: process.env.POSTGRES_URL!, + }, + verbose: true, + strict: true, }); diff --git a/apps/analytics-web/drizzle/meta/0000_snapshot.json b/apps/analytics-web/drizzle/meta/0000_snapshot.json index 1fb47454..7a1d446a 100644 --- a/apps/analytics-web/drizzle/meta/0000_snapshot.json +++ b/apps/analytics-web/drizzle/meta/0000_snapshot.json @@ -1,93 +1,91 @@ { - "id": "dd5e2f22-a389-41cc-98a4-d6b3a7a3feee", - "prevId": "00000000-0000-0000-0000-000000000000", - "version": "5", - "dialect": "pg", - "tables": { - "events": { - "name": "events", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "serial", - "primaryKey": true, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "installationId": { - "name": "installationId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "timestamp": { - "name": "timestamp", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "countryISOCode": { - "name": "countryISOCode", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "message": { - "name": "message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stack": { - "name": "stack", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "cause": { - "name": "cause", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "metadata": { - "name": "metadata", - "type": "json", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "unique_idx": { - "name": "unique_idx", - "columns": [ - "id" - ], - "isUnique": true - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - } - }, - "enums": {}, - "schemas": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file + "id": "dd5e2f22-a389-41cc-98a4-d6b3a7a3feee", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "5", + "dialect": "pg", + "tables": { + "events": { + "name": "events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "installationId": { + "name": "installationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "countryISOCode": { + "name": "countryISOCode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stack": { + "name": "stack", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cause": { + "name": "cause", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "unique_idx": { + "name": "unique_idx", + "columns": ["id"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/apps/analytics-web/drizzle/meta/_journal.json b/apps/analytics-web/drizzle/meta/_journal.json index 45076fc3..8d902fb3 100644 --- a/apps/analytics-web/drizzle/meta/_journal.json +++ b/apps/analytics-web/drizzle/meta/_journal.json @@ -1,13 +1,13 @@ { - "version": "5", - "dialect": "pg", - "entries": [ - { - "idx": 0, - "version": "5", - "when": 1707131657461, - "tag": "0000_overconfident_zzzax", - "breakpoints": true - } - ] -} \ No newline at end of file + "version": "5", + "dialect": "pg", + "entries": [ + { + "idx": 0, + "version": "5", + "when": 1707131657461, + "tag": "0000_overconfident_zzzax", + "breakpoints": true + } + ] +} diff --git a/apps/analytics-web/middleware.ts b/apps/analytics-web/middleware.ts index 046d2472..6a2d2239 100644 --- a/apps/analytics-web/middleware.ts +++ b/apps/analytics-web/middleware.ts @@ -1,27 +1,23 @@ -import { - clerkClient, - clerkMiddleware, - createRouteMatcher, -} from '@clerk/nextjs/server'; -import { NextResponse } from 'next/server'; +import { clerkClient, clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server"; +import { NextResponse } from "next/server"; -const isPublicRoute = createRouteMatcher(['/verification(.*)']); +const isPublicRoute = createRouteMatcher(["/verification(.*)"]); 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; + // 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) { - 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 export const config = { - matcher: ['/', '/((?!api|static|.*\\..*|_next).*)'], + matcher: ["/", "/((?!api|static|.*\\..*|_next).*)"], }; diff --git a/apps/analytics-web/next.config.js b/apps/analytics-web/next.config.js index fdb56177..72bf4213 100644 --- a/apps/analytics-web/next.config.js +++ b/apps/analytics-web/next.config.js @@ -1,15 +1,13 @@ /** @type {import("next").NextConfig} */ const config = { - reactStrictMode: true, + reactStrictMode: true, - /** Enables hot reloading for local packages without a build step */ - transpilePackages: [ - "@codaco/ui", - ], + /** Enables hot reloading for local packages without a build step */ + transpilePackages: ["@codaco/ui"], - /** We already do linting and typechecking as separate tasks in CI */ - eslint: { ignoreDuringBuilds: true }, - typescript: { ignoreBuildErrors: true }, + /** We already do linting and typechecking as separate tasks in CI */ + eslint: { ignoreDuringBuilds: true }, + typescript: { ignoreBuildErrors: true }, }; export default config; diff --git a/apps/analytics-web/package.json b/apps/analytics-web/package.json index 59a18729..7544b505 100644 --- a/apps/analytics-web/package.json +++ b/apps/analytics-web/package.json @@ -1,76 +1,62 @@ { - "name": "analytics-web", - "version": "1.0.0", - "private": true, - "type": "module", - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "next lint", - "test": "vitest run", - "test:watch": "vitest watch", - "generate": "npx drizzle-kit generate:pg", - "migrate": "npx tsx scripts/migrate.ts", - "seed": "pnpm run generate && pnpm run migrate && npx tsx scripts/seed.ts" - }, - "dependencies": { - "@clerk/nextjs": "^6.3.4", - "@codaco/analytics": "workspace:*", - "@codaco/ui": "workspace:*", - "@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.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.18", - "papaparse": "^5.4.1", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "tailwind-merge": "^2.0.0", - "zod": "^3.23.8" - }, - "devDependencies": { - "@codaco/eslint-config": "workspace:*", - "@codaco/prettier-config": "workspace:*", - "@codaco/tailwind-config": "workspace:*", - "@codaco/tsconfig": "workspace:*", - "@faker-js/faker": "^8.2.0", - "@testing-library/react": "^14.1.2", - "@types/node": "^20.5.2", - "@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.15", - "typescript": "^5.6.3", - "vite-tsconfig-paths": "^5.1.2", - "vitest": "^2.1.5" - }, - "eslintConfig": { - "root": true, - "extends": [ - "@codaco/eslint-config/base", - "@codaco/eslint-config/nextjs", - "@codaco/eslint-config/react" - ] - }, - "prettier": "@codaco/prettier-config" + "name": "analytics-web", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "test": "vitest run", + "test:watch": "vitest watch", + "generate": "npx drizzle-kit generate:pg", + "migrate": "npx tsx scripts/migrate.ts", + "seed": "pnpm run generate && pnpm run migrate && npx tsx scripts/seed.ts" + }, + "dependencies": { + "@clerk/nextjs": "^6.3.4", + "@codaco/analytics": "workspace:*", + "@codaco/ui": "workspace:*", + "@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.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.18", + "papaparse": "^5.4.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "tailwind-merge": "^2.0.0", + "zod": "^3.23.8" + }, + "devDependencies": { + "@codaco/tailwind-config": "workspace:*", + "@codaco/tsconfig": "workspace:*", + "@faker-js/faker": "^8.2.0", + "@testing-library/react": "^14.1.2", + "@types/node": "^20.5.2", + "@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", + "jsdom": "^25.0.1", + "next-test-api-route-handler": "^4.0.14", + "tailwindcss": "^3.4.15", + "typescript": "^5.6.3", + "vite-tsconfig-paths": "^5.1.2", + "vitest": "^2.1.5" + } } diff --git a/apps/analytics-web/postcss.config.cjs b/apps/analytics-web/postcss.config.cjs index 12a703d9..e873f1a4 100644 --- a/apps/analytics-web/postcss.config.cjs +++ b/apps/analytics-web/postcss.config.cjs @@ -1,6 +1,6 @@ module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, }; diff --git a/apps/analytics-web/scripts/migrate.ts b/apps/analytics-web/scripts/migrate.ts index 9b2dd680..541c5b16 100644 --- a/apps/analytics-web/scripts/migrate.ts +++ b/apps/analytics-web/scripts/migrate.ts @@ -1,16 +1,15 @@ -import dotenv from 'dotenv'; +import dotenv from "dotenv"; dotenv.config(); -import { migrate } from 'drizzle-orm/vercel-postgres/migrator'; -import { db } from '~/db/db'; +import { migrate } from "drizzle-orm/vercel-postgres/migrator"; +import { db } from "~/db/db"; export async function runMigration() { - try { - await migrate(db, { migrationsFolder: './drizzle' }); - } catch (error) { - // eslint-disable-next-line no-console - console.error('Error running migration:', error); - } + try { + await migrate(db, { migrationsFolder: "./drizzle" }); + } catch (error) { + console.error("Error running migration:", error); + } } await runMigration(); diff --git a/apps/analytics-web/scripts/seed.ts b/apps/analytics-web/scripts/seed.ts index 680de4a9..cbdca5d7 100644 --- a/apps/analytics-web/scripts/seed.ts +++ b/apps/analytics-web/scripts/seed.ts @@ -1,66 +1,64 @@ -import dotenv from 'dotenv'; +import dotenv from "dotenv"; dotenv.config(); -import { faker } from '@faker-js/faker'; -import { db, type EventInsertType } from '~/db/db'; -import { eventsTable } from '~/db/schema'; -import { eventTypes } from '@codaco/analytics'; +import { eventTypes } from "@codaco/analytics"; +import { faker } from "@faker-js/faker"; +import { type EventInsertType, db } from "~/db/db"; +import { eventsTable } from "~/db/schema"; const installationIds: string[] = []; for (let i = 0; i < 20; i++) { - installationIds.push(faker.string.uuid()); + installationIds.push(faker.string.uuid()); } async function seedEvents() { - // eslint-disable-next-line no-console - console.info('Starting to seed events'); + console.info("Starting to seed events"); - try { - for (let i = 0; i < 100; i++) { - const type = faker.helpers.arrayElement([...eventTypes, 'Error']); - const installationId = faker.helpers.arrayElement(installationIds); - const timestamp = faker.date.recent(); - const metadata = { - details: faker.lorem.sentence(), - path: faker.lorem.sentence(), - }; - const countryISOCode = faker.location.countryCode(); - const message = faker.lorem.sentence(); - const name = faker.lorem.sentence(); - const stack = faker.lorem.sentence(); - const cause = faker.lorem.sentence(); + try { + for (let i = 0; i < 100; i++) { + const type = faker.helpers.arrayElement([...eventTypes, "Error"]); + const installationId = faker.helpers.arrayElement(installationIds); + const timestamp = faker.date.recent(); + const metadata = { + details: faker.lorem.sentence(), + path: faker.lorem.sentence(), + }; + const countryISOCode = faker.location.countryCode(); + const message = faker.lorem.sentence(); + const name = faker.lorem.sentence(); + const stack = faker.lorem.sentence(); + const cause = faker.lorem.sentence(); - const noneErrorEvent: EventInsertType = { - type, - installationId, - timestamp, - metadata, - countryISOCode, - }; + const noneErrorEvent: EventInsertType = { + type, + installationId, + timestamp, + metadata, + countryISOCode, + }; - const errorEvent: EventInsertType = { - type, - installationId, - timestamp, - metadata, - countryISOCode, - message, - name, - stack, - cause, - }; + const errorEvent: EventInsertType = { + type, + installationId, + timestamp, + metadata, + countryISOCode, + message, + name, + stack, + cause, + }; - await db - .insert(eventsTable) - .values(type === 'Error' ? errorEvent : noneErrorEvent) - .returning(); - } - } catch (error) { - // eslint-disable-next-line no-console - console.error('Error seeding events', error); - } + await db + .insert(eventsTable) + .values(type === "Error" ? errorEvent : noneErrorEvent) + .returning(); + } + } catch (error) { + console.error("Error seeding events", error); + } - process.exit(); + process.exit(); } await seedEvents(); diff --git a/apps/analytics-web/tailwind.config.ts b/apps/analytics-web/tailwind.config.ts index 5f3c1711..2d3cd827 100644 --- a/apps/analytics-web/tailwind.config.ts +++ b/apps/analytics-web/tailwind.config.ts @@ -1,16 +1,16 @@ -import type { Config } from 'tailwindcss'; -import sharedConfig from '@codaco/tailwind-config/fresco'; +import sharedConfig from "@codaco/tailwind-config/fresco"; +import type { Config } from "tailwindcss"; -const config: Pick = { - content: [ - ...sharedConfig.content, - './pages/**/*.{ts,tsx}', - './components/**/*.{ts,tsx}', - './app/**/*.{ts,tsx}', - './src/**/*.{ts,tsx}', - '../../packages/ui/src/**/*.{ts,tsx}', // UI package - ], - presets: [sharedConfig], +const config: Pick = { + content: [ + ...sharedConfig.content, + "./pages/**/*.{ts,tsx}", + "./components/**/*.{ts,tsx}", + "./app/**/*.{ts,tsx}", + "./src/**/*.{ts,tsx}", + "../../packages/ui/src/**/*.{ts,tsx}", // UI package + ], + presets: [sharedConfig], }; export default config; diff --git a/apps/analytics-web/tsconfig.json b/apps/analytics-web/tsconfig.json index caa490ca..a5171a64 100644 --- a/apps/analytics-web/tsconfig.json +++ b/apps/analytics-web/tsconfig.json @@ -1,17 +1,17 @@ { - "extends": "@codaco/tsconfig/base.json", - "compilerOptions": { - "baseUrl": ".", - "paths": { - "~/*": ["./*"] - }, - "plugins": [ - { - "name": "next" - } - ], - "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "extends": "@codaco/tsconfig/base.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "~/*": ["./*"] + }, + "plugins": [ + { + "name": "next" + } + ], + "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] } diff --git a/apps/analytics-web/utils/getRegionsTotals.ts b/apps/analytics-web/utils/getRegionsTotals.ts index a599a3fe..58a0ccdc 100644 --- a/apps/analytics-web/utils/getRegionsTotals.ts +++ b/apps/analytics-web/utils/getRegionsTotals.ts @@ -1,33 +1,33 @@ -import { getName } from 'i18n-iso-countries'; -import { type Event } from '~/app/_actions/actions'; +import { getName } from "i18n-iso-countries"; +import type { Event } from "~/app/_actions/actions"; export type RegionTotal = { - country: string; - total: number; + country: string; + total: number; }; export default function getRegionsTotals(events: Event[]): RegionTotal[] { - const calculatedTotals: Record = {}; + const calculatedTotals: Record = {}; - for (const event of events) { - const isocode = event.countryISOCode; + for (const event of events) { + const isocode = event.countryISOCode; - if (isocode) { - calculatedTotals[isocode] = (calculatedTotals[isocode] ?? 0) + 1; - } - } + if (isocode) { + calculatedTotals[isocode] = (calculatedTotals[isocode] ?? 0) + 1; + } + } - const regionsTotals: RegionTotal[] = []; + const regionsTotals: RegionTotal[] = []; - for (const isocode in calculatedTotals) { - regionsTotals.push({ - country: getName(isocode, 'en') ?? '', - total: calculatedTotals[isocode] ?? 0, - }); - } + for (const isocode in calculatedTotals) { + regionsTotals.push({ + country: getName(isocode, "en") ?? "", + total: calculatedTotals[isocode] ?? 0, + }); + } - // Sort in descending order - regionsTotals.sort((a, b) => b.total - a.total); + // Sort in descending order + regionsTotals.sort((a, b) => b.total - a.total); - return regionsTotals; + return regionsTotals; } diff --git a/apps/analytics-web/utils/getTotalAppsSetup.ts b/apps/analytics-web/utils/getTotalAppsSetup.ts index 86e125e5..4cf6e6ca 100644 --- a/apps/analytics-web/utils/getTotalAppsSetup.ts +++ b/apps/analytics-web/utils/getTotalAppsSetup.ts @@ -1,12 +1,12 @@ -import { type Event } from '~/app/_actions/actions'; +import type { Event } from "~/app/_actions/actions"; export const getTotalAppsSetup = (events: Event[]) => { - const nAppsSetup = events.reduce((count, event) => { - if (event.type === 'AppSetup') { - return count + 1; - } - return count; - }, 0); + const nAppsSetup = events.reduce((count, event) => { + if (event.type === "AppSetup") { + return count + 1; + } + return count; + }, 0); - return nAppsSetup; + return nAppsSetup; }; diff --git a/apps/analytics-web/utils/getTotalDataExported.ts b/apps/analytics-web/utils/getTotalDataExported.ts index 9e2e658e..51e9754d 100644 --- a/apps/analytics-web/utils/getTotalDataExported.ts +++ b/apps/analytics-web/utils/getTotalDataExported.ts @@ -1,12 +1,12 @@ -import { type Event } from '~/app/_actions/actions'; +import type { Event } from "~/app/_actions/actions"; export const getTotalDataExported = (events: Event[]) => { - const nDataExported = events.reduce((count, event) => { - if (event.type === 'DataExported') { - return count + 1; - } - return count; - }, 0); + const nDataExported = events.reduce((count, event) => { + if (event.type === "DataExported") { + return count + 1; + } + return count; + }, 0); - return nDataExported; + return nDataExported; }; diff --git a/apps/analytics-web/utils/getTotalErrors.ts b/apps/analytics-web/utils/getTotalErrors.ts index 2fb1b80b..80f1a611 100644 --- a/apps/analytics-web/utils/getTotalErrors.ts +++ b/apps/analytics-web/utils/getTotalErrors.ts @@ -1,12 +1,12 @@ -import { type Event } from '~/app/_actions/actions'; +import type { Event } from "~/app/_actions/actions"; export const getTotalErrors = (events: Event[]) => { - const nErrors = events.reduce((count, event) => { - if (event.type === 'Error') { - return count + 1; - } - return count; - }, 0); + const nErrors = events.reduce((count, event) => { + if (event.type === "Error") { + return count + 1; + } + return count; + }, 0); - return nErrors; + return nErrors; }; diff --git a/apps/analytics-web/utils/getTotalInterviewsCompleted.ts b/apps/analytics-web/utils/getTotalInterviewsCompleted.ts index 8fb1e486..f46f132c 100644 --- a/apps/analytics-web/utils/getTotalInterviewsCompleted.ts +++ b/apps/analytics-web/utils/getTotalInterviewsCompleted.ts @@ -1,12 +1,12 @@ -import { type Event } from '~/app/_actions/actions'; +import type { Event } from "~/app/_actions/actions"; export const getTotalInterviewsCompleted = (events: Event[]) => { - const nInterviewsCompleted = events.reduce((count, event) => { - if (event.type === 'InterviewCompleted') { - return count + 1; - } - return count; - }, 0); + const nInterviewsCompleted = events.reduce((count, event) => { + if (event.type === "InterviewCompleted") { + return count + 1; + } + return count; + }, 0); - return nInterviewsCompleted; + return nInterviewsCompleted; }; diff --git a/apps/analytics-web/utils/getTotalInterviewsStarted.ts b/apps/analytics-web/utils/getTotalInterviewsStarted.ts index e787d9b4..1583c0a8 100644 --- a/apps/analytics-web/utils/getTotalInterviewsStarted.ts +++ b/apps/analytics-web/utils/getTotalInterviewsStarted.ts @@ -1,12 +1,12 @@ -import { type Event } from '~/app/_actions/actions'; +import type { Event } from "~/app/_actions/actions"; export const getTotalInterviewsStarted = (events: Event[]) => { - const nInterviewsStarted = events.reduce((count, event) => { - if (event.type === 'InterviewStarted') { - return count + 1; - } - return count; - }, 0); + const nInterviewsStarted = events.reduce((count, event) => { + if (event.type === "InterviewStarted") { + return count + 1; + } + return count; + }, 0); - return nInterviewsStarted; + return nInterviewsStarted; }; diff --git a/apps/analytics-web/utils/getTotalProtocolsInstalled.ts b/apps/analytics-web/utils/getTotalProtocolsInstalled.ts index 663494ae..3cb1a114 100644 --- a/apps/analytics-web/utils/getTotalProtocolsInstalled.ts +++ b/apps/analytics-web/utils/getTotalProtocolsInstalled.ts @@ -1,12 +1,12 @@ -import { type Event } from '~/app/_actions/actions'; +import type { Event } from "~/app/_actions/actions"; export const getTotalProtocolsInstalled = (events: Event[]) => { - const nProtocolsInstalled = events.reduce((count, event) => { - if (event.type === 'ProtocolInstalled') { - return count + 1; - } - return count; - }, 0); + const nProtocolsInstalled = events.reduce((count, event) => { + if (event.type === "ProtocolInstalled") { + return count + 1; + } + return count; + }, 0); - return nProtocolsInstalled; + return nProtocolsInstalled; }; diff --git a/apps/analytics-web/utils/shadcn.ts b/apps/analytics-web/utils/shadcn.ts index 9ad0df42..ac680b30 100644 --- a/apps/analytics-web/utils/shadcn.ts +++ b/apps/analytics-web/utils/shadcn.ts @@ -1,6 +1,6 @@ -import { type ClassValue, clsx } from 'clsx'; -import { twMerge } from 'tailwind-merge'; +import { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); + return twMerge(clsx(inputs)); } diff --git a/apps/analytics-web/vitest.config.ts b/apps/analytics-web/vitest.config.ts index 1d482eaf..73d944e2 100644 --- a/apps/analytics-web/vitest.config.ts +++ b/apps/analytics-web/vitest.config.ts @@ -1,9 +1,9 @@ -import { defineConfig } from 'vitest/config'; -import tsconfigPaths from 'vite-tsconfig-paths'; +import tsconfigPaths from "vite-tsconfig-paths"; +import { defineConfig } from "vitest/config"; export default defineConfig({ - test: { - environment: 'node', - }, - plugins: [tsconfigPaths()], + test: { + environment: "node", + }, + plugins: [tsconfigPaths()], }); diff --git a/apps/documentation/.gitignore b/apps/documentation/.gitignore deleted file mode 100644 index 07ae5a8a..00000000 --- a/apps/documentation/.gitignore +++ /dev/null @@ -1,42 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.jsfind . -name .DS_Store -delete - -# testing -/coverage - -# next.js -/.next/ - -# production -/build -/out - -# misc -.DS_Store -._.DS_Store -**/.DS_Store -**/._.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files -.env -.env*.local - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts - -# dont commit the sidebar -/public/sidebar.json \ No newline at end of file diff --git a/apps/documentation/app/[locale]/[project]/[...docPath]/page.tsx b/apps/documentation/app/[locale]/[project]/[...docPath]/page.tsx index 65c4db1a..89ce4066 100644 --- a/apps/documentation/app/[locale]/[project]/[...docPath]/page.tsx +++ b/apps/documentation/app/[locale]/[project]/[...docPath]/page.tsx @@ -1,60 +1,60 @@ -import { notFound } from 'next/navigation'; -import { setRequestLocale } from 'next-intl/server'; -import type { Locale } from '~/app/types'; -import Article from '~/components/article'; -import { getDocsForRouteSegment, getDocumentForPath } from '~/lib/docs'; +import { setRequestLocale } from "next-intl/server"; +import { notFound } from "next/navigation"; +import type { Locale } from "~/app/types"; +import Article from "~/components/article"; +import { getDocsForRouteSegment, getDocumentForPath } from "~/lib/docs"; type PageParams = { - locale: Locale; - project: string; - docPath: string[]; + locale: Locale; + project: string; + docPath: string[]; }; export async function generateMetadata({ params }: { params: PageParams }) { - const { locale, project, docPath } = params; - const document = await getDocumentForPath({ - locale, - project, - pathSegment: docPath, - }); - - return { title: document?.frontmatter.title }; + const { locale, project, docPath } = params; + const document = await getDocumentForPath({ + locale, + project, + pathSegment: docPath, + }); + + return { title: document?.frontmatter.title }; } export function generateStaticParams({ - params, + params, }: { - params: Omit; + params: Omit; }) { - const { locale, project } = params; - const docPathSegmentsForRoute = getDocsForRouteSegment({ - locale, - project, - }); + const { locale, project } = params; + const docPathSegmentsForRoute = getDocsForRouteSegment({ + locale, + project, + }); - return docPathSegmentsForRoute; + return docPathSegmentsForRoute; } export default async function Page({ params }: { params: PageParams }) { - const { locale, project, docPath } = params; - // setting setRequestLocale to support next-intl for static rendering - setRequestLocale(locale); - - const document = await getDocumentForPath({ - locale, - project, - pathSegment: docPath, - }); - - if (document === null) notFound(); - - return ( -
0} - wip={document.frontmatter.wip} - /> - ); + const { locale, project, docPath } = params; + // setting setRequestLocale to support next-intl for static rendering + setRequestLocale(locale); + + const document = await getDocumentForPath({ + locale, + project, + pathSegment: docPath, + }); + + if (document === null) notFound(); + + return ( +
0} + wip={document.frontmatter.wip} + /> + ); } diff --git a/apps/documentation/app/[locale]/[project]/_components/InnerLanguageSwitcher.tsx b/apps/documentation/app/[locale]/[project]/_components/InnerLanguageSwitcher.tsx index 67d86b5c..7ab663a0 100644 --- a/apps/documentation/app/[locale]/[project]/_components/InnerLanguageSwitcher.tsx +++ b/apps/documentation/app/[locale]/[project]/_components/InnerLanguageSwitcher.tsx @@ -1,44 +1,35 @@ -import Link from 'next/link'; -import { getTranslations } from 'next-intl/server'; -import { getAvailableLocalesForPath } from '~/lib/docs'; +import { getTranslations } from "next-intl/server"; +import Link from "next/link"; +import { getAvailableLocalesForPath } from "~/lib/docs"; type InnerLanguageSwitcherProps = { - pathSegment: string[]; - currentLocale: string; - project: string; + pathSegment: string[]; + currentLocale: string; + project: string; }; -const InnerLanguageSwitcher = async ({ - pathSegment, - currentLocale, - project, -}: InnerLanguageSwitcherProps) => { - const t = await getTranslations('DocPage'); - const availableLocales = getAvailableLocalesForPath(project, pathSegment); - const filePath = `/${project}/` + pathSegment.join('/'); //document file path to navigate to +const InnerLanguageSwitcher = async ({ pathSegment, currentLocale, project }: InnerLanguageSwitcherProps) => { + const t = await getTranslations("DocPage"); + const availableLocales = getAvailableLocalesForPath(project, pathSegment); + const filePath = `/${project}/${pathSegment.join("/")}`; //document file path to navigate to - // removes the current locale from availableLocales - const supportedLanguages = availableLocales.filter( - (locale) => locale !== currentLocale, - ); + // removes the current locale from availableLocales + const supportedLanguages = availableLocales.filter((locale) => locale !== currentLocale); - if (!supportedLanguages.length) return null; + if (!supportedLanguages.length) return null; - return ( -
- {t('docAvailableTxt')} - {supportedLanguages.map((lang) => ( -
- - {lang} - -
- ))} -
- ); + return ( +
+ {t("docAvailableTxt")} + {supportedLanguages.map((lang) => ( +
+ + {lang} + +
+ ))} +
+ ); }; export default InnerLanguageSwitcher; diff --git a/apps/documentation/app/[locale]/[project]/layout.tsx b/apps/documentation/app/[locale]/[project]/layout.tsx index 37c24a62..3890012d 100644 --- a/apps/documentation/app/[locale]/[project]/layout.tsx +++ b/apps/documentation/app/[locale]/[project]/layout.tsx @@ -1,25 +1,25 @@ -import { type ReactNode } from 'react'; -import { projects } from '~/app/types'; +import type { ReactNode } from "react"; +import { projects } from "~/app/types"; export function generateStaticParams({ - params, + params, }: { - params: { locale: string }; + params: { locale: string }; }) { - const { locale } = params; + const { locale } = params; - return projects.map((project) => { - return { - locale, - project, - }; - }); + return projects.map((project) => { + return { + locale, + project, + }; + }); } type Props = { - children: ReactNode; + children: ReactNode; }; export default function Layout({ children }: Props) { - return children; + return children; } diff --git a/apps/documentation/app/[locale]/[project]/page.tsx b/apps/documentation/app/[locale]/[project]/page.tsx index 1e2a41c3..ea4c6d18 100644 --- a/apps/documentation/app/[locale]/[project]/page.tsx +++ b/apps/documentation/app/[locale]/[project]/page.tsx @@ -1,31 +1,31 @@ -import { notFound } from 'next/navigation'; -import { setRequestLocale } from 'next-intl/server'; -import { getDocumentForPath } from '~/lib/docs'; -import Article from '~/components/article'; -import type { Locale } from '~/app/types'; +import { setRequestLocale } from "next-intl/server"; +import { notFound } from "next/navigation"; +import type { Locale } from "~/app/types"; +import Article from "~/components/article"; +import { getDocumentForPath } from "~/lib/docs"; type PageProps = { params: { locale: Locale; project: string } }; export default async function Page({ params }: PageProps) { - const { locale, project } = params; + const { locale, project } = params; - // setting setRequestLocale to support next-intl for static rendering - setRequestLocale(params.locale); + // setting setRequestLocale to support next-intl for static rendering + setRequestLocale(params.locale); - const document = await getDocumentForPath({ - locale, - project, - }); + const document = await getDocumentForPath({ + locale, + project, + }); - if (document === null) notFound(); + if (document === null) notFound(); - return ( -
0} - wip={document.frontmatter.wip} - /> - ); + return ( +
0} + wip={document.frontmatter.wip} + /> + ); } diff --git a/apps/documentation/app/[locale]/layout.tsx b/apps/documentation/app/[locale]/layout.tsx index 93dedaaa..8bbb1d2c 100644 --- a/apps/documentation/app/[locale]/layout.tsx +++ b/apps/documentation/app/[locale]/layout.tsx @@ -1,97 +1,80 @@ -import type { Metadata } from 'next'; -import { Quicksand } from 'next/font/google'; -import { notFound } from 'next/navigation'; -import { NextIntlClientProvider } from 'next-intl'; -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'; +import { GoogleAnalytics } from "@next/third-parties/google"; +import { Analytics } from "@vercel/analytics/react"; +import type { Metadata } from "next"; +import { NextIntlClientProvider } from "next-intl"; +import { getNow, getTimeZone, setRequestLocale } from "next-intl/server"; +import { Quicksand } from "next/font/google"; +import { notFound } from "next/navigation"; +import type { Locale, Messages } from "~/app/types"; +import { locales } from "~/app/types"; +import { LayoutComponent } from "~/components/Layout"; +import { ThemeProvider } from "~/components/Providers/theme-provider"; +import AIAssistant from "~/components/ai-assistant"; +import { env } from "~/env"; const quicksand = Quicksand({ - weight: ['300', '400', '500', '600', '700'], - subsets: ['latin', 'latin-ext'], - display: 'swap', + weight: ["300", "400", "500", "600", "700"], + subsets: ["latin", "latin-ext"], + display: "swap", }); export function generateMetadata({ - params: { locale }, + params: { locale }, }: { - params: { locale: Locale }; + params: { locale: Locale }; }) { - const metadata: Metadata = { - other: { - 'docsearch:language': locale, - 'docsearch:version': '1.0.1', - }, - }; + const metadata: Metadata = { + other: { + "docsearch:language": locale, + "docsearch:version": "1.0.1", + }, + }; - return metadata; + return metadata; } export function generateStaticParams() { - return locales.map((locale) => ({ locale })); + return locales.map((locale) => ({ locale })); } type MainLayoutProps = { - children: React.ReactNode; - params: { locale: Locale }; + children: React.ReactNode; + params: { locale: Locale }; }; -export default async function MainLayout({ - children, - params: { locale }, -}: MainLayoutProps) { - // Validate that the incoming `locale` parameter is valid - const isValidLocale = locales.some((cur) => cur === locale); - if (!isValidLocale) notFound(); +export default async function MainLayout({ children, params: { locale } }: MainLayoutProps) { + // Validate that the incoming `locale` parameter is valid + const isValidLocale = locales.some((cur) => cur === locale); + if (!isValidLocale) notFound(); - // setting setRequestLocale to support next-intl for static rendering - setRequestLocale(locale); + // setting setRequestLocale to support next-intl for static rendering + setRequestLocale(locale); - const now = await getNow({ locale }); - const timeZone = await getTimeZone({ locale }); + const now = await getNow({ locale }); + const timeZone = await getTimeZone({ locale }); - let messages; + let messages: { default: Messages }; - try { - messages = (await import(`../../messages/${locale}.json`)) as { - default: Messages; - }; - } catch (e) { - notFound(); - } + try { + messages = (await import(`../../messages/${locale}.json`)) as { + default: Messages; + }; + } catch (e) { + notFound(); + } - return ( - - - - - {children} - - - - - - - - ); + return ( + + + + + {children} + + + + + + + + ); } diff --git a/apps/documentation/app/[locale]/page.tsx b/apps/documentation/app/[locale]/page.tsx index d716e124..b8be431a 100644 --- a/apps/documentation/app/[locale]/page.tsx +++ b/apps/documentation/app/[locale]/page.tsx @@ -1,11 +1,11 @@ -import { setRequestLocale } from 'next-intl/server'; -import type { Locale } from '~/app/types'; -import { Hero } from '~/components/Hero'; +import { setRequestLocale } from "next-intl/server"; +import type { Locale } from "~/app/types"; +import { Hero } from "~/components/Hero"; const Page = ({ params: { locale } }: { params: { locale: Locale } }) => { - // setting setRequestLocale to support next-intl for static rendering - setRequestLocale(locale); + // setting setRequestLocale to support next-intl for static rendering + setRequestLocale(locale); - return ; + return ; }; export default Page; diff --git a/apps/documentation/app/layout.tsx b/apps/documentation/app/layout.tsx index b79c9863..497c9eed 100644 --- a/apps/documentation/app/layout.tsx +++ b/apps/documentation/app/layout.tsx @@ -1,14 +1,14 @@ -import { type ReactNode } from 'react'; -import '@codaco/tailwind-config/globals.css'; -import '~/styles/globals.css'; -import '~/lib/highlight.js/styles/tokyo-night-dark.css'; +import type { ReactNode } from "react"; +import "@codaco/tailwind-config/globals.css"; +import "~/styles/globals.css"; +import "~/styles/tokyo-night-dark.css"; type Props = { - children: ReactNode; + children: ReactNode; }; // Since we have a `not-found.tsx` page on the root, a layout file // is required, even if it's just passing children through. export default function RootLayout({ children }: Props) { - return children; + return children; } diff --git a/apps/documentation/app/manifest.ts b/apps/documentation/app/manifest.ts index 6fce13a1..77a6d7f7 100644 --- a/apps/documentation/app/manifest.ts +++ b/apps/documentation/app/manifest.ts @@ -1,26 +1,26 @@ -import { type MetadataRoute } from 'next'; +import type { MetadataRoute } from "next"; export default function manifest(): MetadataRoute.Manifest { - return { - name: 'Network Canvas Documentation', - short_name: 'Documentation', - description: - 'Documentation and information about the Network Canvas project. Network Canvas is free suite of tools that facilitate research that uses complex personal network data.', - start_url: '/', - display: 'standalone', - background_color: '#1E283A', - theme_color: '#1E283A', - icons: [ - { - src: '/favicons/icon192.png', - sizes: '192x192', - type: 'image/png', - }, - { - src: '/favicons/icon512.png', - sizes: '512x512', - type: 'image/png', - }, - ], - }; + return { + name: "Network Canvas Documentation", + short_name: "Documentation", + description: + "Documentation and information about the Network Canvas project. Network Canvas is free suite of tools that facilitate research that uses complex personal network data.", + start_url: "/", + display: "standalone", + background_color: "#1E283A", + theme_color: "#1E283A", + icons: [ + { + src: "/favicons/icon192.png", + sizes: "192x192", + type: "image/png", + }, + { + src: "/favicons/icon512.png", + sizes: "512x512", + type: "image/png", + }, + ], + }; } diff --git a/apps/documentation/app/not-found.tsx b/apps/documentation/app/not-found.tsx index 793ac62b..67fbc611 100644 --- a/apps/documentation/app/not-found.tsx +++ b/apps/documentation/app/not-found.tsx @@ -1,67 +1,47 @@ -import { - Divider, - Heading, - ListItem, - Paragraph, - UnorderedList, -} from '@codaco/ui'; -import { Quicksand } from 'next/font/google'; +import { Divider, Heading, ListItem, Paragraph, UnorderedList } from "@codaco/ui"; +import { Quicksand } from "next/font/google"; -import Link from '~/components/Link'; -import { ThemeProvider } from '~/components/Providers/theme-provider'; +import Link from "~/components/Link"; +import { ThemeProvider } from "~/components/Providers/theme-provider"; const quicksand = Quicksand({ - weight: ['300', '400', '500', '600', '700'], - subsets: ['latin', 'latin-ext'], - display: 'swap', + weight: ["300", "400", "500", "600", "700"], + subsets: ["latin", "latin-ext"], + display: "swap", }); export default function NotFound() { - return ( - - - -
- 404 - Not found - - - The requested page could not be found. - - - This is most likely to have happened because the page has been - moved or deleted. We apologize for any inconvenience this has - caused. - - Please try the following: - - - If you typed the page address in the address bar, make sure that - it is spelled correctly. - - - Use the 'return home' link below to navigate to the - home page, and then navigate to the page you are looking for - using the menus. - - - Contact the project team if you believe you are seeing this page - in error, including details of the page you were trying to - reach. Please email us at{' '} - - info@networkcanvas.com - - . - - - Return Home -
- -
- - ); + return ( + + + +
+ 404 - Not found + + The requested page could not be found. + + This is most likely to have happened because the page has been moved or deleted. We apologize for any + inconvenience this has caused. + + Please try the following: + + + If you typed the page address in the address bar, make sure that it is spelled correctly. + + + Use the 'return home' link below to navigate to the home page, and then navigate to the page + you are looking for using the menus. + + + Contact the project team if you believe you are seeing this page in error, including details of the page + you were trying to reach. Please email us at{" "} + info@networkcanvas.com. + + + Return Home +
+ +
+ + ); } diff --git a/apps/documentation/app/page.tsx b/apps/documentation/app/page.tsx index 2977201d..351d9ecb 100644 --- a/apps/documentation/app/page.tsx +++ b/apps/documentation/app/page.tsx @@ -1,8 +1,8 @@ -import { redirect } from 'next/navigation'; +import { permanentRedirect } from "next/navigation"; export default function RootPage() { - // Redirect to en for now. This is a limitation of static site generation - // that we need to work around by creating a client component to read the - // navigator.language and redirect to the appropriate locale. - redirect('/en'); + // Redirect to en for now. This is a limitation of static site generation + // that we need to work around by creating a client component to read the + // navigator.language and redirect to the appropriate locale. + permanentRedirect("/en"); } diff --git a/apps/documentation/app/types.ts b/apps/documentation/app/types.ts index 1b84213e..8baf05c7 100644 --- a/apps/documentation/app/types.ts +++ b/apps/documentation/app/types.ts @@ -1,33 +1,32 @@ -import { z } from 'zod'; +import { z } from "zod"; -export const projects = ['desktop', 'fresco'] as const; +export const projects = ["desktop", "fresco"] as const; -export type ProjectsEnum = (typeof projects)[number]; +export type Project = (typeof projects)[number]; -export const locales = ['en'] as const; +export const locales = ["en"] as const; -const Locale = z.enum(locales); +export const zlocales = z.enum(locales); + +export type Locales = typeof locales; export type Locale = (typeof locales)[number]; export const itemTypes = [ - 'project', // Top level projects - 'folder', // Anything that has children - 'page', // Single page + "project", // Top level projects + "folder", // Anything that has children + "page", // Single page ] as const; -const ItemTypesEnum = z.enum(itemTypes); - export const SidebarItemBase = z.object({ - type: ItemTypesEnum, - sourceFile: z.string().optional(), - label: z.string(), + sourceFile: z.string().optional(), + label: z.string(), }); export const SidebarPageSchema = SidebarItemBase.extend({ - type: z.literal('page'), - sourceFile: z.string(), - navOrder: z.number().nullable(), + type: z.literal("page"), + sourceFile: z.string(), + navOrder: z.number().nullable(), }); export type SidebarPage = z.infer; @@ -38,56 +37,48 @@ export type SidebarPage = z.infer; // Because of that, we have to do some other shenanigans. export const baseSidebarFolder = SidebarItemBase.extend({ - type: z.literal('folder'), - navOrder: z.number().nullable(), - expanded: z.boolean().optional(), + type: z.literal("folder"), + navOrder: z.number().nullable(), + expanded: z.boolean().optional(), }); export type TSidebarFolder = z.infer & { - children: Record; + children: Record; }; -export const SidebarFolderSchema: z.ZodType = - baseSidebarFolder.extend({ - children: z.lazy(() => - z.record(z.union([SidebarFolderSchema, SidebarPageSchema])), - ), - }); +export const SidebarFolderSchema: z.ZodType = baseSidebarFolder.extend({ + children: z.lazy(() => z.record(z.union([SidebarFolderSchema, SidebarPageSchema]))), +}); export type SidebarFolder = z.infer; export const SidebarProjectSchema = SidebarItemBase.extend({ - type: z.literal('project'), - children: z.record(z.union([SidebarFolderSchema, SidebarPageSchema])), + type: z.literal("project"), + children: z.record(z.union([SidebarFolderSchema, SidebarPageSchema])), }); export type SidebarProject = z.infer; -export const SidebarLocaleDefinitionSchema = z.record( - z.string(), - SidebarProjectSchema, -); +export const SidebarLocaleDefinitionSchema = z.record(z.enum(projects), SidebarProjectSchema); -export type SidebarLocaleDefinition = z.infer< - typeof SidebarLocaleDefinitionSchema ->; +export type SidebarLocaleDefinition = Record; -export const SideBarSchema = z.record(Locale, SidebarLocaleDefinitionSchema); +export const SideBarSchema = z.record(zlocales, SidebarLocaleDefinitionSchema); -export type TSideBar = z.infer; +// Can't infer this from above because of this: https://github.com/colinhacks/zod/issues/2623 +export type TSideBar = Record; -const metadatatypes = ['folder', 'project'] as const; +const metadatatypes = ["folder", "project"] as const; export const MetadataFileSchema = z.object({ - type: z.enum(metadatatypes), - sourceFile: z.string().optional(), - localeLabels: z.record(z.string(), z.string()).optional(), - localeIndexFiles: z.record(z.string(), z.string()).optional(), - isExpanded: z.boolean().optional(), - navOrder: z.number().optional(), + type: z.enum(metadatatypes), + sourceFile: z.string().optional(), + localeLabels: z.record(z.string(), z.string()).optional(), + localeIndexFiles: z.record(z.string(), z.string()).optional(), + isExpanded: z.boolean().optional(), + navOrder: z.number().optional(), }); export type MetadataFile = z.infer; -// eslint-disable-next-line @typescript-eslint/consistent-type-imports -export type Messages = typeof import('../messages/en.json'); +export type Messages = typeof import("../messages/en.json"); diff --git a/apps/documentation/components.json b/apps/documentation/components.json index 3dc921a0..5aeba109 100644 --- a/apps/documentation/components.json +++ b/apps/documentation/components.json @@ -1,16 +1,16 @@ { - "$schema": "https://ui.shadcn.com/schema.json", - "style": "default", - "rsc": true, - "tsx": true, - "tailwind": { - "config": "tailwind.config.ts", - "css": "app/globals.css", - "baseColor": "slate", - "cssVariables": true - }, - "aliases": { - "components": "~/components", - "utils": "~/lib/utils" - } + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "slate", + "cssVariables": true + }, + "aliases": { + "components": "~/components", + "utils": "~/lib/utils" + } } diff --git a/apps/documentation/components/DocSearchComponent.tsx b/apps/documentation/components/DocSearchComponent.tsx index 94ae36f9..87de1f36 100644 --- a/apps/documentation/components/DocSearchComponent.tsx +++ b/apps/documentation/components/DocSearchComponent.tsx @@ -1,149 +1,119 @@ -'use client'; +"use client"; -import { DocSearch } from '@docsearch/react'; -import { useLocale, useTranslations } from 'next-intl'; -import '@docsearch/css'; -import { env } from '~/env'; -import { inputVariants } from '@codaco/ui'; -import { Search } from 'lucide-react'; -import { cn } from '~/lib/utils'; +import { DocSearch } from "@docsearch/react"; +import { useLocale, useTranslations } from "next-intl"; +import "@docsearch/css"; +import { inputVariants } from "@codaco/ui"; +import { Search } from "lucide-react"; +import { env } from "~/env"; +import { cn } from "~/lib/utils"; const DocSearchComponent = ({ - className, - large, + className, + large, }: { - className?: string; - large?: boolean; + className?: string; + large?: boolean; }) => { - const locale = useLocale(); - const t = useTranslations('DocSearch'); + const locale = useLocale(); + const t = useTranslations("DocSearch"); - // This is honestly some of the biggest bullshit I've ever had to deal with. - // Algolia - fix your shit. - const madHax = () => { - const element = document.getElementsByClassName( - 'DocSearch-Button', - )[0] as HTMLButtonElement; + // This is honestly some of the biggest bullshit I've ever had to deal with. + // Algolia - fix your shit. + const madHax = () => { + const element = document.getElementsByClassName("DocSearch-Button")[0] as HTMLButtonElement; - if (element) { - element.click(); - } - }; + if (element) { + element.click(); + } + }; - return ( - <> - -
- -
- - ); + + +
+ +
+ + ); }; export default DocSearchComponent; diff --git a/apps/documentation/components/FancyHeading.tsx b/apps/documentation/components/FancyHeading.tsx index b75b31f5..363511ca 100644 --- a/apps/documentation/components/FancyHeading.tsx +++ b/apps/documentation/components/FancyHeading.tsx @@ -1,67 +1,69 @@ -'use client'; +"use client"; -import { motion } from 'framer-motion'; -import { Heading, type HeadingProps } from '@codaco/ui'; -import { Children } from 'react'; +import { Heading, type HeadingProps } from "@codaco/ui"; +import { motion } from "framer-motion"; +import { Children } from "react"; // FancyHeading is a component that animates the words in a heading. const FancyHeading = (props: HeadingProps) => { - const words = Children.toArray(props.children); + const words = Children.toArray(props.children); - const variants = { - hidden: { y: '100%' }, - visible: (custom: number) => ({ - y: 0, - transition: { - type: 'spring', - stiffness: 200, - damping: 30, - mass: 1, - delay: 0.1 * custom, - }, - }), - }; + const variants = { + hidden: { y: "100%" }, + visible: (custom: number) => ({ + y: 0, + transition: { + type: "spring", + stiffness: 200, + damping: 30, + mass: 1, + delay: 0.1 * custom, + }, + }), + }; - const renderWord = (word: string, outerIndex: number) => { - const segments = word.split(' '); - return segments.map((segment, innerIndex) => ( - - - {segment}  - - - )); - }; + const renderWord = (word: string, outerIndex: number) => { + const segments = word.split(" "); + return segments.map((segment, innerIndex) => ( + + + {segment}  + + + )); + }; - return ( - - {words.map((word, index) => - typeof word === 'string' ? ( - renderWord(word, index) - ) : ( - - {word} - - ), - )} - - ); + return ( + + {words.map((word, index) => + typeof word === "string" ? ( + renderWord(word, index) + ) : ( + + {word} + + ), + )} + + ); }; export default FancyHeading; diff --git a/apps/documentation/components/FancyParagraph.tsx b/apps/documentation/components/FancyParagraph.tsx index c6416349..edd81378 100644 --- a/apps/documentation/components/FancyParagraph.tsx +++ b/apps/documentation/components/FancyParagraph.tsx @@ -1,67 +1,69 @@ -import { motion } from 'framer-motion'; -import { Paragraph, type ParagraphProps } from '@codaco/ui'; -import { Children } from 'react'; +import { Paragraph, type ParagraphProps } from "@codaco/ui"; +import { motion } from "framer-motion"; +import { Children } from "react"; // FancyParagraph animates individual words in a paragraph. const FancyParagraph = (props: ParagraphProps) => { - const { children, ...rest } = props; + const { children, ...rest } = props; - const words = Children.toArray(children); + const words = Children.toArray(children); - const variants = { - hidden: { y: '100%' }, - visible: (custom: number) => ({ - y: 0, - transition: { - type: 'spring', - stiffness: 200, - damping: 30, - mass: 1, - delay: 0.1 * custom, - }, - }), - }; + const variants = { + hidden: { y: "100%" }, + visible: (custom: number) => ({ + y: 0, + transition: { + type: "spring", + stiffness: 200, + damping: 30, + mass: 1, + delay: 0.1 * custom, + }, + }), + }; - const renderWord = (word: string, outerIndex: number) => { - const segments = word.split(' '); - return segments.map((segment, innerIndex) => ( - - - {segment}  - - - )); - }; + const renderWord = (word: string, outerIndex: number) => { + const segments = word.split(" "); + return segments.map((segment, innerIndex) => ( + + + {segment}  + + + )); + }; - return ( - - {words.map((word, index) => - typeof word === 'string' ? ( - renderWord(word, index) - ) : ( - - {word} - - ), - )} - - ); + return ( + + {words.map((word, index) => + typeof word === "string" ? ( + renderWord(word, index) + ) : ( + + {word} + + ), + )} + + ); }; export default FancyParagraph; diff --git a/apps/documentation/components/Hero.tsx b/apps/documentation/components/Hero.tsx index 99d416ba..27a4b6a7 100644 --- a/apps/documentation/components/Hero.tsx +++ b/apps/documentation/components/Hero.tsx @@ -1,103 +1,96 @@ -'use client'; +"use client"; -import { useTranslations } from 'next-intl'; -import DocSearchComponent from './DocSearchComponent'; -import FancyHeading from './FancyHeading'; -import FancyParagraph from './FancyParagraph'; -import { motion } from 'framer-motion'; -import { Paragraph } from '@codaco/ui'; -import { cn } from '~/lib/utils'; -import { useTheme } from 'next-themes'; -import Link from 'next/link'; +import { Paragraph } from "@codaco/ui"; +import { motion } from "framer-motion"; +import { useTranslations } from "next-intl"; +import { useTheme } from "next-themes"; +import Link from "next/link"; +import { cn } from "~/lib/utils"; +import DocSearchComponent from "./DocSearchComponent"; +import FancyHeading from "./FancyHeading"; +import FancyParagraph from "./FancyParagraph"; function ProjectCard({ - href, - title, - description, - icon, + href, + title, + description, + icon, }: { - href: string; - title: string; - description: string; - icon: string; + href: string; + title: string; + description: string; + icon: string; }) { - return ( - -
-
- {title} - - {title} - -
- {description} -
- - ); + return ( + +
+
+ {title} + + {title} + +
+ {description} +
+ + ); } export function Hero() { - const t = useTranslations(); - const { resolvedTheme } = useTheme(); + const t = useTranslations(); + const { resolvedTheme } = useTheme(); - return ( - <> - -
-
- - {t('Hero.title')} - - {t('Hero.tagline')} - -
- {resolvedTheme !== 'dark' && ( -
- - Robot - -
- )} -
-
- - -
-
- - ); + return ( + <> + +
+
+ + {t("Hero.title")} + + {t("Hero.tagline")} + +
+ {resolvedTheme !== "dark" && ( +
+ + Robot + +
+ )} +
+
+ + +
+
+ + ); } diff --git a/apps/documentation/components/Layout.tsx b/apps/documentation/components/Layout.tsx index 6e8a58b4..aacf09e6 100644 --- a/apps/documentation/components/Layout.tsx +++ b/apps/documentation/components/Layout.tsx @@ -1,53 +1,53 @@ -'use client'; +"use client"; -import { useLocale } from 'next-intl'; -import { usePathname } from 'next/navigation'; -import { Sidebar } from '~/components/Sidebar'; -import { cn } from '~/lib/utils'; -import SharedNav from './SharedNav/SharedNav'; -import { motion } from 'framer-motion'; -import { BackgroundBlobs } from '@codaco/art'; +import { BackgroundBlobs } from "@codaco/art"; +import { motion } from "framer-motion"; +import { useLocale } from "next-intl"; +import { usePathname } from "next/navigation"; +import { Sidebar } from "~/components/Sidebar"; +import { cn } from "~/lib/utils"; +import SharedNav from "./SharedNav/SharedNav"; export function LayoutComponent({ children }: { children: React.ReactNode }) { - const pathname = usePathname(); - const locale = useLocale(); + const pathname = usePathname(); + const locale = useLocale(); - // Check if we are on the home page by comparing the pathname to our supported locals - const isHomePage = pathname === `/${locale}`; + // Check if we are on the home page by comparing the pathname to our supported locals + const isHomePage = pathname === `/${locale}`; - return ( - <> - - - - -
- {!isHomePage && ( - - )} + return ( + <> + + + + +
+ {!isHomePage && ( + + )} - {children} -
-
-
- © {new Date().getFullYear()} Complex Data Collective -
-
- - ); + {children} +
+
+
+ © {new Date().getFullYear()} Complex Data Collective +
+
+ + ); } diff --git a/apps/documentation/components/Link.tsx b/apps/documentation/components/Link.tsx index 33892337..7c0442a0 100644 --- a/apps/documentation/components/Link.tsx +++ b/apps/documentation/components/Link.tsx @@ -1,27 +1,24 @@ -import NextLink, { type LinkProps } from 'next/link'; -import { type ReactNode, forwardRef } from 'react'; -import { cn } from '~/lib/utils'; +import NextLink, { type LinkProps } from "next/link"; +import { type ReactNode, forwardRef } from "react"; +import { cn } from "~/lib/utils"; -const Link = forwardRef< - HTMLAnchorElement, - LinkProps & { children: ReactNode; className?: string } ->((props, ref) => { - return ( - - - {props.children} - - - ); +const Link = forwardRef((props, ref) => { + return ( + + + {props.children} + + + ); }); -Link.displayName = 'Link'; +Link.displayName = "Link"; export default Link; diff --git a/apps/documentation/components/MobileNavBar.tsx b/apps/documentation/components/MobileNavBar.tsx index 9122a809..1ffe557d 100644 --- a/apps/documentation/components/MobileNavBar.tsx +++ b/apps/documentation/components/MobileNavBar.tsx @@ -1,49 +1,41 @@ -import { Button } from '@codaco/ui'; -import { X as CloseMenu, Menu as HamburgerMenu } from 'lucide-react'; -import DocSearchComponent from './DocSearchComponent'; -import MobileSidebarDialog from './MobileSidebarDialog'; -import { useState } from 'react'; -import { usePathname } from 'next/navigation'; -import { useLocale } from 'next-intl'; -import { cn } from '~/lib/utils'; +import { Button } from "@codaco/ui"; +import { X as CloseMenu, Menu as HamburgerMenu } from "lucide-react"; +import { useLocale } from "next-intl"; +import { usePathname } from "next/navigation"; +import { useState } from "react"; +import { cn } from "~/lib/utils"; +import DocSearchComponent from "./DocSearchComponent"; +import MobileSidebarDialog from "./MobileSidebarDialog"; const MobileNavBar = () => { - const [open, setOpen] = useState(false); - const pathname = usePathname(); - const locale = useLocale(); + const [open, setOpen] = useState(false); + const pathname = usePathname(); + const locale = useLocale(); - // Check if we are on the home page by comparing the pathname to our supported locals - const isHomePage = pathname === `/${locale}`; - return ( - <> -
- - - {open ? ( - - ) : ( - - )} -
- - ); + // Check if we are on the home page by comparing the pathname to our supported locals + const isHomePage = pathname === `/${locale}`; + return ( + <> +
+ + + {open ? ( + + ) : ( + + )} +
+ + ); }; export default MobileNavBar; diff --git a/apps/documentation/components/MobileSidebarDialog.tsx b/apps/documentation/components/MobileSidebarDialog.tsx index 56d7b0d4..10a03288 100644 --- a/apps/documentation/components/MobileSidebarDialog.tsx +++ b/apps/documentation/components/MobileSidebarDialog.tsx @@ -1,54 +1,48 @@ -import { useLocale } from 'next-intl'; -import { usePathname } from 'next/navigation'; -import { NavigationMenuMobile } from '~/components/SharedNav/Menu'; -import { Sheet, SheetContent } from '~/components/ui/sheet'; -import LogoComponent from './SharedNav/LogoComponent'; -import { Sidebar } from './Sidebar'; -import { Button } from '@codaco/ui'; -import { X as CloseMenu } from 'lucide-react'; -import { useEffect } from 'react'; +import { Button } from "@codaco/ui"; +import { X as CloseMenu } from "lucide-react"; +import { useLocale } from "next-intl"; +import { usePathname } from "next/navigation"; +import { useEffect } from "react"; +import { NavigationMenuMobile } from "~/components/SharedNav/Menu"; +import { Sheet, SheetContent } from "~/components/ui/sheet"; +import LogoComponent from "./SharedNav/LogoComponent"; +import { Sidebar } from "./Sidebar"; type MobileSidebarDialogProps = { - open: boolean; - setOpen: React.Dispatch>; + open: boolean; + setOpen: React.Dispatch>; }; -export default function MobileSidebarDialog({ - open, - setOpen, -}: MobileSidebarDialogProps) { - const pathname = usePathname(); - const locale = useLocale(); +export default function MobileSidebarDialog({ open, setOpen }: MobileSidebarDialogProps) { + const pathname = usePathname(); + const locale = useLocale(); - // Check if we are on the home page by comparing the pathname to our supported locals - const isHomePage = pathname === `/${locale}`; + // Check if we are on the home page by comparing the pathname to our supported locals + const isHomePage = pathname === `/${locale}`; - // When the path changes, close - useEffect(() => { - setOpen(false); - }, [pathname, setOpen]); + // biome-ignore lint/correctness/useExhaustiveDependencies: close when the path changes + useEffect(() => { + setOpen(false); + }, [pathname, setOpen]); - return ( - - -
- - -
+ return ( + + +
+ + +
- - {!isHomePage && } -
-
- ); + + {!isHomePage && } +
+
+ ); } diff --git a/apps/documentation/components/PopoutBox.tsx b/apps/documentation/components/PopoutBox.tsx index f353cd24..b5dc2139 100644 --- a/apps/documentation/components/PopoutBox.tsx +++ b/apps/documentation/components/PopoutBox.tsx @@ -1,52 +1,46 @@ -import { Heading } from '@codaco/ui'; -import { type ReactNode } from 'react'; -import { cn } from '~/lib/utils'; +import { Heading } from "@codaco/ui"; +import type { ReactNode } from "react"; +import { cn } from "~/lib/utils"; export type PopoutBoxProps = { - title?: string; - children: ReactNode; - icon?: ReactNode; - iconClassName?: string; - className?: string; + title?: string; + children: ReactNode; + icon?: ReactNode; + iconClassName?: string; + className?: string; }; -const PopoutBox = ({ - title, - children, - icon, - className, - iconClassName, -}: PopoutBoxProps) => { - return ( - + ); }; export default PopoutBox; diff --git a/apps/documentation/components/ProjectSwitcher.tsx b/apps/documentation/components/ProjectSwitcher.tsx index 8c265f49..e614c5d6 100644 --- a/apps/documentation/components/ProjectSwitcher.tsx +++ b/apps/documentation/components/ProjectSwitcher.tsx @@ -1,97 +1,78 @@ -'use client'; +"use client"; -import { - Heading, - Paragraph, - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectTrigger, -} from '@codaco/ui'; -import { useRouter } from '~/navigation'; -import { useLocale, useTranslations } from 'next-intl'; -import { usePathname } from 'next/navigation'; -import { type Locale, type ProjectsEnum, projects } from '~/app/types'; -import { forwardRef } from 'react'; -import { cn } from '~/lib/utils'; +import { Heading, Paragraph, Select, SelectContent, SelectGroup, SelectItem, SelectTrigger } from "@codaco/ui"; +import { useLocale, useTranslations } from "next-intl"; +import { usePathname } from "next/navigation"; +import { forwardRef } from "react"; +import { type Locale, type Project, projects } from "~/app/types"; +import { cn } from "~/lib/utils"; +import { useRouter } from "~/navigation"; -const getImageForProject = (project: ProjectsEnum) => { - if (project === 'desktop') { - return ( - {project} - ); - } +const getImageForProject = (project: Project) => { + if (project === "desktop") { + return {project}; + } - if (project === 'fresco') { - return ( - {project} - ); - } + if (project === "fresco") { + return {project}; + } }; const ProjectValue = forwardRef< - HTMLDivElement, - { - project: ProjectsEnum; - showDescription?: boolean; - } + HTMLDivElement, + { + project: Project; + showDescription?: boolean; + } >(({ project, showDescription }, ref) => { - const t = useTranslations('ProjectSwitcher'); - return ( -
-
- {getImageForProject(project)} -
-
- - {t(`${project}.label`)} - - {showDescription && ( - - {t(`${project}.description`)} - - )} -
-
- ); + const t = useTranslations("ProjectSwitcher"); + return ( +
+
+ {getImageForProject(project)} +
+
+ + {t(`${project}.label`)} + + {showDescription && ( + + {t(`${project}.description`)} + + )} +
+
+ ); }); -ProjectValue.displayName = 'ProjectValue'; +ProjectValue.displayName = "ProjectValue"; export default function ProjectSwitcher() { - const router = useRouter(); - const pathname = usePathname(); - const project = pathname.split('/')[2]! as ProjectsEnum; - const locale = useLocale() as Locale; + const router = useRouter(); + const pathname = usePathname(); + // biome-ignore lint/style/noNonNullAssertion: path structure is known + const project = pathname.split("/")[2]! as Project; + const locale = useLocale() as Locale; - return ( - - ); + return ( + + ); } diff --git a/apps/documentation/components/Providers/theme-provider.tsx b/apps/documentation/components/Providers/theme-provider.tsx index 4a47fb7e..7d814e0b 100644 --- a/apps/documentation/components/Providers/theme-provider.tsx +++ b/apps/documentation/components/Providers/theme-provider.tsx @@ -1,11 +1,7 @@ -'use client'; +"use client"; -import * as React from 'react'; -import { - ThemeProvider as NextThemesProvider, - type ThemeProviderProps, -} from 'next-themes'; +import { ThemeProvider as NextThemesProvider, type ThemeProviderProps } from "next-themes"; export function ThemeProvider({ children, ...props }: ThemeProviderProps) { - return {children}; + return {children}; } diff --git a/apps/documentation/components/SharedNav/LogoComponent.tsx b/apps/documentation/components/SharedNav/LogoComponent.tsx index 4ebb346c..0659fed8 100644 --- a/apps/documentation/components/SharedNav/LogoComponent.tsx +++ b/apps/documentation/components/SharedNav/LogoComponent.tsx @@ -1,42 +1,31 @@ -import { useTheme } from 'next-themes'; -import Link from 'next/link'; -import { cn } from '~/lib/utils'; +import { useTheme } from "next-themes"; +import Link from "next/link"; +import { cn } from "~/lib/utils"; type LogoComponentProps = { - invisible?: boolean; - className?: string; + invisible?: boolean; + className?: string; }; -const LogoComponent = ({ - invisible = false, - className, -}: LogoComponentProps) => { - const { resolvedTheme } = useTheme(); - return ( - - Network Canvas Documentation - Network Canvas Documentation - - ); +const LogoComponent = ({ invisible = false, className }: LogoComponentProps) => { + const { resolvedTheme } = useTheme(); + return ( + + Network Canvas Documentation + Network Canvas Documentation + + ); }; export default LogoComponent; diff --git a/apps/documentation/components/SharedNav/Menu.tsx b/apps/documentation/components/SharedNav/Menu.tsx index 11508758..b17b6c3e 100644 --- a/apps/documentation/components/SharedNav/Menu.tsx +++ b/apps/documentation/components/SharedNav/Menu.tsx @@ -1,220 +1,202 @@ -import { - Heading, - Paragraph, - buttonVariants, - headingVariants, -} from '@codaco/ui'; -import * as NavigationMenu from '@radix-ui/react-navigation-menu'; -import { ArrowLeftCircle, ChevronDown } from 'lucide-react'; -import { useTranslations } from 'next-intl'; -import Link from 'next/link'; -import { useState } from 'react'; -import { cn } from '~/lib/utils'; +import { Heading, Paragraph, buttonVariants, headingVariants } from "@codaco/ui"; +import * as NavigationMenu from "@radix-ui/react-navigation-menu"; +import { ArrowLeftCircle, ChevronDown } from "lucide-react"; +import { useTranslations } from "next-intl"; +import Link from "next/link"; +import { useState } from "react"; +import { cn } from "~/lib/utils"; const links = [ - { - translationKey: 'community', - href: 'https://community.networkcanvas.com', - }, - { - translationKey: 'documentation', - href: '/', - }, - { - translationKey: 'projects', - menu: [ - { - titleTranslationKey: 'projectsChildren.partner-services.label', - descriptionTranslationKey: - 'projectsChildren.partner-services.description', - href: 'https://partnerservices.networkcanvas.com', - image: '/images/projects/partner-services.jpg', - }, - { - titleTranslationKey: 'projectsChildren.fresco.label', - descriptionTranslationKey: 'projectsChildren.fresco.description', - href: 'https://github.com/complexdatacollective/Fresco', - image: '/images/fresco.svg', - }, - { - titleTranslationKey: 'projectsChildren.studio.label', - descriptionTranslationKey: 'projectsChildren.studio.description', - href: 'https://github.com/complexdatacollective/Studio', - image: '/images/studio.svg', - }, - ], - }, - { - translationKey: 'download', - style: 'button', - href: 'https://networkcanvas.com/download', - }, + { + translationKey: "community", + href: "https://community.networkcanvas.com", + }, + { + translationKey: "documentation", + href: "/", + }, + { + translationKey: "projects", + menu: [ + { + titleTranslationKey: "projectsChildren.partner-services.label", + descriptionTranslationKey: "projectsChildren.partner-services.description", + href: "https://partnerservices.networkcanvas.com", + image: "/images/projects/partner-services.jpg", + }, + { + titleTranslationKey: "projectsChildren.fresco.label", + descriptionTranslationKey: "projectsChildren.fresco.description", + href: "https://github.com/complexdatacollective/Fresco", + image: "/images/fresco.svg", + }, + { + titleTranslationKey: "projectsChildren.studio.label", + descriptionTranslationKey: "projectsChildren.studio.description", + href: "https://github.com/complexdatacollective/Studio", + image: "/images/studio.svg", + }, + ], + }, + { + translationKey: "download", + style: "button", + href: "https://networkcanvas.com/download", + }, ]; const linkClasses = cn( - headingVariants({ variant: 'h4-all-caps', margin: 'none' }), - 'focusable flex items-center underline-offset-8 hover:text-success', + headingVariants({ variant: "h4-all-caps", margin: "none" }), + "focusable flex items-center underline-offset-8 hover:text-success", ); export const NavigationMenuDemo = () => { - const t = useTranslations('SharedNavigation'); + const t = useTranslations("SharedNavigation"); - return ( - -