/ client / src / App.tsx
App.tsx
  1  import { Refine } from "@refinedev/core";
  2  import { RefineKbar, RefineKbarProvider } from "@refinedev/kbar";
  3  import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
  4  
  5  import { ErrorComponent } from "@refinedev/antd";
  6  import "@refinedev/antd/dist/reset.css";
  7  
  8  import {
  9    FileOutlined,
 10    HighlightOutlined,
 11    HomeOutlined,
 12    QuestionOutlined,
 13    TableOutlined,
 14    ToolOutlined,
 15    UserOutlined,
 16  } from "@ant-design/icons";
 17  import loadable from "@loadable/component";
 18  import routerBindings, { DocumentTitleHandler, UnsavedChangesNotifier } from "@refinedev/react-router";
 19  import { ConfigProvider } from "antd";
 20  import { Locale } from "antd/es/locale";
 21  import { useEffect, useState } from "react";
 22  import { useTranslation } from "react-i18next";
 23  import { BrowserRouter, Outlet, Route, Routes } from "react-router";
 24  import dataProvider from "./components/dataProvider";
 25  import { Favicon } from "./components/favicon";
 26  import { SpoolmanLayout } from "./components/layout";
 27  import liveProvider from "./components/liveProvider";
 28  import SpoolmanNotificationProvider from "./components/notificationProvider";
 29  import { ColorModeContextProvider } from "./contexts/color-mode";
 30  import { languages } from "./i18n";
 31  import { getAPIURL, getBasePath } from "./utils/url";
 32  
 33  interface ResourcePageProps {
 34    resource: "spools" | "filaments" | "vendors";
 35    page: "list" | "create" | "edit" | "show";
 36    mode?: "create" | "clone";
 37  }
 38  
 39  const LoadableResourcePage = loadable(
 40    (props: ResourcePageProps) => import(`./pages/${props.resource}/${props.page}.tsx`),
 41    {
 42      fallback: <div>Page is Loading...</div>,
 43      cacheKey: (props: ResourcePageProps) => `${props.resource}-${props.page}-${props.mode ?? ""}`,
 44    },
 45  );
 46  
 47  interface LoadablePageProps {
 48    name: string;
 49  }
 50  
 51  const LoadablePage = loadable((props: LoadablePageProps) => import(`./pages/${props.name}/index.tsx`), {
 52    fallback: <div>Page is Loading...</div>,
 53    cacheKey: (props: LoadablePageProps) => `page-${props.name}`,
 54  });
 55  
 56  function App() {
 57    const { t, i18n } = useTranslation();
 58  
 59    const i18nProvider = {
 60      translate: (key: string, params?: never) => t(key, params),
 61      changeLocale: (lang: string) => i18n.changeLanguage(lang),
 62      getLocale: () => i18n.language,
 63    };
 64  
 65    // Fetch the antd locale using dynamic imports
 66    const [antdLocale, setAntdLocale] = useState<Locale | undefined>();
 67    useEffect(() => {
 68      const fetchLocale = async () => {
 69        const locale = await import(
 70          `./../node_modules/antd/es/locale/${languages[i18n.language].fullCode.replace("-", "_")}.js`
 71        );
 72        setAntdLocale(locale.default);
 73      };
 74      fetchLocale().catch(console.error);
 75    }, [i18n.language]);
 76  
 77    if (!import.meta.env.VITE_APIURL) {
 78      return (
 79        <>
 80          <h1>Missing API URL</h1>
 81          <p>
 82            App was built without an API URL. Please set the VITE_APIURL environment variable to the URL of your Spoolman
 83            API.
 84          </p>
 85        </>
 86      );
 87    }
 88  
 89    return (
 90      <BrowserRouter basename={getBasePath() + "/"}>
 91        <RefineKbarProvider>
 92          <ColorModeContextProvider>
 93            <ConfigProvider locale={antdLocale}>
 94              <Refine
 95                dataProvider={dataProvider(getAPIURL())}
 96                notificationProvider={SpoolmanNotificationProvider}
 97                i18nProvider={i18nProvider}
 98                routerProvider={routerBindings}
 99                liveProvider={liveProvider(getAPIURL())}
100                resources={[
101                  {
102                    name: "home",
103                    list: "/",
104                    meta: {
105                      canDelete: false,
106                      icon: <HomeOutlined />,
107                    },
108                  },
109                  {
110                    name: "spool",
111                    list: "/spool",
112                    create: "/spool/create",
113                    clone: "/spool/clone/:id",
114                    edit: "/spool/edit/:id",
115                    show: "/spool/show/:id",
116                    meta: {
117                      canDelete: true,
118                      icon: <FileOutlined />,
119                    },
120                  },
121                  {
122                    name: "filament",
123                    list: "/filament",
124                    create: "/filament/create",
125                    clone: "/filament/clone/:id",
126                    edit: "/filament/edit/:id",
127                    show: "/filament/show/:id",
128                    meta: {
129                      canDelete: true,
130                      icon: <HighlightOutlined />,
131                    },
132                  },
133                  {
134                    name: "vendor",
135                    list: "/vendor",
136                    create: "/vendor/create",
137                    clone: "/vendor/clone/:id",
138                    edit: "/vendor/edit/:id",
139                    show: "/vendor/show/:id",
140                    meta: {
141                      canDelete: true,
142                      icon: <UserOutlined />,
143                    },
144                  },
145                  {
146                    name: "locations",
147                    list: "/locations",
148                    meta: {
149                      canDelete: false,
150                      icon: <TableOutlined />,
151                    },
152                  },
153                  {
154                    name: "settings",
155                    list: "/settings",
156                    meta: {
157                      canDelete: false,
158                      icon: <ToolOutlined />,
159                    },
160                  },
161                  {
162                    name: "help",
163                    list: "/help",
164                    meta: {
165                      canDelete: false,
166                      icon: <QuestionOutlined />,
167                    },
168                  },
169                ]}
170                options={{
171                  syncWithLocation: true,
172                  warnWhenUnsavedChanges: true,
173                  disableTelemetry: true,
174                }}
175              >
176                <Routes>
177                  <Route
178                    element={
179                      <SpoolmanLayout>
180                        <Outlet />
181                      </SpoolmanLayout>
182                    }
183                  >
184                    <Route index element={<LoadablePage name="home" />} />
185                    <Route path="/spool">
186                      <Route index element={<LoadableResourcePage resource="spools" page="list" />} />
187                      <Route
188                        path="create"
189                        element={<LoadableResourcePage resource="spools" page="create" mode="create" />}
190                      />
191                      <Route
192                        path="clone/:id"
193                        element={<LoadableResourcePage resource="spools" page="create" mode="clone" />}
194                      />
195                      <Route path="edit/:id" element={<LoadableResourcePage resource="spools" page="edit" />} />
196                      <Route path="show/:id" element={<LoadableResourcePage resource="spools" page="show" />} />
197                      <Route path="print" element={<LoadablePage name="printing" />} />
198                    </Route>
199                    <Route path="/filament">
200                      <Route index element={<LoadableResourcePage resource="filaments" page="list" />} />
201                      <Route
202                        path="create"
203                        element={<LoadableResourcePage resource="filaments" page="create" mode="create" />}
204                      />
205                      <Route
206                        path="clone/:id"
207                        element={<LoadableResourcePage resource="filaments" page="create" mode="clone" />}
208                      />
209                      <Route path="edit/:id" element={<LoadableResourcePage resource="filaments" page="edit" />} />
210                      <Route path="show/:id" element={<LoadableResourcePage resource="filaments" page="show" />} />
211                    </Route>
212                    <Route path="/vendor">
213                      <Route index element={<LoadableResourcePage resource="vendors" page="list" />} />
214                      <Route
215                        path="create"
216                        element={<LoadableResourcePage resource="vendors" page="create" mode="create" />}
217                      />
218                      <Route
219                        path="clone/:id"
220                        element={<LoadableResourcePage resource="vendors" page="create" mode="clone" />}
221                      />
222                      <Route path="edit/:id" element={<LoadableResourcePage resource="vendors" page="edit" />} />
223                      <Route path="show/:id" element={<LoadableResourcePage resource="vendors" page="show" />} />
224                    </Route>
225                    <Route path="/settings/*" element={<LoadablePage name="settings" />} />
226                    <Route path="/help" element={<LoadablePage name="help" />} />
227                    <Route path="/locations" element={<LoadablePage name="locations" />} />
228                    <Route path="*" element={<ErrorComponent />} />
229                  </Route>
230                </Routes>
231  
232                <RefineKbar />
233                <UnsavedChangesNotifier />
234                <DocumentTitleHandler />
235                <ReactQueryDevtools />
236                <Favicon url={getBasePath() + "/favicon.svg"} />
237              </Refine>
238            </ConfigProvider>
239          </ColorModeContextProvider>
240        </RefineKbarProvider>
241      </BrowserRouter>
242    );
243  }
244  
245  export default App;