O7W4FZVRKDQDAAXEW4T7P262PPRILRCSSACODMUTQZ6VNR36PVCQC
import * as React from "react"
import { cn } from "@/lib/utils"
function Table({ className, ...props }: React.ComponentProps<"table">) {
return (
<div
data-slot="table-container"
className="relative w-full overflow-x-auto"
>
<table
data-slot="table"
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
</div>
)
}
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
return (
<thead
data-slot="table-header"
className={cn("[&_tr]:border-b", className)}
{...props}
/>
)
}
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
return (
<tbody
data-slot="table-body"
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
)
}
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
return (
<tfoot
data-slot="table-footer"
className={cn(
"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
className
)}
{...props}
/>
)
}
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
return (
<tr
data-slot="table-row"
className={cn(
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
className
)}
{...props}
/>
)
}
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
return (
<th
data-slot="table-head"
className={cn(
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
)
}
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
return (
<td
data-slot="table-cell"
className={cn(
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
)
}
function TableCaption({
className,
...props
}: React.ComponentProps<"caption">) {
return (
<caption
data-slot="table-caption"
className={cn("text-muted-foreground mt-4 text-sm", className)}
{...props}
/>
)
}
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}
? (
<div>
<h2>{user?.givenName} {user?.familyName}</h2>
<p>{user?.email}</p>
<p>{user?.id}</p>
&& (
<div className="flex items-center justify-between py-4 mb-6 border-b">
<div className="flex items-center gap-3">
{user?.picture
? (
<img
src={user.picture}
alt="User avatar"
className="w-8 h-8 rounded-full"
/>
)
: (
<div className="w-8 h-8 rounded-full bg-stone-200 flex items-center justify-center text-stone-600 text-sm font-medium">
{user?.givenName?.[0] || user?.email?.[0] || "U"}
</div>
)}
<div>
<h2 className="text-base font-medium">
{user?.givenName ? `${user.givenName} ${user?.familyName || ""}` : user?.email}
</h2>
{user?.givenName && user?.email && <p className="text-xs text-muted-foreground">{user.email}</p>}
</div>
import { useKindeAuth } from "@kinde-oss/kinde-auth-react";
import React from "react";
import Authenticate from "./Authenticate";
const PublicContent: React.FC = () => {
const { isAuthenticated, isLoading } = useKindeAuth();
if (isLoading) {
return (
<div className="h-screen flex items-center justify-center">
<p className="text-lg text-gray-500">Loading...</p>
</div>
);
}
return (
!isAuthenticated && (
<div className="h-screen flex flex-col items-center justify-center">
<div className="text-center mb-10">
<h1 className="text-4xl font-bold mb-2">Skraak</h1>
<p className="text-lg text-gray-500">Organise and label bioacoustic data</p>
</div>
<Authenticate />
</div>
)
);
};
export default PublicContent;
import { useKindeAuth } from "@kinde-oss/kinde-auth-react";
import React, { useState, useEffect } from "react";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "./ui/table";
interface Dataset {
id: string;
name: string;
public: boolean;
description?: string;
createdBy: string;
createdAt: string;
lastModified: string;
modifiedBy: string;
owner: string;
active: boolean;
type: string;
}
interface DatasetsResponse {
data: Dataset[];
}
const Datasets: React.FC = () => {
const { isAuthenticated, isLoading: authLoading, getAccessToken } = useKindeAuth();
const [datasets, setDatasets] = useState<Dataset[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchDatasets = async () => {
if (!isAuthenticated) {
if (!authLoading) {
setLoading(false);
}
return;
}
setLoading(true);
setError(null);
try {
const accessToken = await getAccessToken();
const response = await fetch("/api/datasets", {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json() as DatasetsResponse;
setDatasets(data.data);
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to fetch datasets");
console.error("Error fetching datasets:", err);
} finally {
setLoading(false);
}
};
if (isAuthenticated && !authLoading) {
fetchDatasets();
}
}, [isAuthenticated, authLoading, getAccessToken]);
return (
<div className="card p-6 bg-white shadow-sm rounded-lg">
<h2 className="text-xl font-bold mb-4">Datasets</h2>
{loading && <p className="text-gray-500">Loading datasets...</p>}
{!isAuthenticated && !authLoading &&
<p className="text-amber-600 mb-4">Please log in to access datasets</p>}
{error && <p className="text-red-600 mb-4">Error: {error}</p>}
{!loading && !error && isAuthenticated && datasets.length > 0 && (
<div className="w-full overflow-visible">
<Table>
<TableHeader className="bg-muted">
<TableRow className="border-b-2 border-primary/20">
<TableHead className="w-[180px] py-3 font-bold text-sm uppercase">Name</TableHead>
<TableHead className="w-[100px] py-3"></TableHead>
<TableHead className="py-3 font-bold text-sm uppercase">Description</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{datasets.map((dataset) => (
<TableRow key={dataset.id}>
<TableCell className="font-medium whitespace-normal break-words">{dataset.name}</TableCell>
<TableCell className="text-center">
<span className={`inline-block px-2 py-1 rounded-full text-xs font-medium ${
dataset.type === 'organize' ? 'bg-stone-300 text-stone-800' :
dataset.type === 'train' ? 'bg-stone-200 text-stone-700' :
'bg-stone-100 text-stone-600'
}`}>
{dataset.type}
</span>
</TableCell>
<TableCell className="whitespace-normal break-words">{dataset.description || "—"}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
{!loading && !error && isAuthenticated && datasets.length === 0 && (
<p className="text-gray-500">No datasets available</p>
)}
</div>
);
};
export default Datasets;
import React, { useState } from "react";
import { Button } from "./ui/button";
const Count: React.FC = () => {
const [count, setCount] = useState(0);
return (
<div className="card">
<Button
onClick={() => setCount((count) => count + 1)}
aria-label="increment"
variant="default"
>
count is {count}
</Button>
</div>
);
};
export default Count;
const { isAuthenticated, isLoading: authLoading, getAccessToken } = useKindeAuth();
const [count, setCount] = useState(0);
const [datasets, setDatasets] = useState<string>("No data loaded");
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const { isAuthenticated, isLoading } = useKindeAuth();
const fetchDatasets = async () => {
if (!isAuthenticated) {
setError("Please log in to access datasets");
return;
}
setLoading(true);
setError(null);
try {
// Get the access token
const accessToken = await getAccessToken();
console.log("Auth status:", isAuthenticated ? "Authenticated" : "Not authenticated");
console.log("Token obtained:", accessToken ? "Yes" : "No");
// Include the token in the request header
const response = await fetch("/api/datasets", {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
console.log("Response status:", response.status);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json() as DatasetsResponse;
setDatasets(JSON.stringify(data, null, 2));
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to fetch datasets");
console.error("Error fetching datasets:", err);
} finally {
setLoading(false);
}
};
if (isLoading) {
return <p>Loading</p>;
}
<div className="container mx-auto text-xl px-5 space-y-2">
<div className="card">
<Button
onClick={() => setCount((count) => count + 1)}
aria-label="increment"
variant="default"
>
count is {count}
</Button>
<p>
Edit <code>src/components/AuthorisedContent.tsx</code> and save to test HMR
</p>
</div>
<div className="card">
<h2>Datasets</h2>
<Button
onClick={fetchDatasets}
disabled={loading || authLoading}
aria-label="fetch datasets"
variant="secondary"
>
{loading ? "Loading..." : "Fetch Datasets"}
</Button>
{!isAuthenticated && !authLoading &&
<p className="text-amber-600 mt-2">Please log in to access datasets</p>
}
{error && <p className="error">Error: {error}</p>}
<pre className="json-display">{datasets}</pre>
isAuthenticated
&& (
<div className="container mx-auto text-xl px-5 space-y-2">
<UserProfile />
<DataSets />
- TypeScript strict mode with noUnusedLocals and noUnusedParameters flags
- React functional components with hooks (follow react-hooks plugin rules)
- Project structure:
- src/react-app: Frontend React code
- src/worker: Cloudflare Workers backend
- src/components: UI components (including shadcn)
- src/lib: Utility functions
- db: Database schema and migrations
- TypeScript strict mode with noUnusedLocals, noUnusedParameters, and noUncheckedSideEffects
- React functional components with hooks (follow react-hooks/exhaustive-deps rules)
- Path aliases: @/ (src), @react/ (src/react-app), @worker/ (src/worker), @db/ (db)
- Import order: React/external libraries first, then internal modules
- Formatting: Use dprint for code formatting
- Naming: PascalCase for components/types, camelCase for functions/variables
- Error handling: try/catch with proper error typing for async operations
- API: Hono (4.x) for backend routes with typed request/response interfaces
- Authentication: Kinde Auth (components in @kinde-oss/kinde-auth-react)
- Import order: React/external libraries first, then internal modules (@/ path aliases available)
- Naming: PascalCase for components/types, camelCase for functions/variables
- Error handling: use try/catch blocks for async operations with proper error typing
- API: Hono for backend routes with typed request/response interfaces
- Avoid any type; use explicit interfaces for data structures
- Avoid any; use explicit interfaces and type narrowing
- Cloudflare Workers: Follow Workers runtime constraints and use proper typing