Add a draft of data table and display all the repos

This commit is contained in:
Vladyslav Fedoriuk 2023-08-29 22:18:23 +02:00
parent 3ba2723368
commit 7bc2e6bb05
13 changed files with 748 additions and 393 deletions

4
.gitignore vendored
View File

@ -14,7 +14,6 @@ dist/
downloads/ downloads/
eggs/ eggs/
.eggs/ .eggs/
lib/
lib64/ lib64/
parts/ parts/
sdist/ sdist/
@ -158,3 +157,6 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear # and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/ .idea/
# Ruff cache
.ruff_cache/

View File

@ -1,5 +1,6 @@
"""The application-level conftest.""" """The application-level conftest."""
import asyncio import asyncio
import contextlib
from collections.abc import AsyncGenerator, Generator from collections.abc import AsyncGenerator, Generator
from typing import Literal from typing import Literal
@ -43,11 +44,8 @@ def event_loop(
An event loop is destroyed at the end of the test session. An event loop is destroyed at the end of the test session.
https://docs.pytest.org/en/6.2.x/fixture.html#fixture-scopes https://docs.pytest.org/en/6.2.x/fixture.html#fixture-scopes
""" """
loop = asyncio.get_event_loop_policy().get_event_loop() with contextlib.closing(loop := asyncio.get_event_loop_policy().get_event_loop()):
try:
yield loop yield loop
finally:
loop.close()
@pytest.fixture(scope="session") @pytest.fixture(scope="session")

View File

@ -1,6 +1,7 @@
"""Create index.json file from database.""" """Create index.json file from database."""
import json import json
from pathlib import Path from pathlib import Path
from typing import Final
import aiofiles import aiofiles
import sqlalchemy.orm import sqlalchemy.orm
@ -10,7 +11,8 @@ from app.database import Repo
from app.models import RepoDetail from app.models import RepoDetail
from app.uow import async_session_uow from app.uow import async_session_uow
INDEX_PATH = Path(__file__).parent.parent / "index.json" #: The path to the index.json file.
INDEX_PATH: Final[Path] = Path(__file__).parent.parent / "index.json"
async def create_index() -> None: async def create_index() -> None:

View File

@ -9,6 +9,7 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@tanstack/react-table": "^8.9.3",
"@types/node": "20.5.1", "@types/node": "20.5.1",
"@types/react": "18.2.20", "@types/react": "18.2.20",
"@types/react-dom": "18.2.7", "@types/react-dom": "18.2.7",
@ -25,6 +26,7 @@
"tailwind-merge": "^1.14.0", "tailwind-merge": "^1.14.0",
"tailwindcss": "3.3.3", "tailwindcss": "3.3.3",
"tailwindcss-animate": "^1.0.6", "tailwindcss-animate": "^1.0.6",
"typescript": "5.1.6" "typescript": "5.1.6",
"zod": "^3.22.2"
} }
} }

View File

@ -5,6 +5,9 @@ settings:
excludeLinksFromLockfile: false excludeLinksFromLockfile: false
dependencies: dependencies:
"@tanstack/react-table":
specifier: ^8.9.3
version: 8.9.3(react-dom@18.2.0)(react@18.2.0)
"@types/node": "@types/node":
specifier: 20.5.1 specifier: 20.5.1
version: 20.5.1 version: 20.5.1
@ -56,6 +59,9 @@ dependencies:
typescript: typescript:
specifier: 5.1.6 specifier: 5.1.6
version: 5.1.6 version: 5.1.6
zod:
specifier: ^3.22.2
version: 3.22.2
packages: packages:
/@aashutoshrathi/word-wrap@1.2.6: /@aashutoshrathi/word-wrap@1.2.6:
@ -377,6 +383,29 @@ packages:
tslib: 2.6.2 tslib: 2.6.2
dev: false dev: false
/@tanstack/react-table@8.9.3(react-dom@18.2.0)(react@18.2.0):
resolution:
{
integrity: sha512-Ng9rdm3JPoSCi6cVZvANsYnF+UoGVRxflMb270tVj0+LjeT/ZtZ9ckxF6oLPLcKesza6VKBqtdF9mQ+vaz24Aw==,
}
engines: { node: ">=12" }
peerDependencies:
react: ">=16"
react-dom: ">=16"
dependencies:
"@tanstack/table-core": 8.9.3
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@tanstack/table-core@8.9.3:
resolution:
{
integrity: sha512-NpHZBoHTfqyJk0m/s/+CSuAiwtebhYK90mDuf5eylTvgViNOujiaOaxNDxJkQQAsVvHWZftUGAx1EfO1rkKtLg==,
}
engines: { node: ">=12" }
dev: false
/@types/json5@0.0.29: /@types/json5@0.0.29:
resolution: resolution:
{ {
@ -3640,3 +3669,10 @@ packages:
integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==, integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==,
} }
dev: false dev: false
/zod@3.22.2:
resolution:
{
integrity: sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==,
}
dev: false

View File

@ -0,0 +1,17 @@
import { Repo } from "@/lib/schemas";
import { ColumnDef } from "@tanstack/react-table";
export const columns: ColumnDef<Repo>[] = [
{
accessorKey: "url",
header: "Link",
},
{
accessorKey: "description",
header: "Description",
},
{
accessorKey: "stars",
header: "Stars",
},
];

View File

@ -0,0 +1,80 @@
"use client";
import {
ColumnDef,
flexRender,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
}
export function DataTable<TData, TValue>({
columns,
data,
}: DataTableProps<TData, TValue>) {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
});
return (
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
);
}

View File

@ -5,64 +5,47 @@
@layer base { @layer base {
:root { :root {
--background: 0 0% 100%; --background: 0 0% 100%;
--foreground: 222.2 84% 4.9%; --foreground: 240 10% 3.9%;
--card: 0 0% 100%; --card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%; --card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%; --popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%; --popover-foreground: 240 10% 3.9%;
--primary: 142.1 76.2% 36.3%;
--primary: 222.2 47.4% 11.2%; --primary-foreground: 355.7 100% 97.3%;
--primary-foreground: 210 40% 98%; --secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--secondary: 210 40% 96.1%; --muted: 240 4.8% 95.9%;
--secondary-foreground: 222.2 47.4% 11.2%; --muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--muted: 210 40% 96.1%; --accent-foreground: 240 5.9% 10%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%; --destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%; --destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--border: 214.3 31.8% 91.4%; --input: 240 5.9% 90%;
--input: 214.3 31.8% 91.4%; --ring: 142.1 76.2% 36.3%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem; --radius: 0.5rem;
} }
.dark { .dark {
--background: 222.2 84% 4.9%; --background: 20 14.3% 4.1%;
--foreground: 210 40% 98%; --foreground: 0 0% 95%;
--card: 24 9.8% 10%;
--card: 222.2 84% 4.9%; --card-foreground: 0 0% 95%;
--card-foreground: 210 40% 98%; --popover: 0 0% 9%;
--popover-foreground: 0 0% 95%;
--popover: 222.2 84% 4.9%; --primary: 142.1 70.6% 45.3%;
--popover-foreground: 210 40% 98%; --primary-foreground: 144.9 80.4% 10%;
--secondary: 240 3.7% 15.9%;
--primary: 210 40% 98%; --secondary-foreground: 0 0% 98%;
--primary-foreground: 222.2 47.4% 11.2%; --muted: 0 0% 15%;
--muted-foreground: 240 5% 64.9%;
--secondary: 217.2 32.6% 17.5%; --accent: 12 6.5% 15.1%;
--secondary-foreground: 210 40% 98%; --accent-foreground: 0 0% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%; --destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%; --destructive-foreground: 0 85.7% 97.3%;
--border: 240 3.7% 15.9%;
--border: 217.2 32.6% 17.5%; --input: 240 3.7% 15.9%;
--input: 217.2 32.6% 17.5%; --ring: 142.4 71.8% 29.2%;
--ring: 212.7 26.8% 83.9%;
} }
} }

View File

