This commit is contained in:
parent
ffa332af7b
commit
893fd8b0ba
14 changed files with 409 additions and 228 deletions
|
|
@ -2,7 +2,6 @@
|
||||||
inputs:
|
inputs:
|
||||||
nixpkgs:
|
nixpkgs:
|
||||||
url: github:cachix/devenv-nixpkgs/rolling
|
url: github:cachix/devenv-nixpkgs/rolling
|
||||||
|
|
||||||
# If you're using non-OSS software, you can set allowUnfree to true.
|
# If you're using non-OSS software, you can set allowUnfree to true.
|
||||||
# allowUnfree: true
|
# allowUnfree: true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,28 @@
|
||||||
import js from '@eslint/js'
|
import js from "@eslint/js";
|
||||||
import globals from 'globals'
|
import globals from "globals";
|
||||||
import reactHooks from 'eslint-plugin-react-hooks'
|
import reactHooks from "eslint-plugin-react-hooks";
|
||||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
import reactRefresh from "eslint-plugin-react-refresh";
|
||||||
import tseslint from 'typescript-eslint'
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
export default tseslint.config(
|
export default tseslint.config(
|
||||||
{ ignores: ['dist'] },
|
{ ignores: ["dist"] },
|
||||||
{
|
{
|
||||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||||
files: ['**/*.{ts,tsx}'],
|
files: ["**/*.{ts,tsx}"],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
ecmaVersion: 2020,
|
ecmaVersion: 2020,
|
||||||
globals: globals.browser,
|
globals: globals.browser,
|
||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
'react-hooks': reactHooks,
|
"react-hooks": reactHooks,
|
||||||
'react-refresh': reactRefresh,
|
"react-refresh": reactRefresh,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
...reactHooks.configs.recommended.rules,
|
...reactHooks.configs.recommended.rules,
|
||||||
'react-refresh/only-export-components': [
|
"react-refresh/only-export-components": [
|
||||||
'warn',
|
"warn",
|
||||||
{ allowConstantExport: true },
|
{ allowConstantExport: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,11 @@
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
|
"prettier": {
|
||||||
|
"plugins": [
|
||||||
|
"prettier-plugin-tailwindcss"
|
||||||
|
]
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.0.6",
|
"@tailwindcss/vite": "^4.0.6",
|
||||||
"pixelarticons": "^1.8.1",
|
"pixelarticons": "^1.8.1",
|
||||||
|
|
@ -28,6 +33,8 @@
|
||||||
"eslint-plugin-react-hooks": "^5.0.0",
|
"eslint-plugin-react-hooks": "^5.0.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.18",
|
"eslint-plugin-react-refresh": "^0.4.18",
|
||||||
"globals": "^15.14.0",
|
"globals": "^15.14.0",
|
||||||
|
"prettier": "^3.5.3",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
"typescript": "~5.7.2",
|
"typescript": "~5.7.2",
|
||||||
"typescript-eslint": "^8.22.0",
|
"typescript-eslint": "^8.22.0",
|
||||||
"vite": "^6.1.0",
|
"vite": "^6.1.0",
|
||||||
|
|
|
||||||
72
pnpm-lock.yaml
generated
72
pnpm-lock.yaml
generated
|
|
@ -57,6 +57,12 @@ importers:
|
||||||
globals:
|
globals:
|
||||||
specifier: ^15.14.0
|
specifier: ^15.14.0
|
||||||
version: 15.15.0
|
version: 15.15.0
|
||||||
|
prettier:
|
||||||
|
specifier: ^3.5.3
|
||||||
|
version: 3.5.3
|
||||||
|
prettier-plugin-tailwindcss:
|
||||||
|
specifier: ^0.6.11
|
||||||
|
version: 0.6.11(prettier@3.5.3)
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ~5.7.2
|
specifier: ~5.7.2
|
||||||
version: 5.7.3
|
version: 5.7.3
|
||||||
|
|
@ -1273,6 +1279,66 @@ packages:
|
||||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
|
prettier-plugin-tailwindcss@0.6.11:
|
||||||
|
resolution: {integrity: sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==}
|
||||||
|
engines: {node: '>=14.21.3'}
|
||||||
|
peerDependencies:
|
||||||
|
'@ianvs/prettier-plugin-sort-imports': '*'
|
||||||
|
'@prettier/plugin-pug': '*'
|
||||||
|
'@shopify/prettier-plugin-liquid': '*'
|
||||||
|
'@trivago/prettier-plugin-sort-imports': '*'
|
||||||
|
'@zackad/prettier-plugin-twig': '*'
|
||||||
|
prettier: ^3.0
|
||||||
|
prettier-plugin-astro: '*'
|
||||||
|
prettier-plugin-css-order: '*'
|
||||||
|
prettier-plugin-import-sort: '*'
|
||||||
|
prettier-plugin-jsdoc: '*'
|
||||||
|
prettier-plugin-marko: '*'
|
||||||
|
prettier-plugin-multiline-arrays: '*'
|
||||||
|
prettier-plugin-organize-attributes: '*'
|
||||||
|
prettier-plugin-organize-imports: '*'
|
||||||
|
prettier-plugin-sort-imports: '*'
|
||||||
|
prettier-plugin-style-order: '*'
|
||||||
|
prettier-plugin-svelte: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@ianvs/prettier-plugin-sort-imports':
|
||||||
|
optional: true
|
||||||
|
'@prettier/plugin-pug':
|
||||||
|
optional: true
|
||||||
|
'@shopify/prettier-plugin-liquid':
|
||||||
|
optional: true
|
||||||
|
'@trivago/prettier-plugin-sort-imports':
|
||||||
|
optional: true
|
||||||
|
'@zackad/prettier-plugin-twig':
|
||||||
|
optional: true
|
||||||
|
prettier-plugin-astro:
|
||||||
|
optional: true
|
||||||
|
prettier-plugin-css-order:
|
||||||
|
optional: true
|
||||||
|
prettier-plugin-import-sort:
|
||||||
|
optional: true
|
||||||
|
prettier-plugin-jsdoc:
|
||||||
|
optional: true
|
||||||
|
prettier-plugin-marko:
|
||||||
|
optional: true
|
||||||
|
prettier-plugin-multiline-arrays:
|
||||||
|
optional: true
|
||||||
|
prettier-plugin-organize-attributes:
|
||||||
|
optional: true
|
||||||
|
prettier-plugin-organize-imports:
|
||||||
|
optional: true
|
||||||
|
prettier-plugin-sort-imports:
|
||||||
|
optional: true
|
||||||
|
prettier-plugin-style-order:
|
||||||
|
optional: true
|
||||||
|
prettier-plugin-svelte:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
prettier@3.5.3:
|
||||||
|
resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
punycode@2.3.1:
|
punycode@2.3.1:
|
||||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
@ -2556,6 +2622,12 @@ snapshots:
|
||||||
|
|
||||||
prelude-ls@1.2.1: {}
|
prelude-ls@1.2.1: {}
|
||||||
|
|
||||||
|
prettier-plugin-tailwindcss@0.6.11(prettier@3.5.3):
|
||||||
|
dependencies:
|
||||||
|
prettier: 3.5.3
|
||||||
|
|
||||||
|
prettier@3.5.3: {}
|
||||||
|
|
||||||
punycode@2.3.1: {}
|
punycode@2.3.1: {}
|
||||||
|
|
||||||
queue-microtask@1.2.3: {}
|
queue-microtask@1.2.3: {}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import QuoteList from "./QuoteList";
|
import QuoteList from "./QuoteList";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return <QuoteList />
|
return <QuoteList />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
121
src/QuoteAdd.tsx
121
src/QuoteAdd.tsx
|
|
@ -1,63 +1,92 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Collections, IsoDateString, TypedPocketBase } from "./pocketbase-types";
|
import {
|
||||||
import PocketBase from 'pocketbase'
|
Collections,
|
||||||
|
IsoDateString,
|
||||||
|
TypedPocketBase,
|
||||||
|
} from "./pocketbase-types";
|
||||||
|
import PocketBase from "pocketbase";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import { useForm, SubmitHandler } from "react-hook-form"
|
import { useForm, SubmitHandler } from "react-hook-form";
|
||||||
interface QuoteAddProps {
|
interface QuoteAddProps {
|
||||||
quote: string;
|
quote: string;
|
||||||
context?: string;
|
context?: string;
|
||||||
collection: Collections
|
collection: Collections;
|
||||||
author: string;
|
author: string;
|
||||||
date: IsoDateString;
|
date: IsoDateString;
|
||||||
|
|
||||||
}
|
}
|
||||||
export default function QuoteAdd() {
|
export default function QuoteAdd() {
|
||||||
|
const pb = new PocketBase("https://api.m3.fyi") as TypedPocketBase;
|
||||||
const pb = new PocketBase("https://api.m3.fyi") as TypedPocketBase
|
const [date, setDate] = useState("");
|
||||||
const [date, setDate] = useState("")
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const d = new Date();
|
const d = new Date();
|
||||||
d.setTime(d.getTime() + 60 * 60 * 1000)
|
d.setTime(d.getTime() + 60 * 60 * 1000);
|
||||||
const formattedDate = d.toISOString().slice(0, 16)
|
const formattedDate = d.toISOString().slice(0, 16);
|
||||||
setDate(formattedDate)
|
setDate(formattedDate);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { register, handleSubmit, watch, formState: { errors }, } = useForm<QuoteAddProps>()
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
watch,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<QuoteAddProps>();
|
||||||
|
|
||||||
const onSubmit: SubmitHandler<QuoteAddProps> = async (data) => {
|
const onSubmit: SubmitHandler<QuoteAddProps> = async (data) => {
|
||||||
console.log(data)
|
console.log(data);
|
||||||
console.log(date)
|
console.log(date);
|
||||||
let formDate = new Date(date)
|
let formDate = new Date(date);
|
||||||
formDate.setTime(formDate.getTime() + 60 * 60 * 1000)
|
formDate.setTime(formDate.getTime() + 60 * 60 * 1000);
|
||||||
|
|
||||||
console.log(date, formDate)
|
console.log(date, formDate);
|
||||||
let quote = {
|
let quote = {
|
||||||
"quote": data.quote,
|
quote: data.quote,
|
||||||
"context": data.context || "",
|
context: data.context || "",
|
||||||
"author": data.author,
|
author: data.author,
|
||||||
"date": formDate.toISOString().substring(0, 19) + "Z"
|
date: formDate.toISOString().substring(0, 19) + "Z",
|
||||||
}
|
};
|
||||||
console.log(quote)
|
console.log(quote);
|
||||||
await pb.collection(data.collection).create(quote)
|
await pb.collection(data.collection).create(quote);
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
navigate("/")
|
navigate("/");
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className='border-2 bg-slate-800 ml-2 mb-5 p-2 border-lime-500 shadow-(--box) flex flex-col' onSubmit={handleSubmit(onSubmit)}>
|
<form
|
||||||
<textarea required className="grow border-4 border-black bg-slate-300 text-black p-1 my-1 cursor-text" placeholder="Quote" {...register("quote", { required: true })} />
|
className="mb-5 ml-2 flex flex-col border-2 border-lime-500 bg-slate-800 p-2 shadow-(--box)"
|
||||||
<textarea className="border-4 border-black bg-slate-300 text-slate-600 p-1 my-1 cursor-text" placeholder="Context" {...register("context")} />
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
required
|
||||||
|
className="my-1 grow cursor-text border-4 border-black bg-slate-300 p-1 text-black"
|
||||||
|
placeholder="Quote"
|
||||||
|
{...register("quote", { required: true })}
|
||||||
|
/>
|
||||||
|
<textarea
|
||||||
|
className="my-1 cursor-text border-4 border-black bg-slate-300 p-1 text-slate-600"
|
||||||
|
placeholder="Context"
|
||||||
|
{...register("context")}
|
||||||
|
/>
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<p className="bg-lime-400 text-zinc-800 p-1 border-4 border-r-2 border-black w-20 cursor-text">Author:</p>
|
<p className="w-20 cursor-text border-4 border-r-2 border-black bg-lime-400 p-1 text-zinc-800">
|
||||||
<input required className="bg-slate-300 border-4 border-l-2 border-black text-black p-1 grow cursor-text {}" type="text" placeholder="Author" {...register("author", { required: true })} aria-invalid={errors.author ? "true" : "false"} />
|
Author:
|
||||||
|
</p>
|
||||||
|
<input
|
||||||
|
required
|
||||||
|
className="{} grow cursor-text border-4 border-l-2 border-black bg-slate-300 p-1 text-black"
|
||||||
|
type="text"
|
||||||
|
placeholder="Author"
|
||||||
|
{...register("author", { required: true })}
|
||||||
|
aria-invalid={errors.author ? "true" : "false"}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<p className="bg-lime-400 text-zinc-800 p-1 border-4 border-r-2 border-black w-20 cursor-text">Source:</p>
|
<p className="w-20 cursor-text border-4 border-r-2 border-black bg-lime-400 p-1 text-zinc-800">
|
||||||
|
Source:
|
||||||
|
</p>
|
||||||
<select
|
<select
|
||||||
title='Select quote collection'
|
title="Select quote collection"
|
||||||
required
|
required
|
||||||
className='bg-slate-300 border-4 border-l-2 border-black text-black p-1 grow cursor-pointer'
|
className="grow cursor-pointer border-4 border-l-2 border-black bg-slate-300 p-1 text-black"
|
||||||
{...register("collection", { required: true })}
|
{...register("collection", { required: true })}
|
||||||
>
|
>
|
||||||
<option value="qt_bit">Arcus Notsus</option>
|
<option value="qt_bit">Arcus Notsus</option>
|
||||||
|
|
@ -66,13 +95,27 @@ export default function QuoteAdd() {
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<p className="bg-lime-400 text-zinc-800 p-1 border-4 border-r-2 border-black w-20 cursor-text">Date:</p>
|
<p className="w-20 cursor-text border-4 border-r-2 border-black bg-lime-400 p-1 text-zinc-800">
|
||||||
<input value={date} onChange={(e) => setDate(e.target.value)} required name="date" className="bg-slate-300 border-4 border-l-2 border-black text-black p-1 grow cursor-text" placeholder="Date" type="datetime-local"
|
Date:
|
||||||
|
</p>
|
||||||
|
<input
|
||||||
|
value={date}
|
||||||
|
onChange={(e) => setDate(e.target.value)}
|
||||||
|
required
|
||||||
|
name="date"
|
||||||
|
className="grow cursor-text border-4 border-l-2 border-black bg-slate-300 p-1 text-black"
|
||||||
|
placeholder="Date"
|
||||||
|
type="datetime-local"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row justify-center">
|
<div className="flex flex-row justify-center">
|
||||||
<button type="submit" className="bg-slate-300 active:bg-lime-400 text-zinc-800 p-1 border-4 border-r-2 border-black w-40 cursor-pointer">Add Quote</button>
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-40 cursor-pointer border-4 border-r-2 border-black bg-slate-300 p-1 text-zinc-800 active:bg-lime-400"
|
||||||
|
>
|
||||||
|
Add Quote
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,46 @@
|
||||||
import { useParams } from "react-router"
|
import { useParams } from "react-router";
|
||||||
import PocketBase from 'pocketbase'
|
import PocketBase from "pocketbase";
|
||||||
import { TypedPocketBase } from "./pocketbase-types"
|
import { TypedPocketBase } from "./pocketbase-types";
|
||||||
import { Quote } from "./pocketbase-types"
|
import { Quote } from "./pocketbase-types";
|
||||||
import QuoteInfo from "./QuoteInfo"
|
import QuoteInfo from "./QuoteInfo";
|
||||||
import { useState, useEffect } from "react"
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
|
||||||
export default function QuoteDetail() {
|
export default function QuoteDetail() {
|
||||||
let params = useParams()
|
let params = useParams();
|
||||||
const pb = new PocketBase("https://api.m3.fyi") as TypedPocketBase
|
const pb = new PocketBase("https://api.m3.fyi") as TypedPocketBase;
|
||||||
|
|
||||||
const [quote, setQuote] = useState<Quote | null>(null)
|
const [quote, setQuote] = useState<Quote | null>(null);
|
||||||
const [loading, setLoading] = useState<boolean>(true)
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
const [error, setError] = useState<Error | null>(null)
|
const [error, setError] = useState<Error | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getQuote = async () => {
|
const getQuote = async () => {
|
||||||
try {
|
try {
|
||||||
const record = await pb.collection(params.collection as string).getOne<Quote>(params.quoteId as string, { requestKey: null });
|
const record = await pb
|
||||||
|
.collection(params.collection as string)
|
||||||
|
.getOne<Quote>(params.quoteId as string, { requestKey: null });
|
||||||
setQuote(record);
|
setQuote(record);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err)
|
setError(err);
|
||||||
console.error("Error:", err)
|
console.error("Error:", err);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
getQuote()
|
getQuote();
|
||||||
}, [])
|
}, []);
|
||||||
// return <p>Loading...</p>
|
// return <p>Loading...</p>
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <p>Loading...</p>
|
return <p>Loading...</p>;
|
||||||
}
|
}
|
||||||
if (error) {
|
if (error) {
|
||||||
return <p>Error: {error.message}</p>
|
return <p>Error: {error.message}</p>;
|
||||||
}
|
}
|
||||||
return quote ? <div className="flex flex-col items-center grow"><QuoteInfo quote={quote} /></div> : <p>No quote found.</p>
|
return quote ? (
|
||||||
|
<div className="flex grow flex-col items-center">
|
||||||
|
<QuoteInfo quote={quote} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p>No quote found.</p>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,61 @@
|
||||||
import { NavLink } from "react-router";
|
import { NavLink } from "react-router";
|
||||||
import { Quote } from "./pocketbase-types"
|
import { Quote } from "./pocketbase-types";
|
||||||
|
|
||||||
import ClipboardIcon from "pixelarticons/svg/clipboard.svg?react"
|
import ClipboardIcon from "pixelarticons/svg/clipboard.svg?react";
|
||||||
import LinkIcon from "pixelarticons/svg/link.svg?react"
|
import LinkIcon from "pixelarticons/svg/link.svg?react";
|
||||||
interface QuoteInfoProps {
|
interface QuoteInfoProps {
|
||||||
quote: Quote;
|
quote: Quote;
|
||||||
}
|
}
|
||||||
export default function QuoteInfo({ quote }: QuoteInfoProps) {
|
export default function QuoteInfo({ quote }: QuoteInfoProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
//TODO: Bettes Colors
|
//TODO: Bettes Colors
|
||||||
<div className="dark:bg-slate-800 shadow-(--box) m-1 p-2 cursor-pointer" >
|
<div className="m-1 cursor-pointer p-2 shadow-(--box) dark:bg-slate-800">
|
||||||
<div className="flex-row flex">
|
<div className="flex flex-row">
|
||||||
<p className="grow border-4 border-black bg-slate-300 text-black p-1 my-1 cursor-text">{quote.quote}</p>
|
<p className="my-1 grow cursor-text border-4 border-black bg-slate-300 p-1 text-black">
|
||||||
|
{quote.quote}
|
||||||
|
</p>
|
||||||
{/*TODO: Let items grow vertically */}
|
{/*TODO: Let items grow vertically */}
|
||||||
<div className="ml-auto flex-row flex items-center">
|
<div className="ml-auto flex flex-row items-center">
|
||||||
<p className="border-4 border-black border-x-2 bg-slate-300 text-black p-1 my-1 cursor-pointer active:text-lime-500" title="Copy quote" onClick={() => { navigator.clipboard.writeText(quote.quote) }}><ClipboardIcon className="w-5" /></p>
|
<p
|
||||||
<NavLink className=" border-4 border-l-2 border-black bg-slate-300 text-black p-1 my-1 cursor-pointer active:text-lime-500" to={`/${quote.collectionName}/${quote.id}`}>
|
className="my-1 cursor-pointer border-4 border-x-2 border-black bg-slate-300 p-1 text-black active:text-lime-500"
|
||||||
<p className=""><LinkIcon className="w-5" /></p>
|
title="Copy quote"
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(quote.quote);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ClipboardIcon className="w-5" />
|
||||||
|
</p>
|
||||||
|
<NavLink
|
||||||
|
className="my-1 cursor-pointer border-4 border-l-2 border-black bg-slate-300 p-1 text-black active:text-lime-500"
|
||||||
|
to={`/${quote.collectionName}/${quote.id}`}
|
||||||
|
>
|
||||||
|
<p className="">
|
||||||
|
<LinkIcon className="w-5" />
|
||||||
|
</p>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{quote.context && <p className="border-4 border-black bg-slate-300 text-slate-600 p-1 my-1 cursor-text">{quote.context}</p>}
|
{quote.context && (
|
||||||
|
<p className="my-1 cursor-text border-4 border-black bg-slate-300 p-1 text-slate-600">
|
||||||
|
{quote.context}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<p className="bg-lime-400 text-zinc-800 p-1 border-4 border-r-2 border-black w-20 cursor-text">Author:</p>
|
<p className="w-20 cursor-text border-4 border-r-2 border-black bg-lime-400 p-1 text-zinc-800">
|
||||||
<p className="bg-slate-300 border-4 border-l-2 border-black text-black p-1 grow cursor-text">{quote.author}</p>
|
Author:
|
||||||
|
</p>
|
||||||
|
<p className="grow cursor-text border-4 border-l-2 border-black bg-slate-300 p-1 text-black">
|
||||||
|
{quote.author}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<p className="bg-lime-400 text-zinc-800 p-1 border-4 border-r-2 border-black w-20 cursor-text">Date:</p>
|
<p className="w-20 cursor-text border-4 border-r-2 border-black bg-lime-400 p-1 text-zinc-800">
|
||||||
<p className="bg-slate-300 border-4 border-l-2 border-black text-black p-1 grow cursor-text">{quote.date}</p>
|
Date:
|
||||||
|
</p>
|
||||||
|
<p className="grow cursor-text border-4 border-l-2 border-black bg-slate-300 p-1 text-black">
|
||||||
|
{quote.date}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,123 +1,145 @@
|
||||||
import PocketBase from 'pocketbase'
|
import PocketBase from "pocketbase";
|
||||||
import { ChangeEvent, useEffect, useState } from 'react'
|
import { ChangeEvent, useEffect, useState } from "react";
|
||||||
import QuoteInfo from './QuoteInfo.tsx'
|
import QuoteInfo from "./QuoteInfo.tsx";
|
||||||
import { Collections, Quote, TypedPocketBase } from './pocketbase-types.ts'
|
import { Collections, Quote, TypedPocketBase } from "./pocketbase-types.ts";
|
||||||
import AddIcon from "pixelarticons/svg/note-plus.svg?react"
|
import AddIcon from "pixelarticons/svg/note-plus.svg?react";
|
||||||
import { NavLink } from 'react-router'
|
import { NavLink } from "react-router";
|
||||||
//TODO: Add Comments
|
//TODO: Add Comments
|
||||||
|
|
||||||
export default function QuoteList() {
|
export default function QuoteList() {
|
||||||
|
|
||||||
// const { quotes, loading, error } = getAllQuotes(collection)
|
// const { quotes, loading, error } = getAllQuotes(collection)
|
||||||
const pb = new PocketBase("https://api.m3.fyi") as TypedPocketBase
|
const pb = new PocketBase("https://api.m3.fyi") as TypedPocketBase;
|
||||||
|
|
||||||
const [quotes, setQuotes] = useState<Quote[] | null>(null)
|
const [quotes, setQuotes] = useState<Quote[] | null>(null);
|
||||||
const [filteredQuotes, setFilteredQuotes] = useState<Quote[] | null>(null)
|
const [filteredQuotes, setFilteredQuotes] = useState<Quote[] | null>(null);
|
||||||
const [loading, setLoading] = useState<boolean>(true)
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
const [error, setError] = useState<Error | null>(null)
|
const [error, setError] = useState<Error | null>(null);
|
||||||
const [collection, setCollection] = useState<Collections | null>(Collections.QtBit)
|
const [collection, setCollection] = useState<Collections | null>(
|
||||||
const [searchItem, setSearchItem] = useState<string>("")
|
Collections.QtBit,
|
||||||
const [searchFilter, setSearchFilter] = useState<string | null>("quote")
|
);
|
||||||
|
const [searchItem, setSearchItem] = useState<string>("");
|
||||||
|
const [searchFilter, setSearchFilter] = useState<string | null>("quote");
|
||||||
|
|
||||||
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const searchTerm = e.target.value;
|
const searchTerm = e.target.value;
|
||||||
setSearchItem(searchTerm)
|
setSearchItem(searchTerm);
|
||||||
let filteredItems;
|
let filteredItems;
|
||||||
switch (searchFilter) {
|
switch (searchFilter) {
|
||||||
case "quote":
|
case "quote":
|
||||||
filteredItems = quotes?.filter((quote) => quote.quote.toLowerCase().includes(searchTerm.toLowerCase()));
|
filteredItems = quotes?.filter((quote) =>
|
||||||
|
quote.quote.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case "context":
|
case "context":
|
||||||
filteredItems = quotes?.filter((quote) => quote.context?.toLowerCase().includes(searchTerm.toLowerCase()));
|
filteredItems = quotes?.filter((quote) =>
|
||||||
|
quote.context?.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case "author":
|
case "author":
|
||||||
filteredItems = quotes?.filter((quote) => quote.author.toLowerCase().includes(searchTerm.toLowerCase()));
|
filteredItems = quotes?.filter((quote) =>
|
||||||
|
quote.author.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
setFilteredQuotes(filteredItems!)
|
setFilteredQuotes(filteredItems!);
|
||||||
}
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
pb.realtime.subscribe(`${collection}`, function (e) {
|
pb.realtime.subscribe(`${collection}`, function (e) {
|
||||||
console.log("realtime", e.record);
|
console.log("realtime", e.record);
|
||||||
let x = quotes!.filter((quote) => quote.id !== e.record.id);
|
let x = quotes!.filter((quote) => quote.id !== e.record.id);
|
||||||
setQuotes([e.record, ...x])
|
setQuotes([e.record, ...x]);
|
||||||
setFilteredQuotes([e.record, ...x])
|
setFilteredQuotes([e.record, ...x]);
|
||||||
});
|
});
|
||||||
return () => {
|
return () => {
|
||||||
pb.realtime.unsubscribe();
|
pb.realtime.unsubscribe();
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
const getQuotes = async () => {
|
const getQuotes = async () => {
|
||||||
try {
|
try {
|
||||||
const records = await pb.collection(`${collection}`).getFullList<Quote>({ sort: "-created", requestKey: null });
|
const records = await pb
|
||||||
|
.collection(`${collection}`)
|
||||||
|
.getFullList<Quote>({ sort: "-created", requestKey: null });
|
||||||
setQuotes(records);
|
setQuotes(records);
|
||||||
setFilteredQuotes(records);
|
setFilteredQuotes(records);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err)
|
setError(err);
|
||||||
console.error("Error:", error)
|
console.error("Error:", error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getQuotes()
|
getQuotes();
|
||||||
}, [collection])
|
}, [collection]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
//TODO: Add Quote source selection
|
//TODO: Add Quote source selection
|
||||||
<div className='p-3 pt-0'>
|
<div className="p-3 pt-0">
|
||||||
<div className='border-2 bg-slate-800 ml-2 mb-5 p-2 border-lime-500 shadow-(--box) flex flex-row'>
|
<div className="mb-5 ml-2 flex flex-row border-2 border-lime-500 bg-slate-800 p-2 shadow-(--box)">
|
||||||
<select
|
<select
|
||||||
title='Select quote collection'
|
title="Select quote collection"
|
||||||
className='pr-10 bg-slate-700'
|
className="bg-slate-700 pr-10"
|
||||||
value={collection != null ? collection as string : Collections.QtBit}
|
value={
|
||||||
onChange={e => setCollection(e.target.value as Collections)}>
|
collection != null ? (collection as string) : Collections.QtBit
|
||||||
|
}
|
||||||
|
onChange={(e) => setCollection(e.target.value as Collections)}
|
||||||
|
>
|
||||||
<option value="qt_bit">Arcus Notsus</option>
|
<option value="qt_bit">Arcus Notsus</option>
|
||||||
<option value="qt_kfk">Kaffeeklatsch</option>
|
<option value="qt_kfk">Kaffeeklatsch</option>
|
||||||
<option value="qt_theshegays">TheSheGays</option>
|
<option value="qt_theshegays">TheSheGays</option>
|
||||||
</select>
|
</select>
|
||||||
<p className='text-lime-500'>|</p>
|
<p className="text-lime-500">|</p>
|
||||||
<div className='pl-2 flex flex-row'>
|
<div className="flex flex-row pl-2">
|
||||||
<input
|
<input
|
||||||
title='Search for quote'
|
title="Search for quote"
|
||||||
placeholder={'Search for ' + searchFilter as string}
|
placeholder={("Search for " + searchFilter) as string}
|
||||||
value={searchItem as string}
|
value={searchItem as string}
|
||||||
onChange={e => handleInputChange(e)}
|
onChange={(e) => handleInputChange(e)}
|
||||||
className='placeholder:text-slate-400 bg-slate-700' />
|
className="bg-slate-700 placeholder:text-slate-400"
|
||||||
<p className='text-lime-500'>|</p>
|
/>
|
||||||
|
<p className="text-lime-500">|</p>
|
||||||
<select
|
<select
|
||||||
title='Filter search'
|
title="Filter search"
|
||||||
className='pr-1 bg-slate-700'
|
className="bg-slate-700 pr-1"
|
||||||
value={searchFilter as string}
|
value={searchFilter as string}
|
||||||
onChange={e => setSearchFilter(e.target.value as string)}>
|
onChange={(e) => setSearchFilter(e.target.value as string)}
|
||||||
|
>
|
||||||
<option value="quote">Quote</option>
|
<option value="quote">Quote</option>
|
||||||
<option value="context">Context</option>
|
<option value="context">Context</option>
|
||||||
<option value="author">Author</option>
|
<option value="author">Author</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<p className='text-lime-500'>|</p>
|
<p className="text-lime-500">|</p>
|
||||||
<button type='button' className='cursor-pointer ml-auto' title='Add new quote'>
|
<button
|
||||||
|
type="button"
|
||||||
|
className="ml-auto cursor-pointer"
|
||||||
|
title="Add new quote"
|
||||||
|
>
|
||||||
<NavLink to="/add">
|
<NavLink to="/add">
|
||||||
<AddIcon className="w-5 mx-1 active:text-lime-500" />
|
<AddIcon className="mx-1 w-5 active:text-lime-500" />
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</button>
|
</button>
|
||||||
<p className='text-lime-500'>|</p>
|
<p className="text-lime-500">|</p>
|
||||||
</div>
|
</div>
|
||||||
{loading ? (<p> Loading...</p >) : (
|
{loading ? (
|
||||||
|
<p> Loading...</p>
|
||||||
|
) : (
|
||||||
//TODO: Replace div with styled QuoteInfo component
|
//TODO: Replace div with styled QuoteInfo component
|
||||||
<div className="grid grid-cols-4 gap-4">
|
<div className="grid grid-cols-4 gap-4">
|
||||||
{/* {filteredQuotes.map(quote => <QuoteInfo key={quote.id} quote={quote}/>)} */}
|
{/* {filteredQuotes.map(quote => <QuoteInfo key={quote.id} quote={quote}/>)} */}
|
||||||
{/* {quotes != null ? (quotes.map((quote) => (<div key={quote.id}>{quote.quote}</div>))) : (<p>No Quotes</p>)} */}
|
{/* {quotes != null ? (quotes.map((quote) => (<div key={quote.id}>{quote.quote}</div>))) : (<p>No Quotes</p>)} */}
|
||||||
{filteredQuotes != null ? (filteredQuotes.map((quote) => (
|
{filteredQuotes != null ? (
|
||||||
<QuoteInfo key={quote.id} quote={quote} />))) : (<p>No Quotes</p>)}
|
filteredQuotes.map((quote) => (
|
||||||
|
<QuoteInfo key={quote.id} quote={quote} />
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p>No Quotes</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
:root,#root {
|
:root,
|
||||||
@apply bg-zinc-100 dark:bg-slate-700 text-black dark:text-white min-h-screen font-mono;
|
#root {
|
||||||
|
@apply min-h-screen bg-zinc-100 font-mono text-black dark:bg-slate-700 dark:text-white;
|
||||||
--box: -20px 20px 0px 0px #000000;
|
--box: -20px 20px 0px 0px #000000;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
24
src/main.tsx
24
src/main.tsx
|
|
@ -1,19 +1,19 @@
|
||||||
import { StrictMode } from 'react'
|
import { StrictMode } from "react";
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from "react-dom/client";
|
||||||
import './index.css'
|
import "./index.css";
|
||||||
import App from './App.tsx'
|
import App from "./App.tsx";
|
||||||
import { BrowserRouter, Routes, Route } from 'react-router'
|
import { BrowserRouter, Routes, Route } from "react-router";
|
||||||
import QuoteDetail from './QuoteDetail.tsx'
|
import QuoteDetail from "./QuoteDetail.tsx";
|
||||||
import QuoteAdd from './QuoteAdd.tsx'
|
import QuoteAdd from "./QuoteAdd.tsx";
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById("root")!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path='/' element={<App />} />
|
<Route path="/" element={<App />} />
|
||||||
<Route path=':collection/:quoteId' element={<QuoteDetail />} />
|
<Route path=":collection/:quoteId" element={<QuoteDetail />} />
|
||||||
<Route path='add' element={<QuoteAdd />} />
|
<Route path="add" element={<QuoteAdd />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
)
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
/**
|
/**
|
||||||
* This file was @generated using pocketbase-typegen
|
* This file was @generated using pocketbase-typegen
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type PocketBase from 'pocketbase'
|
import type PocketBase from "pocketbase";
|
||||||
import type { RecordService } from 'pocketbase'
|
import type { RecordService } from "pocketbase";
|
||||||
|
|
||||||
export enum Collections {
|
export enum Collections {
|
||||||
QtBit = "qt_bit",
|
QtBit = "qt_bit",
|
||||||
|
|
@ -12,62 +12,65 @@ export enum Collections {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alias types for improved usability
|
// Alias types for improved usability
|
||||||
export type IsoDateString = string
|
export type IsoDateString = string;
|
||||||
export type RecordIdString = string
|
export type RecordIdString = string;
|
||||||
export type HTMLString = string
|
export type HTMLString = string;
|
||||||
|
|
||||||
// System fields
|
// System fields
|
||||||
export type BaseSystemFields<T = never> = {
|
export type BaseSystemFields<T = never> = {
|
||||||
id: RecordIdString
|
id: RecordIdString;
|
||||||
collectionId: string
|
collectionId: string;
|
||||||
collectionName: Collections
|
collectionName: Collections;
|
||||||
expand?: T
|
expand?: T;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type AuthSystemFields<T = never> = {
|
export type AuthSystemFields<T = never> = {
|
||||||
email: string
|
email: string;
|
||||||
emailVisibility: boolean
|
emailVisibility: boolean;
|
||||||
username: string
|
username: string;
|
||||||
verified: boolean
|
verified: boolean;
|
||||||
} & BaseSystemFields<T>
|
} & BaseSystemFields<T>;
|
||||||
|
|
||||||
// Record types for each collection
|
// Record types for each collection
|
||||||
|
|
||||||
export type Quote = {
|
export type Quote = {
|
||||||
author: string
|
author: string;
|
||||||
context?: string
|
context?: string;
|
||||||
collectionName: string
|
collectionName: string;
|
||||||
created?: IsoDateString
|
created?: IsoDateString;
|
||||||
date: IsoDateString
|
date: IsoDateString;
|
||||||
id: string
|
id: string;
|
||||||
quote: string
|
quote: string;
|
||||||
updated?: IsoDateString
|
updated?: IsoDateString;
|
||||||
}
|
};
|
||||||
|
|
||||||
// Response types include system fields and match responses from the PocketBase API
|
// Response types include system fields and match responses from the PocketBase API
|
||||||
export type QtBitResponse<Texpand = unknown> = Required<Quote> & BaseSystemFields<Texpand>
|
export type QtBitResponse<Texpand = unknown> = Required<Quote> &
|
||||||
export type QtKfkResponse<Texpand = unknown> = Required<Quote> & BaseSystemFields<Texpand>
|
BaseSystemFields<Texpand>;
|
||||||
export type QtTheshegaysResponse<Texpand = unknown> = Required<Quote> & BaseSystemFields<Texpand>
|
export type QtKfkResponse<Texpand = unknown> = Required<Quote> &
|
||||||
|
BaseSystemFields<Texpand>;
|
||||||
|
export type QtTheshegaysResponse<Texpand = unknown> = Required<Quote> &
|
||||||
|
BaseSystemFields<Texpand>;
|
||||||
|
|
||||||
// Types containing all Records and Responses, useful for creating typing helper functions
|
// Types containing all Records and Responses, useful for creating typing helper functions
|
||||||
|
|
||||||
export type CollectionRecords = {
|
export type CollectionRecords = {
|
||||||
qt_bit: Quote
|
qt_bit: Quote;
|
||||||
qt_kfk: Quote
|
qt_kfk: Quote;
|
||||||
qt_theshegays: Quote
|
qt_theshegays: Quote;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type CollectionResponses = {
|
export type CollectionResponses = {
|
||||||
qt_bit: QtBitResponse
|
qt_bit: QtBitResponse;
|
||||||
qt_kfk: QtKfkResponse
|
qt_kfk: QtKfkResponse;
|
||||||
qt_theshegays: QtTheshegaysResponse
|
qt_theshegays: QtTheshegaysResponse;
|
||||||
}
|
};
|
||||||
|
|
||||||
// Type for usage with type asserted PocketBase instance
|
// Type for usage with type asserted PocketBase instance
|
||||||
// https://github.com/pocketbase/js-sdk#specify-typescript-definitions
|
// https://github.com/pocketbase/js-sdk#specify-typescript-definitions
|
||||||
|
|
||||||
export type TypedPocketBase = PocketBase & {
|
export type TypedPocketBase = PocketBase & {
|
||||||
collection(idOrName: 'qt_bit'): RecordService<QtBitResponse>
|
collection(idOrName: "qt_bit"): RecordService<QtBitResponse>;
|
||||||
collection(idOrName: 'qt_kfk'): RecordService<QtKfkResponse>
|
collection(idOrName: "qt_kfk"): RecordService<QtKfkResponse>;
|
||||||
collection(idOrName: 'qt_theshegays'): RecordService<QtTheshegaysResponse>
|
collection(idOrName: "qt_theshegays"): RecordService<QtTheshegaysResponse>;
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,31 @@
|
||||||
import PocketBase from 'pocketbase'
|
import PocketBase from "pocketbase";
|
||||||
import { Quote, Collections, TypedPocketBase } from './pocketbase-types'
|
import { Quote, Collections, TypedPocketBase } from "./pocketbase-types";
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
const pb = new PocketBase("https://api.m3.fyi") as TypedPocketBase
|
const pb = new PocketBase("https://api.m3.fyi") as TypedPocketBase;
|
||||||
|
|
||||||
//TODO: See if logic can be moved here from QuoteList.tsx, otherwise delete this file as it is no longer needed.
|
//TODO: See if logic can be moved here from QuoteList.tsx, otherwise delete this file as it is no longer needed.
|
||||||
function getAllQuotes(collectionName: Collections) {
|
function getAllQuotes(collectionName: Collections) {
|
||||||
const [quotes, setQuotes] = useState<Quote[] | null>(null)
|
const [quotes, setQuotes] = useState<Quote[] | null>(null);
|
||||||
const [loading, setLoading] = useState<boolean>(true)
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
const [error, setError] = useState<Error | null>(null)
|
const [error, setError] = useState<Error | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchQuotes() {
|
async function fetchQuotes() {
|
||||||
try {
|
try {
|
||||||
const result: Quote[] = await pb.collection(collectionName).getFullList();
|
const result: Quote[] = await pb
|
||||||
setQuotes(result)
|
.collection(collectionName)
|
||||||
|
.getFullList();
|
||||||
|
setQuotes(result);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err)
|
setError(err);
|
||||||
console.error("Couldn't fetch data", err)
|
console.error("Couldn't fetch data", err);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fetchQuotes();
|
fetchQuotes();
|
||||||
}, [collectionName]) //Re-fetch on collection change
|
}, [collectionName]); //Re-fetch on collection change
|
||||||
return { quotes, loading, error }
|
return { quotes, loading, error };
|
||||||
}
|
}
|
||||||
export default getAllQuotes;
|
export default getAllQuotes;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from "vite";
|
||||||
import react from '@vitejs/plugin-react-swc'
|
import react from "@vitejs/plugin-react-swc";
|
||||||
import tailwindcss from '@tailwindcss/vite'
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
import svgr from 'vite-plugin-svgr'
|
import svgr from "vite-plugin-svgr";
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react(), tailwindcss(), svgr()],
|
plugins: [react(), tailwindcss(), svgr()],
|
||||||
})
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue