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
@@ -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"),
|
||||
);
|
||||
Reference in new issue
Block a user