diff --git a/public/favicon.ico b/public/favicon.ico index 60c702a..0f0c01d 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/src/app/api/participants/route.ts b/src/app/api/participants/route.ts index 6a01750..6ad7aec 100644 --- a/src/app/api/participants/route.ts +++ b/src/app/api/participants/route.ts @@ -1,18 +1,36 @@ import { db } from "~/server/db"; -import { participants } from "~/server/db/schema"; +import { participants, trialParticipants, trials } from "~/server/db/schema"; import { NextResponse } from "next/server"; -import { eq } from "drizzle-orm"; +import { eq, sql } from "drizzle-orm"; export async function GET(request: Request) { - const { searchParams } = new URL(request.url); - const studyId = searchParams.get('studyId'); - - if (!studyId) { - return NextResponse.json({ error: 'Study ID is required' }, { status: 400 }); - } + try { + const { searchParams } = new URL(request.url); + const studyId = searchParams.get('studyId'); + + if (!studyId) { + return NextResponse.json({ error: 'Study ID is required' }, { status: 400 }); + } - const allParticipants = await db.select().from(participants).where(eq(participants.studyId, parseInt(studyId))); - return NextResponse.json(allParticipants); + const participantsWithLatestTrial = await db + .select({ + id: participants.id, + name: participants.name, + createdAt: participants.createdAt, + latestTrialTimestamp: sql`MAX(${trials.createdAt})`.as('latestTrialTimestamp') + }) + .from(participants) + .leftJoin(trialParticipants, eq(participants.id, trialParticipants.participantId)) + .leftJoin(trials, eq(trialParticipants.trialId, trials.id)) + .where(eq(participants.studyId, parseInt(studyId))) + .groupBy(participants.id) + .orderBy(sql`COALESCE(MAX(${trials.createdAt}), ${participants.createdAt}) DESC`); + + return NextResponse.json(participantsWithLatestTrial); + } catch (error) { + console.error('Error in GET /api/participants:', error); + return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }); + } } export async function POST(request: Request) { diff --git a/src/app/api/trials/route.ts b/src/app/api/trials/route.ts new file mode 100644 index 0000000..d19c837 --- /dev/null +++ b/src/app/api/trials/route.ts @@ -0,0 +1,52 @@ +import { db } from "~/server/db"; +import { trials, trialParticipants } from "~/server/db/schema"; +import { NextResponse } from "next/server"; +import { eq, sql } from "drizzle-orm"; + +export async function GET() { + const allTrials = await db + .select({ + id: trials.id, + title: trials.title, + participantIds: sql`ARRAY_AGG(${trialParticipants.participantId})`.as('participantIds'), + }) + .from(trials) + .leftJoin(trialParticipants, eq(trials.id, trialParticipants.trialId)) + .groupBy(trials.id); + + return NextResponse.json(allTrials); +} + +export async function POST(request: Request) { + const { title, participantIds } = await request.json(); + + if (!title || !Array.isArray(participantIds) || participantIds.some(id => typeof id !== 'number')) { + return NextResponse.json({ error: 'Title and valid Participant IDs are required' }, { status: 400 }); + } + + // Insert the new trial into the trials table + const newTrial = await db.insert(trials).values({ title }).returning(); + // Check if newTrial is defined and has at least one element + if (!newTrial || newTrial.length === 0) { + throw new Error('Failed to create a new trial'); + } + // Insert the participant associations into the trial_participants table + const trialId = newTrial[0]?.id; // Use optional chaining to safely get the ID of the newly created trial + if (trialId === undefined) { + throw new Error('Trial ID is undefined'); + } + const trialParticipantEntries = participantIds.map(participantId => ({ + trialId, + participantId, + })); + + await db.insert(trialParticipants).values(trialParticipantEntries); + + return NextResponse.json(newTrial[0]); +} + +export async function DELETE(request: Request) { + const { id } = await request.json(); + await db.delete(trials).where(eq(trials.id, id)); + return NextResponse.json({ message: "Trial deleted successfully" }); +} diff --git a/src/app/dash/page.tsx b/src/app/dash/page.tsx index cb6622a..56ee694 100644 --- a/src/app/dash/page.tsx +++ b/src/app/dash/page.tsx @@ -1,11 +1,46 @@ +"use client"; + import Layout from "~/components/layout"; import { Card, CardHeader, CardTitle, CardContent } from "~/components/ui/card"; +import { useStudyContext } from '~/context/StudyContext'; +import { useEffect, useState } from 'react'; +import Link from 'next/link'; +import { Button } from "~/components/ui/button"; +import { Avatar, AvatarFallback } from "~/components/ui/avatar"; + +interface ParticipantWithTrial { + id: number; + name: string; + latestTrialTimestamp: string | null; + createdAt: string; // Add createdAt to the interface +} const DashboardPage: React.FC = () => { + const { selectedStudy } = useStudyContext(); + const [participants, setParticipants] = useState([]); + + useEffect(() => { + const fetchParticipants = async () => { + if (selectedStudy) { + const response = await fetch(`/api/participants?studyId=${selectedStudy.id}`); + const data = await response.json(); + setParticipants(data); + } + }; + + fetchParticipants(); + }, [selectedStudy]); + + const formatDate = (dateString: string | null) => { + if (!dateString) return 'No trials yet'; + const date = new Date(dateString); + return date.toLocaleDateString() + ' ' + date.toLocaleTimeString(); + }; + return ( - +
- + Platform Information @@ -13,12 +48,35 @@ const DashboardPage: React.FC = () => { {/* Add content for Platform Information */} - + Participants - {/* Add content for Participants */} +
+ {participants.slice(0, 4).map(participant => ( + + + {participant.name.split(' ').map(n => n[0]).join('')} + +
+

{participant.name}

+

+ Last trial: {formatDate(participant.latestTrialTimestamp)} +

+
+
+ ))} +
+ {participants.length > 4 && ( +
+ + + +
+ )}
diff --git a/src/app/forms/page.tsx b/src/app/forms/page.tsx index fc28550..c8bffcc 100644 --- a/src/app/forms/page.tsx +++ b/src/app/forms/page.tsx @@ -1,15 +1,22 @@ import Layout from "~/components/layout"; import { FormsGrid } from "~/components/forms/FormsGrid"; import { UploadFormButton } from "~/components/forms/UploadFormButton"; +import { Card, CardHeader, CardTitle, CardContent } from "~/components/ui/card"; export default function FormsPage() { return ( - -
-

Forms

- -
- + + + + + Forms + + + + + + + ); } \ No newline at end of file diff --git a/src/app/participants/page.tsx b/src/app/participants/page.tsx index 69c1bcc..aae21d0 100644 --- a/src/app/participants/page.tsx +++ b/src/app/participants/page.tsx @@ -3,7 +3,7 @@ import { Participants } from "~/components/participant/Participants"; const ParticipantsPage = () => { return ( - + ); diff --git a/src/app/studies/page.tsx b/src/app/studies/page.tsx index 8c1c7c4..1b485c6 100644 --- a/src/app/studies/page.tsx +++ b/src/app/studies/page.tsx @@ -3,7 +3,7 @@ import { Studies } from "~/components/study/Studies"; export default function StudiesPage() { return ( - + ); diff --git a/src/app/trials/page.tsx b/src/app/trials/page.tsx new file mode 100644 index 0000000..d8b97e9 --- /dev/null +++ b/src/app/trials/page.tsx @@ -0,0 +1,10 @@ +import Layout from "~/components/layout"; +import { Trials } from "~/components/trial/Trials"; + +export default function TrialsPage() { + return ( + + + + ); +} diff --git a/src/components/forms/FormCard.tsx b/src/components/forms/FormCard.tsx index effaac5..8853a09 100644 --- a/src/components/forms/FormCard.tsx +++ b/src/components/forms/FormCard.tsx @@ -2,6 +2,7 @@ import Image from 'next/image'; import { Card, CardContent, CardFooter } from "~/components/ui/card"; import { Badge } from "~/components/ui/badge"; import { Trash2 } from "lucide-react"; +import { Button } from "~/components/ui/button"; interface FormCardProps { form: { @@ -23,7 +24,7 @@ export function FormCard({ form, onDelete }: FormCardProps) { }; return ( - + - +

{form.title}

- { e.stopPropagation(); onDelete(form.id); - }} - /> + }} + > + + Delete +
{form.studyTitle} diff --git a/src/components/forms/Forms.tsx b/src/components/forms/Forms.tsx new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/src/components/forms/Forms.tsx @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/layout.tsx b/src/components/layout.tsx index d3fd3e7..ee8cd44 100644 --- a/src/components/layout.tsx +++ b/src/components/layout.tsx @@ -3,13 +3,17 @@ import { Sidebar } from "~/components/sidebar"; import { StudyHeader } from "~/components/study/StudyHeader"; import { Toaster } from "~/components/ui/toaster"; -const Layout = ({ children }: PropsWithChildren) => { +interface LayoutProps { + pageTitle: string; +} + +const Layout = ({ children, pageTitle }: PropsWithChildren) => { return (
- + {children}
diff --git a/src/components/participant/Participants.tsx b/src/components/participant/Participants.tsx index 4639b84..29b312d 100644 --- a/src/components/participant/Participants.tsx +++ b/src/components/participant/Participants.tsx @@ -8,9 +8,17 @@ import { Participant } from '../../types/Participant'; import { CreateParticipantDialog } from './CreateParticipantDialog'; import { useToast } from '~/hooks/use-toast'; import { ParticipantCard } from './ParticipantCard'; +import { Avatar, AvatarFallback } from "~/components/ui/avatar"; + +interface ParticipantWithTrial { + id: number; + name: string; + latestTrialTimestamp: string | null; + createdAt: string; +} export function Participants() { - const [participants, setParticipants] = useState([]); + const [participants, setParticipants] = useState([]); const { selectedStudy } = useStudyContext(); const { toast } = useToast(); @@ -22,9 +30,23 @@ export function Participants() { const fetchParticipants = async () => { if (!selectedStudy) return; - const response = await fetch(`/api/participants?studyId=${selectedStudy.id}`); - const data = await response.json(); - setParticipants(data); + try { + const response = await fetch(`/api/participants?studyId=${selectedStudy.id}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const text = await response.text(); + try { + const data = JSON.parse(text); + setParticipants(data); + } catch (e) { + console.error('Failed to parse JSON:', text); + throw new Error('Invalid JSON in response'); + } + } catch (error) { + console.error('Error fetching participants:', error); + // Handle the error appropriately, e.g., show a toast notification + } }; const createParticipant = async (name: string) => { @@ -41,33 +63,20 @@ export function Participants() { const deleteParticipant = async (id: number) => { if (!selectedStudy) return; try { - console.log(`Attempting to delete participant with ID: ${id}`); const response = await fetch(`/api/participants/${id}`, { method: 'DELETE', }); - console.log('Delete response:', response); - const contentType = response.headers.get("content-type"); - if (contentType && contentType.indexOf("application/json") !== -1) { - const result = await response.json(); - console.log('Delete result:', result); - - if (!response.ok) { - throw new Error(result.error || `Failed to delete participant. Status: ${response.status}`); - } - - setParticipants(participants.filter(p => p.id !== id)); - toast({ - title: "Success", - description: "Participant deleted successfully", - }); - } else { - const text = await response.text(); - console.error('Unexpected response:', text); - throw new Error(`Unexpected response from server. Status: ${response.status}`); + if (!response.ok) { + throw new Error('Failed to delete participant'); } + + setParticipants(participants.filter(p => p.id !== id)); + toast({ + title: "Success", + description: "Participant deleted successfully", + }); } catch (error) { - console.error('Error deleting participant:', error); toast({ title: "Error", description: error instanceof Error ? error.message : 'Failed to delete participant', @@ -81,16 +90,36 @@ export function Participants() { } return ( - + Participants for {selectedStudy.title} {participants.length > 0 ? ( -
+
{participants.map(participant => ( - + + + {participant.name.split(' ').map(n => n[0]).join('')} + +
+

{participant.name}

+

+ {participant.latestTrialTimestamp + ? `Last trial: ${new Date(participant.latestTrialTimestamp).toLocaleString()}` + : 'No trials yet'} +

+
+ +
))}
) : ( diff --git a/src/components/sidebar.tsx b/src/components/sidebar.tsx index 979b3a0..4506dcf 100644 --- a/src/components/sidebar.tsx +++ b/src/components/sidebar.tsx @@ -3,7 +3,8 @@ import { UserButton, useUser } from "@clerk/nextjs" import { BarChartIcon, - BeakerIcon, + UsersRoundIcon, + LandPlotIcon, BotIcon, FolderIcon, FileTextIcon, @@ -22,7 +23,8 @@ import { ThemeToggle } from "~/components/ThemeToggle" const navItems = [ { name: "Dashboard", href: "/dash", icon: LayoutDashboard }, { name: "Studies", href: "/studies", icon: FolderIcon }, - { name: "Participants", href: "/participants", icon: BeakerIcon }, + { name: "Participants", href: "/participants", icon: UsersRoundIcon }, + { name: "Trials", href: "/trials", icon: LandPlotIcon }, { name: "Forms", href: "/forms", icon: FileTextIcon }, { name: "Data Analysis", href: "/analysis", icon: BarChartIcon }, { name: "Settings", href: "/settings", icon: Settings }, diff --git a/src/components/study/StudyHeader.tsx b/src/components/study/StudyHeader.tsx index 8edc4b5..341bf80 100644 --- a/src/components/study/StudyHeader.tsx +++ b/src/components/study/StudyHeader.tsx @@ -8,68 +8,72 @@ import { StudySelector } from './StudySelector'; import { CreateStudyDialog } from '~/components/study/CreateStudyDialog'; import { Study } from '~/types/Study'; -export function StudyHeader() { - const { studies, selectedStudy, setSelectedStudy, validateAndSetSelectedStudy, fetchAndSetStudies } = useStudyContext(); +interface StudyHeaderProps { + pageTitle: string; +} - useEffect(() => { - const savedStudyId = localStorage.getItem('selectedStudyId'); - if (savedStudyId) { - validateAndSetSelectedStudy(parseInt(savedStudyId, 10)); - } - }, [validateAndSetSelectedStudy]); +export const StudyHeader: React.FC = ({ pageTitle }) => { + const { studies, selectedStudy, setSelectedStudy, validateAndSetSelectedStudy, fetchAndSetStudies } = useStudyContext(); - const handleStudyChange = (studyId: string) => { - const study = studies.find(s => s.id.toString() === studyId); - if (study) { - setSelectedStudy(study); - localStorage.setItem('selectedStudyId', studyId); - } - }; + useEffect(() => { + const savedStudyId = localStorage.getItem('selectedStudyId'); + if (savedStudyId) { + validateAndSetSelectedStudy(parseInt(savedStudyId, 10)); + } + }, [validateAndSetSelectedStudy]); - const createStudy = async (newStudy: Omit) => { - const response = await fetch('/api/studies', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(newStudy), - }); - if (!response.ok) { - throw new Error('Failed to create study'); - } - const createdStudy = await response.json(); - await fetchAndSetStudies(); - return createdStudy; - }; + const handleStudyChange = (studyId: string) => { + const study = studies.find(s => s.id.toString() === studyId); + if (study) { + setSelectedStudy(study); + localStorage.setItem('selectedStudyId', studyId); + } + }; - const handleCreateStudy = async (newStudy: Omit) => { - const createdStudy = await createStudy(newStudy); - setSelectedStudy(createdStudy); - localStorage.setItem('selectedStudyId', createdStudy.id.toString()); - }; + const createStudy = async (newStudy: Omit) => { + const response = await fetch('/api/studies', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(newStudy), + }); + if (!response.ok) { + throw new Error('Failed to create study'); + } + const createdStudy = await response.json(); + await fetchAndSetStudies(); + return createdStudy; + }; - return ( - - - - - -

- {selectedStudy ? selectedStudy.title : 'Select a Study'} -

-
- -

{selectedStudy ? selectedStudy.title : 'No study selected'}

-
-
-
-
- - ) => handleCreateStudy(study as Study)} /> -
-
-
- ); -} \ No newline at end of file + const handleCreateStudy = async (newStudy: Omit) => { + const createdStudy = await createStudy(newStudy); + setSelectedStudy(createdStudy); + localStorage.setItem('selectedStudyId', createdStudy.id.toString()); + }; + + return ( + + + + + +

+ {pageTitle} +

+
+ +

{selectedStudy ? selectedStudy.title : 'No study selected'}

+
+
+
+
+ + ) => handleCreateStudy(study as Study)} /> +
+
+
+ ); +}; \ No newline at end of file diff --git a/src/components/trial/CreateTrialDialog.tsx b/src/components/trial/CreateTrialDialog.tsx new file mode 100644 index 0000000..9dd3af6 --- /dev/null +++ b/src/components/trial/CreateTrialDialog.tsx @@ -0,0 +1,58 @@ +import React, { useState } from 'react'; +import { Button } from "~/components/ui/button"; +import { Input } from "~/components/ui/input"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "~/components/ui/dialog"; +import { Label } from "~/components/ui/label"; + +interface CreateTrialDialogProps { + onCreateTrial: (title: string, participantIds: number[]) => void; +} + +export function CreateTrialDialog({ onCreateTrial }: CreateTrialDialogProps) { + const [title, setTitle] = useState(''); + const [participantIds, setParticipantIds] = useState(''); + + const handleCreate = () => { + const ids = participantIds.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id)); + if (title && ids.length > 0) { + onCreateTrial(title, ids); + setTitle(''); + setParticipantIds(''); + } + }; + + return ( + + + + + + + Add New Trial + +
+
+ + setTitle(e.target.value)} + className="col-span-3" + /> +
+
+ + setParticipantIds(e.target.value)} + className="col-span-3" + placeholder="e.g. 1, 2, 3" + /> +
+
+ +
+
+ ); +} diff --git a/src/components/trial/Trials.tsx b/src/components/trial/Trials.tsx new file mode 100644 index 0000000..f918237 --- /dev/null +++ b/src/components/trial/Trials.tsx @@ -0,0 +1,109 @@ +"use client"; + +import React, { useState, useEffect } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; +import { Button } from "~/components/ui/button"; +import { useToast } from '~/hooks/use-toast'; +import { CreateTrialDialog } from '~/components/trial/CreateTrialDialog'; + +interface Trial { + id: number; + title: string; + participantIds: number[]; + createdAt: string; +} + +export function Trials() { + const [trials, setTrials] = useState([]); + const { toast } = useToast(); + + useEffect(() => { + fetchTrials(); + }, []); + + const fetchTrials = async () => { + const response = await fetch('/api/trials'); + + if (!response.ok) { + const errorText = await response.text(); + console.error('Error fetching trials:', response.status, errorText); + return; + } + + const data = await response.json(); + if (!data || data.length === 0) { + console.warn('No trials found'); + setTrials([]); // Set to an empty array if no trials are found + return; + } + + setTrials(data); + }; + + const createTrial = async (title: string, participantIds: number[]) => { + const response = await fetch('/api/trials', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ title, participantIds }), + }); + const newTrial = await response.json(); + setTrials([...trials, newTrial]); + toast({ + title: "Success", + description: "Trial created successfully", + }); + }; + + const deleteTrial = async (id: number) => { + const response = await fetch(`/api/trials/${id}`, { + method: 'DELETE', + }); + + if (response.ok) { + setTrials(trials.filter(trial => trial.id !== id)); + toast({ + title: "Success", + description: "Trial deleted successfully", + }); + } else { + toast({ + title: "Error", + description: "Failed to delete trial", + variant: "destructive", + }); + } + }; + + return ( + + + Trials + + + + {trials.length > 0 ? ( +
+ {trials.map(trial => ( + +
+

{trial.title}

+

Participants: {trial.participantIds.join(', ')}

+
+ +
+ ))} +
+ ) : ( +

No trials added yet.

+ )} +
+
+ ); +} diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts index 5c4969e..99edfe4 100644 --- a/src/server/db/schema.ts +++ b/src/server/db/schema.ts @@ -89,4 +89,24 @@ export const users = pgTable( .default(sql`CURRENT_TIMESTAMP`) .notNull(), } +); + +export const trials = pgTable( + "trial", + { + id: serial("id").primaryKey(), + title: varchar("title", { length: 256 }).notNull(), + createdAt: timestamp("created_at", { withTimezone: true }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + } +); + +export const trialParticipants = pgTable( + "trial_participants", + { + id: serial("id").primaryKey(), + trialId: integer("trial_id").references(() => trials.id).notNull(), + participantId: integer("participant_id").references(() => participants.id).notNull(), + } ); \ No newline at end of file diff --git a/src/styles/globals.css b/src/styles/globals.css index 4c0e2f8..451168a 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -6,8 +6,8 @@ :root { --background: 210 50% 98%; --foreground: 215 25% 27%; - --card: 210 50% 98%; - --card-foreground: 215 25% 27%; + --card: 210 50% 98%; /* Card background color */ + --card-foreground: 215 25% 27%; /* Card text color */ --popover: 210 50% 98%; --popover-foreground: 215 25% 27%; --primary: 215 60% 40%; @@ -35,39 +35,60 @@ --sidebar-foreground: 215 25% 27%; --sidebar-muted: 215 20% 50%; --sidebar-hover: 210 60% 86%; + + --card-level-1: 210 50% 95%; /* Level 1 card background color */ + --card-level-2: 210 50% 90%; /* Level 2 card background color */ + --card-level-3: 210 50% 85%; /* Level 3 card background color */ } .dark { - --background: 220 20% 15%; - --foreground: 220 15% 85%; - --card: 220 20% 15%; - --card-foreground: 220 15% 85%; + --background: 220 20% 15%; /* Dark mode background */ + --foreground: 220 20% 90%; /* Dark mode foreground */ + --card: 220 20% 15%; /* Dark mode card background color */ + --card-foreground: 220 20% 90%; /* Dark mode card text color */ --popover: 220 20% 15%; - --popover-foreground: 220 15% 85%; + --popover-foreground: 220 20% 90%; --primary: 220 60% 50%; - --primary-foreground: 220 15% 85%; - --secondary: 220 25% 20%; - --secondary-foreground: 220 15% 85%; - --muted: 220 25% 20%; - --muted-foreground: 220 15% 65%; - --accent: 220 25% 20%; - --accent-foreground: 220 15% 85%; - --destructive: 0 62% 30%; - --destructive-foreground: 220 15% 85%; - --border: 220 25% 20%; - --input: 220 25% 20%; + --primary-foreground: 220 20% 90%; + --secondary: 220 30% 20%; /* Darker secondary */ + --secondary-foreground: 220 20% 90%; + --muted: 220 30% 20%; + --muted-foreground: 220 20% 70%; + --accent: 220 30% 20%; + --accent-foreground: 220 20% 90%; + --destructive: 0 62% 40%; /* Darker destructive */ + --destructive-foreground: 220 20% 90%; + --border: 220 30% 20%; + --input: 220 30% 20%; --ring: 220 60% 50%; /* Update gradient variables for dark mode */ - --gradient-start: 220 20% 13%; + --gradient-start: 220 20% 12%; --gradient-end: 220 20% 15%; /* Update sidebar variables for dark mode */ --sidebar-background-top: 220 20% 15%; --sidebar-background-bottom: 220 20% 12%; - --sidebar-foreground: 220 15% 85%; - --sidebar-muted: 220 15% 65%; - --sidebar-hover: 220 25% 18%; + --sidebar-foreground: 220 20% 90%; + --sidebar-muted: 220 20% 70%; + --sidebar-hover: 220 30% 20%; + + --card-level-1: 220 20% 12%; /* Dark mode Level 1 card background color */ + --card-level-2: 220 20% 10%; /* Dark mode Level 2 card background color */ + --card-level-3: 220 20% 8%; /* Dark mode Level 3 card background color */ + } + + /* Add these utility classes */ + .card-level-1 { + background-color: hsl(var(--card-level-1)); + } + + .card-level-2 { + background-color: hsl(var(--card-level-2)); + } + + .card-level-3 { + background-color: hsl(var(--card-level-3)); } } @@ -117,4 +138,4 @@ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } -} +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 905062d..6b136f3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,25 +8,32 @@ "resolveJsonModule": true, "moduleDetection": "force", "isolatedModules": true, - /* Strictness */ "strict": true, "noUncheckedIndexedAccess": true, "checkJs": true, - /* Bundled projects */ - "lib": ["dom", "dom.iterable", "ES2022"], + "lib": [ + "dom", + "dom.iterable", + "ES2022" + ], "noEmit": true, "module": "ESNext", "moduleResolution": "Bundler", - "jsx": "preserve", - "plugins": [{ "name": "next" }], + "jsx": "preserve", // or "react" for older versions + "plugins": [ + { + "name": "next" + } + ], "incremental": true, - /* Path Aliases */ "baseUrl": ".", "paths": { - "~/*": ["./src/*"] + "~/*": [ + "./src/*" + ] } }, "include": [ @@ -38,5 +45,7 @@ "**/*.js", ".next/types/**/*.ts" ], - "exclude": ["node_modules"] + "exclude": [ + "node_modules" + ] }