Upgrade frontend to Node 26, React 19, Vite 8, Apollo Client 4 and ESLint 10
This commit is contained in:
44 files changed
+2940
-4597
No files matched your search
@@ -7,7 +7,7 @@ on:
|
||||
jobs:
|
||||
build_frontend:
|
||||
runs-on: ubuntu-latest
|
||||
container: node:20-alpine
|
||||
container: node:26-alpine
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build frontend
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
container: nikolaik/python-nodejs:python3.14-nodejs20
|
||||
container: nikolaik/python-nodejs:python3.14-nodejs26
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
src/types.ts
|
||||
@@ -1,54 +0,0 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
},
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
sourceType: "module",
|
||||
},
|
||||
extends: [
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:import/errors",
|
||||
"plugin:import/warnings",
|
||||
"plugin:import/typescript",
|
||||
"plugin:react/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
],
|
||||
rules: {
|
||||
"no-console": "off",
|
||||
"prefer-destructuring": "off",
|
||||
"import/no-unresolved": ["error", { ignore: ["\\?react$"] }],
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{
|
||||
devDependencies: ["**/*.test.js", "**/*.spec.js", "**/__tests__/**"],
|
||||
},
|
||||
],
|
||||
"import/prefer-default-export": "off",
|
||||
"import/order": [
|
||||
"error",
|
||||
{
|
||||
groups: ["builtin", "external", "internal", ["parent", "sibling", "index"]],
|
||||
"newlines-between": "always",
|
||||
alphabetize: { order: "asc" },
|
||||
},
|
||||
],
|
||||
"react-hooks/exhaustive-deps": "error",
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"react/button-has-type": "error",
|
||||
"react/jsx-filename-extension": ["warn", { extensions: [".js", ".jsx", ".ts", ".tsx"] }],
|
||||
"react/jsx-uses-react": "off",
|
||||
"react/prefer-stateless-function": "warn",
|
||||
"react/prop-types": "off",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react/sort-comp": "off",
|
||||
},
|
||||
plugins: ["prettier", "react", "react-hooks"],
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
},
|
||||
};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
v20
|
||||
v26
|
||||
@@ -0,0 +1,54 @@
|
||||
import js from "@eslint/js";
|
||||
import globals from "globals";
|
||||
import tseslint from "typescript-eslint";
|
||||
import reactHooks from "eslint-plugin-react-hooks";
|
||||
import reactRefresh from "eslint-plugin-react-refresh";
|
||||
import importPlugin from "eslint-plugin-import-x";
|
||||
import prettier from "eslint-plugin-prettier/recommended";
|
||||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(["dist", "src/types.ts"]),
|
||||
{
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
reactHooks.configs.flat.recommended,
|
||||
reactRefresh.configs.vite,
|
||||
prettier,
|
||||
],
|
||||
plugins: {
|
||||
"import-x": importPlugin,
|
||||
},
|
||||
languageOptions: {
|
||||
globals: globals.browser,
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
"no-console": "off",
|
||||
"prefer-destructuring": "off",
|
||||
"import-x/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{
|
||||
devDependencies: ["**/*.test.ts", "**/*.spec.ts", "**/__tests__/**"],
|
||||
},
|
||||
],
|
||||
"import-x/prefer-default-export": "off",
|
||||
"import-x/order": [
|
||||
"error",
|
||||
{
|
||||
groups: ["builtin", "external", "internal", ["parent", "sibling", "index"]],
|
||||
"newlines-between": "always",
|
||||
alphabetize: { order: "asc" },
|
||||
},
|
||||
],
|
||||
"react-hooks/exhaustive-deps": "error",
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
},
|
||||
},
|
||||
]);
|
||||
Generated
+2701
-4361
File diff suppressed because it is too large.
Load diff
+30
-22
@@ -3,8 +3,8 @@
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "tsc && vite build",
|
||||
"check-types": "tsc",
|
||||
"build": "tsc -b && vite build",
|
||||
"check-types": "tsc -b",
|
||||
"dev": "vite",
|
||||
"format:js": "eslint --fix src",
|
||||
"format:styles": "prettier --write \"src/**/*.css\"",
|
||||
@@ -16,45 +16,53 @@
|
||||
"test": "node --test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.14.1",
|
||||
"@apollo/client": "^4.2.0",
|
||||
"@nivo/bar": "^0.99.0",
|
||||
"@nivo/line": "^0.99.0",
|
||||
"@nivo/pie": "^0.99.0",
|
||||
"clsx": "^1.1.1",
|
||||
"date-fns": "^2.28.0",
|
||||
"graphql": "^16.3.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.30.0",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.3.0",
|
||||
"graphql": "^16.14.0",
|
||||
"react": "^19.2.6",
|
||||
"react-dom": "^19.2.6",
|
||||
"react-hook-form": "^7.76.1",
|
||||
"react-router-dom": "^7.18.4",
|
||||
"use-url-search-params": "^2.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.29.7",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@graphql-codegen/core": "^6.2.2",
|
||||
"@graphql-codegen/typescript": "^6.1.2",
|
||||
"@graphql-codegen/typed-document-node": "^7.2.0",
|
||||
"@graphql-codegen/typescript": "^6.1.2",
|
||||
"@graphql-codegen/typescript-operations": "^6.1.10",
|
||||
"@graphql-typed-document-node/core": "^3.1.1",
|
||||
"@types/react": "^18.3.28",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"@typescript-eslint/eslint-plugin": "^8.72.0",
|
||||
"@typescript-eslint/parser": "^8.72.0",
|
||||
"eslint": "^8.57.1",
|
||||
"@rolldown/plugin-babel": "^0.2.3",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"@types/node": "^26.6.5",
|
||||
"@types/react": "^19.2.15",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.2",
|
||||
"babel-plugin-react-compiler": "^1.0.0",
|
||||
"eslint": "^10.4.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-import-x": "^4.16.2",
|
||||
"eslint-plugin-prettier": "^5.5.7",
|
||||
"eslint-plugin-react": "^7.29.4",
|
||||
"eslint-plugin-react-hooks": "^4.4.0",
|
||||
"eslint-plugin-react-hooks": "^7.1.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.2",
|
||||
"globals": "^17.6.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^6.4.5",
|
||||
"typescript": "~6.0.3",
|
||||
"typescript-eslint": "^8.60.0",
|
||||
"vite": "^8.0.14",
|
||||
"vite-plugin-svgr": "^5.2.0",
|
||||
"yaml": "^2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20"
|
||||
"node": "26"
|
||||
},
|
||||
"overrides": {
|
||||
"@whatwg-node/promise-helpers": "^1.3.2"
|
||||
}
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ApolloProvider } from "@apollo/client";
|
||||
import { ApolloProvider } from "@apollo/client/react";
|
||||
import React from "react";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Toast } from "./components/Toast";
|
||||
import { ToastProvider } from "./components/ToastProvider";
|
||||
import { Wireframe } from "./components/Wireframe";
|
||||
|
||||
const App = (): JSX.Element => (
|
||||
const App = (): React.JSX.Element => (
|
||||
<ApolloProvider client={client}>
|
||||
<Router>
|
||||
<ToastProvider>
|
||||
|
||||
@@ -16,7 +16,7 @@ const UserStats = React.lazy(() => import("./features/stats/UserStats"));
|
||||
const UserDetail = React.lazy(() => import("./features/users/UserDetail"));
|
||||
const UserList = React.lazy(() => import("./features/users/UserList"));
|
||||
|
||||
export const Routes = (): JSX.Element => (
|
||||
export const Routes = (): React.JSX.Element => (
|
||||
<React.Suspense fallback={<div>Loading...</div>}>
|
||||
<ReactRouterRoutes>
|
||||
<Route path="/deposit" element={<Deposit />} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ApolloClient, InMemoryCache } from "@apollo/client";
|
||||
import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client";
|
||||
|
||||
const client = new ApolloClient({
|
||||
uri: import.meta.env.DEV ? "/graphql/" : `${import.meta.env.VITE_API_URL}/graphql/`,
|
||||
link: new HttpLink({ uri: import.meta.env.DEV ? "/graphql/" : `${import.meta.env.VITE_API_URL}/graphql/` }),
|
||||
cache: new InMemoryCache(),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from "react";
|
||||
|
||||
import Brick from "../assets/brick.svg?react";
|
||||
import styles from "./Footer.module.css";
|
||||
import Brick from "../assets/brick.svg?react";
|
||||
|
||||
export const Footer = (): JSX.Element => {
|
||||
export const Footer = (): React.JSX.Element => {
|
||||
return (
|
||||
<footer className={styles.footer}>
|
||||
Made with <span className={styles.heart}>❤</span> by{" "}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ApolloError } from "@apollo/client";
|
||||
import React from "react";
|
||||
import type { ErrorLike } from "@apollo/client";
|
||||
import { CombinedGraphQLErrors } from "@apollo/client/errors";
|
||||
|
||||
import styles from "./GQLError.module.css";
|
||||
|
||||
@@ -11,9 +11,9 @@ const formatModel = (text: string) => {
|
||||
return withSpaces.charAt(0).toUpperCase() + withSpaces.slice(1);
|
||||
};
|
||||
|
||||
export const GQLError = ({ error }: { error: ApolloError }) => {
|
||||
if (error?.graphQLErrors?.length == 1) {
|
||||
const single = error?.graphQLErrors[0];
|
||||
export const GQLError = ({ error }: { error: ErrorLike }) => {
|
||||
if (error instanceof CombinedGraphQLErrors && error.errors.length === 1) {
|
||||
const single = error.errors[0];
|
||||
if (single?.extensions?.code === "NOT_FOUND") {
|
||||
const modelPretty = formatModel(single?.extensions?.model as string);
|
||||
return <div className={styles.errorMessage}>{`${modelPretty} not found`}</div>;
|
||||
|
||||
@@ -8,7 +8,7 @@ const ADMIN_URL = `${import.meta.env.VITE_API_URL}/admin/`;
|
||||
|
||||
const navLinkStyles = ({ isActive }: { isActive: boolean }) => clsx(styles.navLink, { [styles.active]: isActive });
|
||||
|
||||
export const Header = (): JSX.Element => {
|
||||
export const Header = (): React.JSX.Element => {
|
||||
return (
|
||||
<header className={styles.header}>
|
||||
<nav className={styles.nav}>
|
||||
|
||||
@@ -8,7 +8,7 @@ interface SplashProps {
|
||||
noOverlay?: boolean;
|
||||
}
|
||||
|
||||
const Splash = ({ brandText = "BBS", noOverlay = false }: SplashProps): JSX.Element => (
|
||||
const Splash = ({ brandText = "BBS", noOverlay = false }: SplashProps): React.JSX.Element => (
|
||||
<div className={clsx(styles.splash, { [styles.overlay]: !noOverlay })}>
|
||||
<div className={styles.wrap}>
|
||||
<div className={styles.sun}></div>
|
||||
|
||||
@@ -2,9 +2,9 @@ import clsx from "clsx";
|
||||
import React from "react";
|
||||
|
||||
import styles from "./Toast.module.css";
|
||||
import { useToast } from "./ToastProvider";
|
||||
import { useToast } from "../hooks/useToast";
|
||||
|
||||
export const Toast = (): JSX.Element => {
|
||||
export const Toast = (): React.JSX.Element => {
|
||||
const { showToast, message, background, visible } = useToast();
|
||||
|
||||
const classes = clsx([styles[background], styles.toast], { [styles.visible]: visible });
|
||||
|
||||
@@ -1,35 +1,17 @@
|
||||
import React, { ReactNode, useContext, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
type Background = "primary" | "danger";
|
||||
|
||||
interface ToastProps {
|
||||
visible: boolean;
|
||||
message: string;
|
||||
showToast: (message: string, background?: Background) => void;
|
||||
hide: () => void;
|
||||
background: Background;
|
||||
}
|
||||
|
||||
const defaultState = {
|
||||
// FIXME: revert
|
||||
visible: false,
|
||||
message: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
showToast: () => {},
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
hide: () => {},
|
||||
background: "primary" as Background,
|
||||
};
|
||||
|
||||
const ToastContext = React.createContext<ToastProps>(defaultState);
|
||||
import { ToastContext, defaultToastState } from "../contexts/ToastContext";
|
||||
import type { Background } from "../contexts/ToastContext";
|
||||
|
||||
type ToastProviderProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
export const ToastProvider = ({ children }: ToastProviderProps): JSX.Element => {
|
||||
const [visible, setVisible] = useState(defaultState.visible);
|
||||
const [message, setMessage] = useState(defaultState.message);
|
||||
const [background, setBackground] = useState(defaultState.background);
|
||||
|
||||
export const ToastProvider = ({ children }: ToastProviderProps): React.JSX.Element => {
|
||||
const [visible, setVisible] = useState(defaultToastState.visible);
|
||||
const [message, setMessage] = useState(defaultToastState.message);
|
||||
const [background, setBackground] = useState(defaultToastState.background);
|
||||
|
||||
const showToast = (newMessage: string, background: Background = "primary") => {
|
||||
setMessage(newMessage);
|
||||
@@ -49,5 +31,3 @@ export const ToastProvider = ({ children }: ToastProviderProps): JSX.Element =>
|
||||
<ToastContext.Provider value={{ visible, showToast, message, hide, background }}>{children}</ToastContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useToast = (): ToastProps => useContext(ToastContext);
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from "react";
|
||||
|
||||
import styles from "./Wireframe.module.css";
|
||||
|
||||
export const Wireframe = () => (
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import React from "react";
|
||||
|
||||
export type Background = "primary" | "danger";
|
||||
|
||||
export interface ToastProps {
|
||||
visible: boolean;
|
||||
message: string;
|
||||
showToast: (message: string, background?: Background) => void;
|
||||
hide: () => void;
|
||||
background: Background;
|
||||
}
|
||||
|
||||
export const defaultToastState: ToastProps = {
|
||||
// FIXME: revert
|
||||
visible: false,
|
||||
message: "",
|
||||
showToast: () => {},
|
||||
hide: () => {},
|
||||
background: "primary",
|
||||
};
|
||||
|
||||
export const ToastContext = React.createContext<ToastProps>(defaultToastState);
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import React from "react";
|
||||
import { useMutation } from "@apollo/client/react";
|
||||
import { SubmitHandler, useForm } from "react-hook-form";
|
||||
|
||||
import { CreateUserDocument } from "../../types";
|
||||
import styles from "./UserCreate.module.css";
|
||||
import { CreateUserDocument } from "../../types";
|
||||
|
||||
type FormValues = {
|
||||
firstName: string;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from "react";
|
||||
|
||||
import { UserCard } from "../users/UserCard";
|
||||
import styles from "./Deposit.module.css";
|
||||
import { useDeposit } from "./useDeposit";
|
||||
import { UserCard } from "../users/UserCard";
|
||||
|
||||
const Deposit = (): JSX.Element => {
|
||||
const Deposit = (): React.JSX.Element => {
|
||||
const {
|
||||
loading,
|
||||
selectedUser,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { useMutation, useQuery } from "@apollo/client/react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { useToast } from "../../components/ToastProvider";
|
||||
import { useToast } from "../../hooks/useToast";
|
||||
import { AllUsersDocument, CreateDepositDocument } from "../../types";
|
||||
|
||||
export const useDeposit = () => {
|
||||
@@ -32,9 +32,9 @@ export const useDeposit = () => {
|
||||
}
|
||||
|
||||
if (res.data?.createDeposit?.__typename === "CreateDepositSuccess") {
|
||||
const { transaction } = res.data?.createDeposit;
|
||||
const { transaction } = res.data.createDeposit;
|
||||
showToast(
|
||||
`${transaction.user.firstName} har satt inn${transaction.amount}. Ny saldo er ${transaction.user.profile?.balance}.`,
|
||||
`${transaction.user.firstName} har satt inn ${transaction.amount}. Ny saldo er ${transaction.user.profile?.balance}.`,
|
||||
);
|
||||
reset();
|
||||
return;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import React, { useState } from "react";
|
||||
import { useQuery } from "@apollo/client/react";
|
||||
import { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import styles from "./Inventory.module.css";
|
||||
import { GQLError } from "../../components/GQLError";
|
||||
import { InventoryPageDocument } from "../../types";
|
||||
import { formatCurrency } from "../../utils/currency";
|
||||
import { formatTime } from "../../utils/time";
|
||||
import styles from "./Inventory.module.css";
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { useMutation, useQuery } from "@apollo/client/react";
|
||||
import React, { useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
|
||||
import { GQLError } from "../../components/GQLError";
|
||||
import { useToast } from "../../components/ToastProvider";
|
||||
import { CreateInventoryTransactionDocument, InventoryProductsDocument } from "../../types";
|
||||
import styles from "./Inventory.module.css";
|
||||
import { GQLError } from "../../components/GQLError";
|
||||
import { useToast } from "../../hooks/useToast";
|
||||
import { CreateInventoryTransactionDocument, InventoryProductsDocument } from "../../types";
|
||||
|
||||
const InventoryAdd = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { useQuery } from "@apollo/client/react";
|
||||
import { format, subDays } from "date-fns";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import styles from "./Inventory.module.css";
|
||||
import { GQLError } from "../../components/GQLError";
|
||||
import { InventoryReportDocument } from "../../types";
|
||||
import { formatCurrency } from "../../utils/currency";
|
||||
import styles from "./Inventory.module.css";
|
||||
|
||||
const today = () => format(new Date(), "yyyy-MM-dd");
|
||||
const monthAgo = () => format(subDays(new Date(), 30), "yyyy-MM-dd");
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { useQuery } from "@apollo/client/react";
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
import { useUrlSearchParams } from "use-url-search-params";
|
||||
|
||||
import styles from "./Log.module.css";
|
||||
import { OrderListDocument, TransactionListDocument } from "../../types";
|
||||
import { formatTime } from "../../utils/time";
|
||||
import styles from "./Log.module.css";
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import clsx from "clsx";
|
||||
import React, { MouseEventHandler } from "react";
|
||||
|
||||
import { AllProductsQuery } from "../../types";
|
||||
import styles from "./ProductCard.module.css";
|
||||
import { AllProductsQuery } from "../../types";
|
||||
|
||||
type ProductCardProps = {
|
||||
product: AllProductsQuery["allProducts"][number];
|
||||
@@ -11,7 +11,7 @@ type ProductCardProps = {
|
||||
amount: number;
|
||||
};
|
||||
|
||||
export const ProductCard = ({ product, onProduct, amount, active = false }: ProductCardProps): JSX.Element => {
|
||||
export const ProductCard = ({ product, onProduct, amount, active = false }: ProductCardProps): React.JSX.Element => {
|
||||
const classes = clsx(styles.product, { [styles.active]: active });
|
||||
return (
|
||||
<button className={classes} type="button" onClick={onProduct} title={product.name}>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import React from "react";
|
||||
|
||||
import { AllProductsQuery, OrderLineInput } from "../../types";
|
||||
import { UserCard } from "../users/UserCard";
|
||||
import { ProductCard } from "./ProductCard";
|
||||
import styles from "./Register.module.css";
|
||||
import { useRegister } from "./useRegister";
|
||||
import { AllProductsQuery, OrderLineInput } from "../../types";
|
||||
import { UserCard } from "../users/UserCard";
|
||||
|
||||
const USER_EXTERNAL = "external";
|
||||
|
||||
const Register = (): JSX.Element => {
|
||||
const Register = (): React.JSX.Element => {
|
||||
const {
|
||||
users,
|
||||
products,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { useMutation, useQuery } from "@apollo/client/react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { useToast } from "../../components/ToastProvider";
|
||||
import { useToast } from "../../hooks/useToast";
|
||||
import {
|
||||
AllProductsDocument,
|
||||
AllProductsQuery,
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { useQuery } from "@apollo/client/react";
|
||||
import { BarDatum, ResponsiveBar } from "@nivo/bar";
|
||||
import React from "react";
|
||||
|
||||
import { OrderStatsDocument, SchemaOrderStatsByTime } from "../../types";
|
||||
import styles from "./Stats.module.css";
|
||||
import { StatsView } from "./StatsView";
|
||||
import { chartTheme } from "./chartTheme";
|
||||
import { OrderStatsDocument, SchemaOrderStatsByTime } from "../../types";
|
||||
|
||||
const days = { 1: "Mandag", 2: "Tirsdag", 3: "Onsdag", 4: "Torsdag", 5: "Fredag", 6: "Lørdag", 7: "Søndag" };
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { useQuery } from "@apollo/client/react";
|
||||
import { ResponsivePie } from "@nivo/pie";
|
||||
import React from "react";
|
||||
|
||||
import { AllProductStatsDocument } from "../../types";
|
||||
import styles from "./Stats.module.css";
|
||||
import { StatsView } from "./StatsView";
|
||||
import { chartTheme } from "./chartTheme";
|
||||
import { AllProductStatsDocument } from "../../types";
|
||||
|
||||
type UserCount = {
|
||||
name: string;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { useQuery } from "@apollo/client/react";
|
||||
import { ResponsiveLine } from "@nivo/line";
|
||||
import React from "react";
|
||||
|
||||
import { ProductStatsDocument } from "../../types";
|
||||
import styles from "./Stats.module.css";
|
||||
import { StatsView } from "./StatsView";
|
||||
import { chartTheme, chartTooltipStyle } from "./chartTheme";
|
||||
import { ProductStatsDocument } from "../../types";
|
||||
|
||||
const Stats = () => {
|
||||
const { data, loading } = useQuery(ProductStatsDocument);
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { useQuery } from "@apollo/client/react";
|
||||
import { ResponsiveBar } from "@nivo/bar";
|
||||
import React from "react";
|
||||
|
||||
import { GQLError } from "../../components/GQLError";
|
||||
import { UserStatsDocument } from "../../types";
|
||||
import styles from "./Stats.module.css";
|
||||
import { StatsView } from "./StatsView";
|
||||
import { chartTheme } from "./chartTheme";
|
||||
import { GQLError } from "../../components/GQLError";
|
||||
import { UserStatsDocument } from "../../types";
|
||||
|
||||
const UserStats = () => {
|
||||
const { data, loading, error } = useQuery(UserStatsDocument);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import clsx from "clsx";
|
||||
import React, { MouseEventHandler } from "react";
|
||||
|
||||
import styles from "./UserCard.module.css";
|
||||
import { AllUsersQuery } from "../../types";
|
||||
import { formatCurrency } from "../../utils/currency";
|
||||
import styles from "./UserCard.module.css";
|
||||
|
||||
type UserCardProps = {
|
||||
user: AllUsersQuery["allUsers"][0] | null;
|
||||
@@ -12,7 +12,7 @@ type UserCardProps = {
|
||||
};
|
||||
|
||||
/** If user is null, then it's the special external user */
|
||||
export const UserCard = ({ user, onUser, active = false }: UserCardProps): JSX.Element => {
|
||||
export const UserCard = ({ user, onUser, active = false }: UserCardProps): React.JSX.Element => {
|
||||
const classes = clsx(styles.user, { [styles.active]: active });
|
||||
const userBalanceString = formatCurrency(Number(user?.profile?.balance));
|
||||
return (
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { useQuery } from "@apollo/client/react";
|
||||
import { ResponsiveLine } from "@nivo/line";
|
||||
import React from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
import styles from "./UserDetail.module.css";
|
||||
import { GQLError } from "../../components/GQLError";
|
||||
import { SchemaUser as User, UserDetailDocument, SchemaUserProfile as UserProfile } from "../../types";
|
||||
import { formatMonth, formatTime } from "../../utils/time";
|
||||
import { chartTheme, chartTooltipStyle } from "../stats/chartTheme";
|
||||
import styles from "./UserDetail.module.css";
|
||||
|
||||
const UserDetail = () => {
|
||||
const { userId } = useParams();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import React, { useState } from "react";
|
||||
import { useQuery } from "@apollo/client/react";
|
||||
import { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { AllUsersDocument } from "../../types";
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
|
||||
export function usePrevious<T>(value: T) {
|
||||
// The ref object is a generic container whose current property is mutable ...
|
||||
// ... and can hold any value, similar to an instance property on a class
|
||||
const ref = useRef<T | undefined>(undefined);
|
||||
|
||||
// Store current value in ref
|
||||
useEffect(() => {
|
||||
ref.current = value;
|
||||
}, [value]); // Only re-run if value changes
|
||||
// Return previous value (happens before update in useEffect above)
|
||||
return ref.current;
|
||||
}
|
||||
|
||||
export function useToast() {
|
||||
const show = useCallback(() => {
|
||||
console.log("showing toast with element from root");
|
||||
}, []);
|
||||
return { show };
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { useContext } from "react";
|
||||
|
||||
import { ToastContext } from "../contexts/ToastContext";
|
||||
|
||||
export const useToast = () => useContext(ToastContext);
|
||||
@@ -1,13 +1,12 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { createRoot } from "react-dom/client";
|
||||
|
||||
import App from "./App";
|
||||
|
||||
import "./global.css";
|
||||
|
||||
ReactDOM.render(
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById("root"),
|
||||
);
|
||||
@@ -1,9 +0,0 @@
|
||||
module.exports = {
|
||||
extends: ["stylelint-config-recommended", "stylelint-prettier/recommended", "stylelint-config-idiomatic-order"],
|
||||
plugins: ["stylelint-order"],
|
||||
rules: {
|
||||
// Useful?
|
||||
"no-descending-specificity": null,
|
||||
},
|
||||
ignoreFiles: ["**/*.md", "**/*.svg", "**/*.jpg", "**/*.graphql", "**/*.tsx", "**/*.ts"],
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2023",
|
||||
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
||||
"module": "esnext",
|
||||
"types": ["vite/client"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Strictness */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
+5
-25
@@ -1,27 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2021",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": [
|
||||
"DOM",
|
||||
"DOM.Iterable",
|
||||
"ES2021"
|
||||
],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ES2020",
|
||||
"moduleResolution": "Node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
},
|
||||
"include": [
|
||||
"./src",
|
||||
"node_modules/vite/types/importMeta.d.ts"
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2023",
|
||||
"lib": ["ES2023"],
|
||||
"module": "esnext",
|
||||
"types": ["node"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Strictness */
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { defineConfig } from "vite";
|
||||
import react, { reactCompilerPreset } from "@vitejs/plugin-react";
|
||||
import babel from "@rolldown/plugin-babel";
|
||||
import svgr from "vite-plugin-svgr";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [svgr()],
|
||||
plugins: [react(), babel({ presets: [reactCompilerPreset()] }), svgr()],
|
||||
define: {
|
||||
__DEV__: process.env.NODE_ENV !== "production",
|
||||
},
|
||||
Reference in new issue
Block a user