diff --git a/frontend/README.md b/frontend/README.md index da98444..a2980c2 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -24,31 +24,31 @@ export default tseslint.config({ languageOptions: { // other options... parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], + project: ["./tsconfig.node.json", "./tsconfig.app.json"], tsconfigRootDir: import.meta.dirname, }, }, -}) +}); ``` You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: ```js // eslint.config.js -import reactX from 'eslint-plugin-react-x' -import reactDom from 'eslint-plugin-react-dom' +import reactX from "eslint-plugin-react-x"; +import reactDom from "eslint-plugin-react-dom"; export default tseslint.config({ plugins: { // Add the react-x and react-dom plugins - 'react-x': reactX, - 'react-dom': reactDom, + "react-x": reactX, + "react-dom": reactDom, }, rules: { // other rules... // Enable its recommended typescript rules - ...reactX.configs['recommended-typescript'].rules, + ...reactX.configs["recommended-typescript"].rules, ...reactDom.configs.recommended.rules, }, -}) +}); ``` diff --git a/frontend/index.html b/frontend/index.html index f5541b9..1b22057 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,33 +1,33 @@ - + - - - + + + - - - - - - - - - - + + + + + + + + + + - - - - + + + + - + {{Title}} diff --git a/frontend/src/app.ts b/frontend/src/app.ts index f9ef0b0..4699c17 100644 --- a/frontend/src/app.ts +++ b/frontend/src/app.ts @@ -1,4 +1,4 @@ -import {User} from "./network/models.ts"; +import { User } from "./network/models.ts"; interface MyWindow extends Window { serverName?: string; @@ -7,7 +7,7 @@ interface MyWindow extends Window { } class App { - appName = "Nysoure" + appName = "Nysoure"; user: User | null = null; @@ -15,7 +15,7 @@ class App { cloudflareTurnstileSiteKey: string | null = null; - siteInfo = "" + siteInfo = ""; constructor() { this.init(); @@ -31,7 +31,8 @@ class App { this.token = JSON.parse(tokenJson); } this.appName = (window as MyWindow).serverName || this.appName; - this.cloudflareTurnstileSiteKey = (window as MyWindow).cloudflareTurnstileSiteKey || null; + this.cloudflareTurnstileSiteKey = + (window as MyWindow).cloudflareTurnstileSiteKey || null; this.siteInfo = (window as MyWindow).siteInfo || ""; } @@ -53,4 +54,4 @@ class App { } } -export const app = new App(); \ No newline at end of file +export const app = new App(); diff --git a/frontend/src/app.tsx b/frontend/src/app.tsx index 842b0c4..5926c54 100644 --- a/frontend/src/app.tsx +++ b/frontend/src/app.tsx @@ -1,4 +1,4 @@ -import {BrowserRouter, Route, Routes} from "react-router"; +import { BrowserRouter, Route, Routes } from "react-router"; import LoginPage from "./pages/login_page.tsx"; import RegisterPage from "./pages/register_page.tsx"; import Navigator from "./components/navigator.tsx"; @@ -17,21 +17,21 @@ export default function App() { return ( - }/> - }/> - }> - }/> - } /> - } /> - }/> - }/> - }/> - }/> - }/> - }/> - }/> + } /> + } /> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> - ) + ); } diff --git a/frontend/src/components/AppContext.tsx b/frontend/src/components/AppContext.tsx index ecf712b..98514ce 100644 --- a/frontend/src/components/AppContext.tsx +++ b/frontend/src/components/AppContext.tsx @@ -1,10 +1,6 @@ -import {createContext, ReactNode, useContext} from "react"; +import { createContext, ReactNode, useContext } from "react"; -export default function AppContext({ - children, -}: { - children: ReactNode; -}) { +export default function AppContext({ children }: { children: ReactNode }) { return ( ()}> {children} @@ -15,5 +11,5 @@ export default function AppContext({ const context = createContext>(new Map()); export function useAppContext() { - return useContext(context) -} \ No newline at end of file + return useContext(context); +} diff --git a/frontend/src/components/alert.tsx b/frontend/src/components/alert.tsx index 8ac81e8..bacab00 100644 --- a/frontend/src/components/alert.tsx +++ b/frontend/src/components/alert.tsx @@ -1,18 +1,53 @@ -export function ErrorAlert({ message, className }: { message: string, className?: string }) { - return
- - - - {message} -
; +export function ErrorAlert({ + message, + className, +}: { + message: string; + className?: string; +}) { + return ( +
+ + + + {message} +
+ ); } -export function InfoAlert({ message, className }: { message: string, className?: string }) { - return
- - - - {message} -
; -} \ No newline at end of file +export function InfoAlert({ + message, + className, +}: { + message: string; + className?: string; +}) { + return ( +
+ + + + {message} +
+ ); +} diff --git a/frontend/src/components/badge.tsx b/frontend/src/components/badge.tsx index 1bb5700..ceed38d 100644 --- a/frontend/src/components/badge.tsx +++ b/frontend/src/components/badge.tsx @@ -1,9 +1,39 @@ -import {ReactNode} from "react"; +import { ReactNode } from "react"; -export default function Badge({children, className, onClick }: { children: ReactNode, className?: string, onClick?: () => void }) { - return {children} +export default function Badge({ + children, + className, + onClick, +}: { + children: ReactNode; + className?: string; + onClick?: () => void; +}) { + return ( + + {children} + + ); } -export function BadgeAccent({children, className, onClick }: { children: ReactNode, className?: string, onClick?: () => void }) { - return {children} -} \ No newline at end of file +export function BadgeAccent({ + children, + className, + onClick, +}: { + children: ReactNode; + className?: string; + onClick?: () => void; +}) { + return ( + + {children} + + ); +} diff --git a/frontend/src/components/button.tsx b/frontend/src/components/button.tsx index abc43c6..413bb3b 100644 --- a/frontend/src/components/button.tsx +++ b/frontend/src/components/button.tsx @@ -1,14 +1,28 @@ import { ReactNode } from "react"; -export default function Button({ children, onClick, className, disabled, isLoading }: { children: ReactNode, onClick?: () => void, className?: string, disabled?: boolean, isLoading?: boolean }) { - return ; -} \ No newline at end of file +export default function Button({ + children, + onClick, + className, + disabled, + isLoading, +}: { + children: ReactNode; + onClick?: () => void; + className?: string; + disabled?: boolean; + isLoading?: boolean; +}) { + return ( + + ); +} diff --git a/frontend/src/components/image_selector.tsx b/frontend/src/components/image_selector.tsx index 3e80465..9ccb8d4 100644 --- a/frontend/src/components/image_selector.tsx +++ b/frontend/src/components/image_selector.tsx @@ -1,12 +1,12 @@ -import {MdAdd} from "react-icons/md"; -import {useTranslation} from "react-i18next"; -import {network} from "../network/network.ts"; +import { MdAdd } from "react-icons/md"; +import { useTranslation } from "react-i18next"; +import { network } from "../network/network.ts"; import showToast from "./toast.ts"; -import {useState} from "react"; +import { useState } from "react"; async function uploadImages(files: File[]): Promise { const images: number[] = []; - + for (const file of files) { const res = await network.uploadImage(file); if (res.success) { @@ -15,65 +15,83 @@ async function uploadImages(files: File[]): Promise { showToast({ type: "error", message: `Failed to upload image: ${res.message}`, - }) + }); } } - + return images; } -export function SelectAndUploadImageButton({onUploaded}: {onUploaded: (image: number[]) => void}) { - const [isUploading, setUploading] = useState(false) +export function SelectAndUploadImageButton({ + onUploaded, +}: { + onUploaded: (image: number[]) => void; +}) { + const [isUploading, setUploading] = useState(false); const { t } = useTranslation(); const addImage = () => { if (isUploading) { - return + return; } - const input = document.createElement("input") - input.type = "file" - input.accept = "image/*" - input.multiple = true + const input = document.createElement("input"); + input.type = "file"; + input.accept = "image/*"; + input.multiple = true; input.onchange = async () => { if (!input.files || input.files.length === 0) { - return + return; } - setUploading(true) + setUploading(true); const files = Array.from(input.files); const uploadedImages = await uploadImages(files); setUploading(false); if (uploadedImages.length > 0) { onUploaded(uploadedImages); } - } - input.click() - } + }; + input.click(); + }; - return + return ( + + ); } -export function UploadClipboardImageButton({onUploaded}: {onUploaded: (image: number[]) => void}) { - const [isUploading, setUploading] = useState(false) +export function UploadClipboardImageButton({ + onUploaded, +}: { + onUploaded: (image: number[]) => void; +}) { + const [isUploading, setUploading] = useState(false); const { t } = useTranslation(); const addClipboardImage = async () => { if (isUploading) { - return + return; } try { const clipboardItems = await navigator.clipboard.read(); const files: File[] = []; for (const item of clipboardItems) { - console.log(item) + console.log(item); for (const type of item.types) { if (type.startsWith("image/")) { const blob = await item.getType(type); - files.push(new File([blob], `clipboard-image.${type.split("/")[1]}`, { type })); + files.push( + new File([blob], `clipboard-image.${type.split("/")[1]}`, { + type, + }), + ); } } } @@ -96,15 +114,27 @@ export function UploadClipboardImageButton({onUploaded}: {onUploaded: (image: nu message: t("Failed to read clipboard image"), }); } - } + }; - return + return ( + + ); } -export function ImageDrapArea({children, onUploaded}: {children: React.ReactNode, onUploaded: (image: number[]) => void}) { +export function ImageDrapArea({ + children, + onUploaded, +}: { + children: React.ReactNode; + onUploaded: (image: number[]) => void; +}) { const [isUploading, setUploading] = useState(false); const handleDragOver = (e: React.DragEvent) => { @@ -128,7 +158,7 @@ export function ImageDrapArea({children, onUploaded}: {children: React.ReactNode if (e.dataTransfer.files.length > 0) { setUploading(true); let files = Array.from(e.dataTransfer.files); - files = files.filter(file => file.type.startsWith("image/")); + files = files.filter((file) => file.type.startsWith("image/")); if (files.length === 0) { setUploading(false); return; @@ -160,4 +190,4 @@ export function ImageDrapArea({children, onUploaded}: {children: React.ReactNode ); -} \ No newline at end of file +} diff --git a/frontend/src/components/input.tsx b/frontend/src/components/input.tsx index 7f8364e..0b50335 100644 --- a/frontend/src/components/input.tsx +++ b/frontend/src/components/input.tsx @@ -11,15 +11,31 @@ interface InputProps { export default function Input(props: InputProps) { if (props.inlineLabel) { - return + return ( + + ); } else { - return
- {props.label} - -
+ return ( +
+ {props.label} + +
+ ); } } @@ -31,10 +47,17 @@ interface TextAreaProps { } export function TextArea(props: TextAreaProps) { - return
- {props.label} -