@ -1,113 +1,12 @@
import Image from "next/image"; import { loadIndexServerOnly } from "@/lib/load-index-server-only";
import { DataTable } from "./data-table";
import { columns } from "./columns";
export default function Home() { export default async function Home() {
const { repos } = await loadIndexServerOnly();
return ( return (
<main className="flex min-h-screen flex-col items-center justify-between p-24"> <section className="py-10">
<div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex"> <DataTable columns={columns} data={repos} />
<p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30"> </section>
Get started by editing&nbsp;
<code className="font-mono font-bold">src/app/page.tsx</code>
</p>
<div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:h-auto lg:w-auto lg:bg-none">
<a
className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
By{" "}
<Image
src="/vercel.svg"
alt="Vercel Logo"
className="dark:invert"
width={100}
height={24}
priority
/>
</a>
</div>
</div>
<div className="relative flex place-items-center before:absolute before:h-[300px] before:w-[480px] before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-[240px] after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700 before:dark:opacity-10 after:dark:from-sky-900 after:dark:via-[#0141ff] after:dark:opacity-40 before:lg:h-[360px] z-[-1]">
<Image
className="relative dark:drop-shadow-[0_0_0.3rem_#ffffff70] dark:invert"
src="/next.svg"
alt="Next.js Logo"
width={180}
height={37}
priority
/>
</div>
<div className="mb-32 grid text-center lg:max-w-5xl lg:w-full lg:mb-0 lg:grid-cols-4 lg:text-left">
<a
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className={`mb-3 text-2xl font-semibold`}>
Docs{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
Find in-depth information about Next.js features and API.
</p>
</a>
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className={`mb-3 text-2xl font-semibold`}>
Learn{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
Learn about Next.js in an interactive course with&nbsp;quizzes!
</p>
</a>
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className={`mb-3 text-2xl font-semibold`}>
Templates{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
Explore the Next.js 13 playground.
</p>
</a>
<a
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className={`mb-3 text-2xl font-semibold`}>
Deploy{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
Instantly deploy your Next.js site to a shareable URL with Vercel.
</p>
</a>
</div>
</main>
); );
} }

View File

@ -0,0 +1,114 @@
import * as React from "react";
import { cn } from "@/lib/utils";
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
</div>
));
Table.displayName = "Table";
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
));
TableHeader.displayName = "TableHeader";
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
));
TableBody.displayName = "TableBody";
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn("bg-primary font-medium text-primary-foreground", className)}
{...props}
/>
));
TableFooter.displayName = "TableFooter";
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className,
)}
{...props}
/>
));
TableRow.displayName = "TableRow";
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
className,
)}
{...props}
/>
));
TableHead.displayName = "TableHead";
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
{...props}
/>
));
TableCell.displayName = "TableCell";
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props}
/>
));
TableCaption.displayName = "TableCaption";
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
};

View File

@ -0,0 +1,30 @@
import { cache } from "react";
import "server-only";
import * as fs from "fs";
import * as path from "path";
import { indexSchema } from "./schemas";
import { ZodError } from "zod";
export const INDEX_FILE_PATH = path.normalize(
path.join(__dirname, "..", "..", "..", "..", "index.json"),
);
export const preload = () => {
void loadIndexServerOnly();
};
export const loadIndexServerOnly = cache(async () => {
try {
const indexData = JSON.parse(
await fs.promises.readFile(INDEX_FILE_PATH, "utf-8"),
);
return await indexSchema.parseAsync(indexData);
} catch (err) {
if (err instanceof ZodError) {
throw new Error(
`Failed to parse the repos index: ${JSON.stringify(err.format())}`,
);
}
throw new Error(`Failed to load the repos index: ${err}`);
}
});

View File

@ -0,0 +1,24 @@
import { z } from "zod";
export const dependencySchema = z.object({
id: z.number().min(0),
name: z.string(),
});
export const repoSchema = z.object({
id: z.number().min(0),
url: z.string(),
description: z.string(),
stars: z.number().min(0),
source_graph_repo_id: z.number().min(0),
dependencies: z.array(dependencySchema),
last_checked_revision: z.nullable(z.string()),
});
export const indexSchema = z.object({
repos: z.array(repoSchema),
});
export type Dependency = z.infer<typeof dependencySchema>;
export type Repo = z.infer<typeof repoSchema>;
export type Index = z.infer<typeof indexSchema>;

File diff suppressed because it is too large Load Diff