Skip to content

Commit

Permalink
chore: axios base query with retry; error handling; qr-code scan
Browse files Browse the repository at this point in the history
  • Loading branch information
stephane-segning committed Jul 26, 2024
1 parent ea5e0cf commit ab74759
Show file tree
Hide file tree
Showing 27 changed files with 223 additions and 64 deletions.
4 changes: 2 additions & 2 deletions openapi-config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { ConfigFile } from '@rtk-query/codegen-openapi';

const baseDir = process.env.GEN_FOLDER!;
const baseDir = './src/store/api/gen';

const config: ConfigFile = {
schemaFile: './openapi.yaml',
apiFile: './src/store/empty.api.ts',
apiFile: './src/store/api/empty.api.ts',
apiImport: 'emptySplitApi',
hooks: true,
outputFiles: {
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"prettier": "prettier --write .",
"stylelint:actions": "stylelint --fix \"src/**/*.{css,scss}\"",
"convert-md-to-pdf": "yarn run md-to-pdf ./docs/*.md ./docs/**/*.md",
"rtk:gen": "export GEN_FOLDER=./src/store/gen && rimraf $GEN_FOLDER && mkdir $GEN_FOLDER && rtk-query-codegen-openapi openapi-config.ts && prettier -w $GEN_FOLDER",
"rtk:gen": "rimraf ./src/store/api/gen && mkdir ./src/store/api/gen && rtk-query-codegen-openapi openapi-config.ts && prettier -w ./src/store/api/gen",
"postinstall": "yarn run rtk:gen"
},
"dependencies": {
Expand All @@ -23,6 +23,7 @@
"@sentry/tracing": "^7.114.0",
"@yudiel/react-qr-scanner": "^2.0.4",
"autoprefixer": "^10.4.19",
"axios": "^1.7.2",
"i18next": "^23.11.4",
"md-to-pdf": "^5.2.4",
"postcss": "^8.4.38",
Expand All @@ -36,6 +37,7 @@
"react-router-dom": "^6.23.0",
"redux": "^5.0.1",
"redux-logger": "^3.0.6",
"redux-persist": "^6.0.0",
"redux-toolkit": "^1.1.2",
"tailwindcss": "^3.4.3",
"theme-change": "^2.5.0"
Expand Down
4 changes: 2 additions & 2 deletions src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { JSX } from 'react';
import { RouterProvider } from 'react-router-dom';
import { router } from './router.tsx';
import { Notification } from './components/notification.tsx';
import { Notification } from '@components/notification.tsx';

/**
* The main application component.
*/
export function App(): JSX.Element {
return (
<div className="mx-auto max-w-screen-lg">
<div>
<RouterProvider router={router} />
<Notification />
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface HeaderProps {

interface BackHeaderProps {
title: string;
back: true;
back: string;
}

interface RawTrail {
Expand All @@ -37,7 +37,7 @@ export function Header({
<ArrowLeft />
</Button>
)}
<h1 className="text-2xl">{title}</h1>
<h1 className="text-2xl ml-2">{title}</h1>
<div className="mx-auto" />
{!back && Icon && (
<Button color="ghost" onClick={onIconClick} shape="circle">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Scan } from '@api/scans.api.gen.ts';
import type { Scan } from '@api';
import { Button, Pagination } from 'react-daisyui';
import { ArrowLeft, ArrowRight } from 'react-feather';

Expand All @@ -9,7 +9,7 @@ export interface ScanListDumpProps {
onPrev: () => void;
}

export function ScanListDump({
export function ScanListSimple({
scans,
onPrev,
onNext,
Expand Down
5 changes: 2 additions & 3 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { setupLogging } from './logging';
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './app.tsx';
Expand All @@ -7,8 +6,8 @@ import { I18nextProvider } from 'react-i18next';
import i18n from './i18n';
import './index.scss';
import { Provider } from 'react-redux';
import { store } from './store';
import { isElectron } from './shared/constants.ts';
import { store } from '@store';
import { isElectron, setupLogging } from '@shared';

Sentry.init({
dsn: 'https://9fd06d22381ef360013d83b6b0c8375e@o4507214219313152.ingest.de.sentry.io/4507214225801296',
Expand Down
10 changes: 5 additions & 5 deletions src/router.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createBrowserRouter, Navigate, Outlet } from 'react-router-dom';
import { FloatingConfig } from './components/floating-config.tsx';
import { FloatingConfig } from '@components/floating-config.tsx';

export const router = createBrowserRouter([
{
Expand All @@ -24,11 +24,11 @@ export const router = createBrowserRouter([
children: [
{
path: 'add',
lazy: () => import('./screens/scan.screen'),
lazy: () => import('@screens/scan.screen'),
},
{
path: '',
lazy: () => import('./screens/scan-list.screen'),
lazy: () => import('@screens/scan-list.screen'),
},
],
},
Expand All @@ -37,11 +37,11 @@ export const router = createBrowserRouter([
children: [
{
path: '',
lazy: () => import('./screens/app-config.screen'),
lazy: () => import('@screens/app-config.screen'),
},
{
path: 'scan',
lazy: () => import('./screens/scan-config.screen'),
lazy: () => import('@screens/scan-config.screen'),
},
],
},
Expand Down
16 changes: 5 additions & 11 deletions src/screens/app-config.screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,25 @@ import { t } from 'i18next';
import { Card } from 'react-daisyui';
import { useNavigate } from 'react-router-dom';
import { isElectron } from '../shared/constants.ts';
import { useSelector } from 'react-redux';
import { selectConfigUrl } from '@store';

const ConfigQrCode = lazy(() => import('../components/config.qr-code'));
const ConfigScanButton = lazy(() => import('../components/config-scan.button'));
const ToListScanButton = lazy(
() => import('../components/to-list-scan.button.tsx')
);

const configKey = 'lynx:config';

export const Component: React.FC = () => {
const navigate = useNavigate();

const checkConfig = () => {
const item = localStorage.getItem(configKey);
if (item) {
return JSON.parse(item);
}
};
const checkConfig = useSelector(selectConfigUrl);

useEffect(() => {
const config = checkConfig();
if (config) {
if (checkConfig) {
navigate('/scans');
}
}, [navigate]);
}, [checkConfig, navigate]);
return (
<div className="flex justify-center h-[100vh] items-center p-4">
<Card className="max-w-sm border-0 sm:border-2 sm:bg-base-200">
Expand Down
12 changes: 8 additions & 4 deletions src/screens/scan-config.screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ import {
} from '@yudiel/react-qr-scanner';
import { Button, Dropdown } from 'react-daisyui';
import { Camera } from 'react-feather';
import { setUrlConfig, useAppDispatch } from '../store';
import { setUrlConfig, useAppDispatch } from '@store';
import { useNavigate } from 'react-router-dom';

/**
* ScanConfig screen component
* @constructor React.FC
*/
export const Component: React.FC = () => {
const dispatch = useAppDispatch();
const navigate = useNavigate();
Expand All @@ -19,7 +23,7 @@ export const Component: React.FC = () => {
);
const onScan = (detectedCodes: IDetectedBarcode[]) => {
for (const { rawValue, format } of detectedCodes) {
if (format in ['qr_code', 'rm_qr_code', 'micro_qr_code']) {
if (['qr_code', 'rm_qr_code', 'micro_qr_code'].includes(format)) {
const config = JSON.parse(rawValue) as Record<string, string>;
dispatch(setUrlConfig(config.url));
navigate('..');
Expand All @@ -31,11 +35,11 @@ export const Component: React.FC = () => {
<div className="p-4">
<Header
title="Configs"
back={true}
back=".."
trailing={
<Dropdown horizontal="left">
<Dropdown.Toggle button={false}>
<Button shape="circle">
<Button color="ghost" shape="circle">
<Camera />
</Button>
</Dropdown.Toggle>
Expand Down
6 changes: 3 additions & 3 deletions src/screens/scan-list.screen.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { useGetScansQuery } from '@api/scans.api.gen.ts';
import { useGetScansQuery } from '@api';
import { Loading } from 'react-daisyui';
import { ScanListDump } from '../components/scan-list.dump.tsx';
import { ScanListSimple } from '@components/scan-list.simple.tsx';
import { Plus } from 'react-feather';
import { useNavigate } from 'react-router-dom';
import { Header } from '../components/header.tsx';
Expand All @@ -24,7 +24,7 @@ export const Component: React.FC = () => {
{isLoading && <Loading />}

{scans && (
<ScanListDump
<ScanListSimple
page={page}
scans={scans}
onNext={() => setPage((prevState) => ++prevState)}
Expand Down
6 changes: 5 additions & 1 deletion src/screens/scan.screen.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import React from 'react';
import { Header } from '../components/header.tsx';

/**
* Scan screen
* @constructor React.FC
*/
export const Component: React.FC = () => {
return (
<>
<Header title="Scan" back={true} />
<Header title="Scan" back=".." />
<div className="bg-base-100">TODO: Form & Camera will be here</div>
</>
);
Expand Down
2 changes: 2 additions & 0 deletions src/shared/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './constants';
export * from './logging';
File renamed without changes.
51 changes: 51 additions & 0 deletions src/store/api/axios-base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { BaseQueryFn } from '@reduxjs/toolkit/query';
import axios, { AxiosError, AxiosRequestConfig } from 'axios';
import { RootState } from '@store';

const axiosInstance = axios.create({});

export const axiosBaseQuery =
(): BaseQueryFn<
{
url: string;
method?: AxiosRequestConfig['method'];
body?: AxiosRequestConfig['data'];
params?: AxiosRequestConfig['params'];
headers?: AxiosRequestConfig['headers'];
},
unknown,
unknown
> =>
async ({ url, method, body: data, params, headers }, { getState }) => {
try {
const baseUrl = (getState() as RootState).config.url;
const result = await axiosInstance({
url: baseUrl + url,
method,
data,
params,
headers,
});
return { data: JSON.parse(result.data) };
} catch (error) {
if (error instanceof SyntaxError) {
return {
error: {
status: 500,
data: error.message,
},
};
}
if (error instanceof AxiosError) {
return {
error: {
status: error.response?.status,
data: error.response?.data || error.message,
},
};
}
return {
error,
};
}
};
10 changes: 10 additions & 0 deletions src/store/api/empty.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { createApi, retry } from '@reduxjs/toolkit/query/react';
import { axiosBaseQuery } from './axios-base.ts';

// initialize an empty api service that we'll inject endpoints into later as needed
export const emptySplitApi = createApi({
baseQuery: retry(axiosBaseQuery(), {
maxRetries: 3,
}),
endpoints: () => ({}),
});
3 changes: 3 additions & 0 deletions src/store/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './empty.api.ts';
export * from './gen/files.api.gen.ts';
export * from './gen/scans.api.gen.ts';
7 changes: 0 additions & 7 deletions src/store/empty.api.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/store/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from './empty.api';
export * from './api/empty.api.ts';
export * from './hooks';
export * from './store';
export * from './slices';
Expand Down
9 changes: 4 additions & 5 deletions src/store/middlewares.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Middleware, MiddlewareAPI } from '@reduxjs/toolkit';
import { isRejectedWithValue } from '@reduxjs/toolkit';
import { addNotification } from './slices';
import { AppDispatch } from './types.ts';
import { AppDispatch } from '@store/types.ts';
import { addNotification } from '@store/slices';

export const rtkQueryErrorLogger: Middleware =
({ dispatch }: MiddlewareAPI<AppDispatch>) =>
Expand All @@ -15,10 +15,9 @@ export const rtkQueryErrorLogger: Middleware =
: action.error.message;

const msg =
'error' in (action.payload as Record<string, string>)
? (action.payload as Record<string, string>).error
'data' in (action.payload as Record<string, string>)
? (action.payload as Record<string, string>).data
: message;
console.error(message, msg);
if (msg) dispatch(addNotification(msg));
}

Expand Down
6 changes: 5 additions & 1 deletion src/store/selectors.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from './types.ts';
import { RootState } from '@store/types.ts';

export const selectNotification = createSelector(
(ro: RootState) => ro.notification.messages,
(p) => p.filter(({ message }) => message.length > 0)
);
export const selectConfigUrl = createSelector(
(ro: RootState) => ro.config,
({ url }) => url
);
Loading

0 comments on commit ab74759

Please sign in to comment.