Add and fix react hooks lint rules, single repertoire view
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import eslint from "@eslint/js";
|
import eslint from "@eslint/js";
|
||||||
import tseslint from "typescript-eslint";
|
|
||||||
import stylistic from "@stylistic/eslint-plugin";
|
import stylistic from "@stylistic/eslint-plugin";
|
||||||
|
import reactHooks from "eslint-plugin-react-hooks";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
export default tseslint.config({
|
export default tseslint.config({
|
||||||
extends: [
|
extends: [
|
||||||
@@ -10,6 +11,7 @@ export default tseslint.config({
|
|||||||
files: ["packages/*/src/**/*.{ts,tsx}"],
|
files: ["packages/*/src/**/*.{ts,tsx}"],
|
||||||
plugins: {
|
plugins: {
|
||||||
"@stylistic": stylistic,
|
"@stylistic": stylistic,
|
||||||
|
"react-hooks": reactHooks,
|
||||||
},
|
},
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
@@ -43,5 +45,7 @@ export default tseslint.config({
|
|||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
"@typescript-eslint/no-namespace": "off",
|
"@typescript-eslint/no-namespace": "off",
|
||||||
"@typescript-eslint/no-misused-promises": ["error", { checksVoidReturn: false }],
|
"@typescript-eslint/no-misused-promises": ["error", { checksVoidReturn: false }],
|
||||||
|
"react-hooks/exhaustive-deps": "error",
|
||||||
|
"react-hooks/rules-of-hooks": "error",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "catalog:",
|
"@eslint/js": "catalog:",
|
||||||
"@stylistic/eslint-plugin": "catalog:",
|
"@stylistic/eslint-plugin": "catalog:",
|
||||||
|
"eslint-plugin-react-hooks": "catalog:",
|
||||||
"typescript": "catalog:",
|
"typescript": "catalog:",
|
||||||
"typescript-eslint": "catalog:"
|
"typescript-eslint": "catalog:"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ export interface Piece extends SystemInformation {
|
|||||||
readonly attachments: readonly Attachment[];
|
readonly attachments: readonly Attachment[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Repertoire extends SystemInformation {
|
||||||
|
readonly repertoireId: RepertoireId;
|
||||||
|
readonly name: string;
|
||||||
|
readonly entries: readonly Piece[];
|
||||||
|
}
|
||||||
|
|
||||||
interface DbSystemInformation {
|
interface DbSystemInformation {
|
||||||
readonly createdBy: UserId | null;
|
readonly createdBy: UserId | null;
|
||||||
readonly createdAt: string;
|
readonly createdAt: string;
|
||||||
@@ -108,6 +114,7 @@ const RepertoireSemaphore = Effect.unsafeMakeSemaphore(1);
|
|||||||
export const userLookup = (userId: UserId) => pipe(
|
export const userLookup = (userId: UserId) => pipe(
|
||||||
Effect.promise((signal) => client.user({ userId }).get({ fetch: { signal } })),
|
Effect.promise((signal) => client.user({ userId }).get({ fetch: { signal } })),
|
||||||
Effect.flatMap(mapResponse),
|
Effect.flatMap(mapResponse),
|
||||||
|
Effect.map((x): User => x), // safely coerce to interface
|
||||||
UserSemaphore.withPermits(1),
|
UserSemaphore.withPermits(1),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -115,6 +122,7 @@ export const pieceLookup = (pieceId: PieceId) => pipe(
|
|||||||
Effect.promise((signal) => client.piece({ pieceId }).get({ fetch: { signal } })),
|
Effect.promise((signal) => client.piece({ pieceId }).get({ fetch: { signal } })),
|
||||||
Effect.flatMap(mapResponse),
|
Effect.flatMap(mapResponse),
|
||||||
Effect.flatMap(denormalizePiece),
|
Effect.flatMap(denormalizePiece),
|
||||||
|
Effect.map((x): Piece => x), // safely coerce to interface
|
||||||
CacheSemaphore.withPermits(1),
|
CacheSemaphore.withPermits(1),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -122,6 +130,7 @@ export const repertoireLookup = (repertoireId: RepertoireId) => pipe(
|
|||||||
Effect.promise((signal) => client.repertoire({ repertoireId }).get({ fetch: { signal } })),
|
Effect.promise((signal) => client.repertoire({ repertoireId }).get({ fetch: { signal } })),
|
||||||
Effect.flatMap(mapResponse),
|
Effect.flatMap(mapResponse),
|
||||||
Effect.flatMap(denormalizeRepertoire),
|
Effect.flatMap(denormalizeRepertoire),
|
||||||
|
Effect.map((x): Repertoire => x), // safely coerce to interface
|
||||||
RepertoireSemaphore.withPermits(1),
|
RepertoireSemaphore.withPermits(1),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,8 @@ export function useLoading<R extends Record<number, unknown>>(fn: () => Promise<
|
|||||||
});
|
});
|
||||||
|
|
||||||
return () => { cancelled = true; };
|
return () => { cancelled = true; };
|
||||||
}, deps);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [fn, navigate, setUser, ...deps]);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -125,7 +126,8 @@ export function useLoadingEffect<A, E>(effect: Effect.Effect<A, E>, deps: React.
|
|||||||
return () => {
|
return () => {
|
||||||
Effect.runFork(interruptEffect);
|
Effect.runFork(interruptEffect);
|
||||||
};
|
};
|
||||||
}, deps);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [effect, setResultEffect, ...deps]);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export function useStore<T = Store>(selector: Selector<T> = Function.identity as
|
|||||||
return () => {
|
return () => {
|
||||||
listeners.delete(listener);
|
listeners.delete(listener);
|
||||||
};
|
};
|
||||||
}, []);
|
}, [selector]);
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export default function Attachment() {
|
|||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
window.removeEventListener("resize", render);
|
window.removeEventListener("resize", render);
|
||||||
};
|
};
|
||||||
}, [isLoading, data]);
|
}, [data, error, isLoading]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export function Piece() {
|
|||||||
const setAttachments = useCallback((action: Update<readonly Attachment[]>) => {
|
const setAttachments = useCallback((action: Update<readonly Attachment[]>) => {
|
||||||
setData!(mapProp("attachments", action));
|
setData!(mapProp("attachments", action));
|
||||||
Effect.runFork(pieceCache.invalidate(id));
|
Effect.runFork(pieceCache.invalidate(id));
|
||||||
}, [setData]);
|
}, [id, setData]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
@@ -236,7 +236,7 @@ function AttachmentRow(props: AttachmentRow.Props) {
|
|||||||
const url = URL.createObjectURL(data);
|
const url = URL.createObjectURL(data);
|
||||||
window.open(url, "_target");
|
window.open(url, "_target");
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
}, [props.attachment.mediaType, props.attachment.attachmentId, props.attachment.pieceId]);
|
}, [props.attachment.attachmentId, props.attachment.mediaType, props.attachment.pieceId]);
|
||||||
|
|
||||||
const doDelete = useCallback(async () => {
|
const doDelete = useCallback(async () => {
|
||||||
|
|
||||||
@@ -252,7 +252,7 @@ function AttachmentRow(props: AttachmentRow.Props) {
|
|||||||
|
|
||||||
props.setAttachments((prev) => prev.filter((a) => a.attachmentId !== props.attachment.attachmentId));
|
props.setAttachments((prev) => prev.filter((a) => a.attachmentId !== props.attachment.attachmentId));
|
||||||
|
|
||||||
}, [props.attachment.attachmentId, props.attachment.pieceId]);
|
}, [props]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
import { Piece, repertoireCache } from "@/cache";
|
import { Piece, type Repertoire, repertoireCache } from "@/cache";
|
||||||
|
import { client } from "@/client";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||||
import { useLoadingEffect } from "@/hooks/useLoading";
|
import { useLoadingEffect } from "@/hooks/useLoading";
|
||||||
import { mapProp, Update } from "@/hooks/useStore";
|
import { mapProp, Update, Updater } from "@/hooks/useStore";
|
||||||
import { RepertoireId } from "common";
|
import { timeout } from "@/lib/utils";
|
||||||
import { Cause, Effect } from "effect";
|
import { PieceId, RepertoireId } from "common";
|
||||||
import { useCallback } from "react";
|
import { Array, Cause, Effect, Option, pipe } from "effect";
|
||||||
import { useParams } from "react-router-dom";
|
import { CircleMinus, Loader2 } from "lucide-react";
|
||||||
|
import { FormEventHandler, ReactNode, useCallback, useId, useState } from "react";
|
||||||
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
|
||||||
export function Repertoire() {
|
export function Repertoire() {
|
||||||
|
|
||||||
@@ -15,7 +22,7 @@ export function Repertoire() {
|
|||||||
const setEntries = useCallback((action: Update<readonly Piece[]>) => {
|
const setEntries = useCallback((action: Update<readonly Piece[]>) => {
|
||||||
setData!(mapProp("entries", action));
|
setData!(mapProp("entries", action));
|
||||||
Effect.runFork(repertoireCache.invalidate(id));
|
Effect.runFork(repertoireCache.invalidate(id));
|
||||||
}, [setData]);
|
}, [id, setData]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
@@ -34,8 +41,188 @@ export function Repertoire() {
|
|||||||
<h3 className="font-bold">Repertuar</h3>
|
<h3 className="font-bold">Repertuar</h3>
|
||||||
<RepertoireForm repertoire={data} />
|
<RepertoireForm repertoire={data} />
|
||||||
</div>
|
</div>
|
||||||
<Entries repertoireId={id} entries={data.entries} setData={setData} />
|
<Entries repertoire={data} setEntries={setEntries} />
|
||||||
</>)}
|
</>)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace RepertoireForm {
|
||||||
|
export interface Props {
|
||||||
|
readonly repertoire: Repertoire;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function RepertoireForm(props: RepertoireForm.Props) {
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [name, setName] = useState(props.repertoire.name);
|
||||||
|
|
||||||
|
const nameId = useId();
|
||||||
|
|
||||||
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
|
|
||||||
|
const onSubmit: FormEventHandler<HTMLFormElement> = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const delay = timeout(250);
|
||||||
|
try {
|
||||||
|
setIsSaving(true);
|
||||||
|
|
||||||
|
const { error } = await client.repertoire({ repertoireId: props.repertoire.repertoireId }).put({
|
||||||
|
name,
|
||||||
|
entries: props.repertoire.entries.map(({ pieceId }) => pieceId),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error !== null) {
|
||||||
|
console.error(error.value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await delay;
|
||||||
|
setIsSaving(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const doDelete = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setIsDeleting(true);
|
||||||
|
|
||||||
|
const { error } = await client
|
||||||
|
.repertoire({ repertoireId: props.repertoire.repertoireId })
|
||||||
|
.delete();
|
||||||
|
|
||||||
|
if (error !== null) {
|
||||||
|
console.error(error.value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Effect.runFork(repertoireCache.invalidate(props.repertoire.repertoireId));
|
||||||
|
navigate("..");
|
||||||
|
} finally {
|
||||||
|
setIsDeleting(false);
|
||||||
|
}
|
||||||
|
}, [props.repertoire.repertoireId, navigate]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className="flex flex-col gap-4" onSubmit={onSubmit}>
|
||||||
|
<div className="grid items-baseline grid-cols-4 gap-4">
|
||||||
|
<Label htmlFor={nameId} className="text-right">Tytuł</Label>
|
||||||
|
<Input
|
||||||
|
id={nameId}
|
||||||
|
className="col-span-3"
|
||||||
|
type="text"
|
||||||
|
value={name}
|
||||||
|
required
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<Button type="button" variant="destructive" disabled={isDeleting} onClick={doDelete}>
|
||||||
|
{isDeleting && <Loader2 className="animate-spin" />}
|
||||||
|
Usuń
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" disabled={isSaving}>
|
||||||
|
{isSaving && <Loader2 className="animate-spin" />}
|
||||||
|
Zapisz
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Entries {
|
||||||
|
export interface Props {
|
||||||
|
readonly repertoire: Repertoire;
|
||||||
|
readonly setEntries: Updater<readonly Piece[]>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Entries(props: Entries.Props) {
|
||||||
|
return (
|
||||||
|
<div className="grow">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Lp.</TableHead>
|
||||||
|
<TableHead>Tytuł</TableHead>
|
||||||
|
<TableHead>Twórcy</TableHead>
|
||||||
|
<TableHead className="text-center">Akcje</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{props.repertoire.entries.map((entry, i) => (
|
||||||
|
<EntryRow
|
||||||
|
key={entry.pieceId}
|
||||||
|
repertoire={props.repertoire}
|
||||||
|
piece={entry}
|
||||||
|
no={i + 1}
|
||||||
|
setEntries={props.setEntries}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace EntryRow {
|
||||||
|
export interface Props {
|
||||||
|
readonly repertoire: Repertoire;
|
||||||
|
readonly piece: Piece;
|
||||||
|
readonly no: number;
|
||||||
|
readonly setEntries: Updater<readonly Piece[]>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function EntryRow(props: EntryRow.Props) {
|
||||||
|
|
||||||
|
const remove = useCallback(async () => {
|
||||||
|
|
||||||
|
const filter = Array.filter<Piece>((piece) => piece.pieceId !== props.piece.pieceId);
|
||||||
|
const mapToId = Array.map<readonly Piece[], PieceId>(({ pieceId }) => pieceId);
|
||||||
|
|
||||||
|
const { error } = await client
|
||||||
|
.repertoire({ repertoireId: props.repertoire.repertoireId })
|
||||||
|
.put({
|
||||||
|
name: props.repertoire.name,
|
||||||
|
entries: pipe(props.repertoire.entries, filter, mapToId),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error !== null) {
|
||||||
|
console.error(error.value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
props.setEntries(filter);
|
||||||
|
|
||||||
|
}, [props]);
|
||||||
|
|
||||||
|
const composerParts: ReactNode[] = [];
|
||||||
|
|
||||||
|
if (Option.isSome(props.piece.composer)) composerParts.push(props.piece.composer.value);
|
||||||
|
if (Option.isSome(props.piece.arranger)) composerParts.push(`opracowanie: ${props.piece.arranger.value}`);
|
||||||
|
if (Option.isSome(props.piece.lyricist)) composerParts.push(`słowa: ${props.piece.lyricist.value}`);
|
||||||
|
if (composerParts.length === 0) composerParts.push(<em>Nieznani</em>);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
{props.no}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{props.piece.name}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{...composerParts.flatMap((x, i, a) => i < a.length - 1 ? [x, <br />] : [x])}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-center flex justify-center gap-4">
|
||||||
|
<Button type="button" variant="ghost" size="icon" title="Usuń" onClick={remove}>
|
||||||
|
<CircleMinus />
|
||||||
|
</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export function Root() {
|
|||||||
setUser(data);
|
setUser(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
useEffect(() => void init(), []);
|
useEffect(() => void init(), []);
|
||||||
|
|
||||||
const onLogoutClick = async () => {
|
const onLogoutClick = async () => {
|
||||||
|
|||||||
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
@@ -63,6 +63,9 @@ catalogs:
|
|||||||
elysia:
|
elysia:
|
||||||
specifier: ^1.1.25
|
specifier: ^1.1.25
|
||||||
version: 1.1.25
|
version: 1.1.25
|
||||||
|
eslint-plugin-react-hooks:
|
||||||
|
specifier: ^5.1.0
|
||||||
|
version: 5.1.0
|
||||||
kysely:
|
kysely:
|
||||||
specifier: ^0.27.4
|
specifier: ^0.27.4
|
||||||
version: 0.27.4
|
version: 0.27.4
|
||||||
@@ -116,6 +119,9 @@ importers:
|
|||||||
'@stylistic/eslint-plugin':
|
'@stylistic/eslint-plugin':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 2.12.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.7.2)
|
version: 2.12.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.7.2)
|
||||||
|
eslint-plugin-react-hooks:
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 5.1.0(eslint@9.17.0(jiti@1.21.6))
|
||||||
typescript:
|
typescript:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 5.7.2
|
version: 5.7.2
|
||||||
@@ -1422,6 +1428,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
|
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
eslint-plugin-react-hooks@5.1.0:
|
||||||
|
resolution: {integrity: sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
peerDependencies:
|
||||||
|
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
|
||||||
|
|
||||||
eslint-scope@8.2.0:
|
eslint-scope@8.2.0:
|
||||||
resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==}
|
resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@@ -3695,6 +3707,10 @@ snapshots:
|
|||||||
|
|
||||||
escape-string-regexp@4.0.0: {}
|
escape-string-regexp@4.0.0: {}
|
||||||
|
|
||||||
|
eslint-plugin-react-hooks@5.1.0(eslint@9.17.0(jiti@1.21.6)):
|
||||||
|
dependencies:
|
||||||
|
eslint: 9.17.0(jiti@1.21.6)
|
||||||
|
|
||||||
eslint-scope@8.2.0:
|
eslint-scope@8.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
esrecurse: 4.3.0
|
esrecurse: 4.3.0
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ catalog:
|
|||||||
clsx: '^2.1.1'
|
clsx: '^2.1.1'
|
||||||
effect: '^3.11.4'
|
effect: '^3.11.4'
|
||||||
elysia: '^1.1.25'
|
elysia: '^1.1.25'
|
||||||
|
eslint-plugin-react-hooks: '^5.1.0'
|
||||||
kysely: '^0.27.4'
|
kysely: '^0.27.4'
|
||||||
kysely-bun-sqlite: '^0.3.2'
|
kysely-bun-sqlite: '^0.3.2'
|
||||||
lucide-react: '^0.462.0'
|
lucide-react: '^0.462.0'
|
||||||
|
|||||||
Reference in New Issue
Block a user