Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New updates #12

Merged
merged 12 commits into from
Feb 3, 2025
Merged
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ This project was bootstrapped with [Vite.js](https://vitejs.dev).
- [Typescript](https://www.typescriptlang.org) - Static type checker
- [Vite](https://vitejs.dev/) - Bundler
- [Tanstack Router](https://tanstack.com/router/latest/docs/framework/react/overview) - Routing (file-based)
- [@svg-use](https://github.com/fpapado/svg-use) - icon management tool
- [Tanstack Query](https://tanstack.com/query/latest/docs/framework/react/overview) - Asynchronous state management
- [@svg-use](https://github.com/fpapado/svg-use) - Icon management tool
- [Eslint](https://eslint.org/) - Code linter
- [Prettier](https://prettier.io/) - Code formatter
- [Husky](https://typicode.github.io/husky/) - commands execution handler on git events
- [CLSX](https://github.com/lukeed/clsx) - classNames management tool
- [Husky](https://typicode.github.io/husky/) - Commands execution handler on git events

</details>

Expand Down Expand Up @@ -227,7 +227,7 @@ Services should:
- Have separate root `src/services` folder
- Each service scope should have its own folder
- Each service scope should have its own file

##### API Services

API services are services that contain API requests functions to be used in API hooks.
Expand Down
10 changes: 8 additions & 2 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import eslint from '@eslint/js';
import pluginTanstackQuery from '@tanstack/eslint-plugin-query';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import react from 'eslint-plugin-react';
import reactHooksPlugin from 'eslint-plugin-react-hooks';
Expand All @@ -8,8 +9,13 @@ import tseslint from 'typescript-eslint';
export default tseslint.config({
ignores: ['**/dist/**', '**/tmp/**', '/src/routeTree.gen.ts'],
files: ['**/*.{js,ts,tsx,cjs,mjs}'],
extends: [eslint.configs.recommended, tseslint.configs.recommended, eslintPluginPrettierRecommended],
plugins: { react, 'react-hooks': reactHooksPlugin },
extends: [
eslint.configs.recommended,
tseslint.configs.recommended,
eslintPluginPrettierRecommended,
pluginTanstackQuery.configs['flat/recommended'],
],
plugins: { react, 'react-hooks': reactHooksPlugin, '@tanstack/query': pluginTanstackQuery },
languageOptions: {
parserOptions: {
project: ['./tsconfig.app.json', './tsconfig.node.json', './tsconfig.json'],
Expand Down
3 changes: 3 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Syne:[email protected]&display=swap" rel="stylesheet" />
</head>

<body>
Expand Down
1,505 changes: 1,005 additions & 500 deletions package-lock.json

Large diffs are not rendered by default.

43 changes: 24 additions & 19 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,39 +23,44 @@
},
"dependencies": {
"@svg-use/react": "^0.4.3",
"@tanstack/react-router": "^1.95.3",
"@tanstack/react-query": "^5.66.0",
"@tanstack/react-router": "^1.99.0",
"axios": "^1.7.9",
"clsx": "^2.1.1",
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react-dom": "^19.0.0",
"zod": "^3.24.1"
},
"devDependencies": {
"@commitlint/cli": "^19.6.1",
"@commitlint/config-conventional": "^19.6.0",
"@eslint/js": "^9.18.0",
"@ianvs/prettier-plugin-sort-imports": "^4.4.0",
"@commitlint/cli": "^19.7.1",
"@commitlint/config-conventional": "^19.7.1",
"@eslint/js": "^9.19.0",
"@ianvs/prettier-plugin-sort-imports": "^4.4.1",
"@svg-use/vite": "^0.1.3",
"@tanstack/router-devtools": "^1.95.3",
"@tanstack/router-vite-plugin": "^1.95.3",
"@types/react": "^19.0.4",
"@types/react-dom": "^19.0.2",
"@tanstack/eslint-plugin-query": "^5.66.0",
"@tanstack/react-query-devtools": "^5.66.0",
"@tanstack/router-devtools": "^1.99.0",
"@tanstack/router-vite-plugin": "^1.99.0",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"eslint": "^9.17.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.37.3",
"eslint": "^9.19.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.3",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^5.1.0",
"globals": "^15.14.0",
"husky": "^9.1.7",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.49",
"postcss": "^8.5.1",
"prettier": "^3.4.2",
"rollup-plugin-visualizer": "^5.14.0",
"stylelint": "^16.12.0",
"stylelint-config-standard": "^36.0.1",
"stylelint": "^16.14.1",
"stylelint-config-standard": "^37.0.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.19.1",
"vite": "^6.0.7",
"typescript-eslint": "^8.22.0",
"vite": "^6.0.11",
"vite-plugin-eslint2": "^5.0.3"
}
}
28 changes: 28 additions & 0 deletions src/api/@axios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import axios from 'axios';
import { getAccessToken } from '@/utils/auth/tokens';

export const http = axios.create({
baseURL: import.meta.env.VITE_API_URL,
adapter: 'fetch',
});

export const httpPrivate = axios.create({
baseURL: import.meta.env.VITE_API_URL,
adapter: 'fetch',
});

httpPrivate.interceptors.request.use(
(config) => {
const token = getAccessToken();

if (token) {
config.headers.Authorization = `Bearer ${token}`;
config.withCredentials = true;
}

return config;
},
(error) => {
return Promise.reject(error);
}
);
17 changes: 17 additions & 0 deletions src/api/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { AxiosRequestConfig } from 'axios';
import { httpPrivate } from '@/api/@axios';

export type RefreshTokenData = {
refreshToken: string;
};

export type RefreshTokenResponseData = {
accessToken: string;
refreshToken: string;
};

export const refreshToken = async (data: RefreshTokenData, config?: AxiosRequestConfig) => {
const response = await httpPrivate.post<RefreshTokenResponseData>('/api/refresh-token', data, config);

return response.data;
};
14 changes: 14 additions & 0 deletions src/api/usersExample.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { AxiosRequestConfig } from 'axios';
import { httpPrivate } from '@/api/@axios';

export const getUsers = async (config?: AxiosRequestConfig) => {
const response = await httpPrivate.get<string[]>('/api/users', config);

return response.data;
};

export const createUser = async (data: string, config?: AxiosRequestConfig) => {
const response = await httpPrivate.post<string>('/api/users', data, config);

return response.data;
};
59 changes: 59 additions & 0 deletions src/hooks/useOptimisticMutation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import type { DataTag, MutationFunction, QueryKey } from '@tanstack/react-query';

type Updater<TQueryFnData, TVariables> = (
_oldData: TQueryFnData | undefined,
_variables: TVariables
) => TQueryFnData | undefined;

type OptimisticProps<
TData = unknown,
TVariables = unknown,
TQueryKey extends QueryKey = QueryKey,
TQueryFnData = unknown,
> = {
mutationFn: MutationFunction<TData, TVariables>;
queryKey: TQueryKey;
updater: Updater<TQueryFnData, TVariables>;
invalidate: () => Promise<void>;
};

export const useOptimisticMutation = <
TData = unknown,
TVariables = unknown,
TQueryFnData = unknown,
TQueryKey extends QueryKey = QueryKey,
TInferredQueryFnData = TQueryKey extends DataTag<unknown, infer TaggedValue> ? TaggedValue : TQueryFnData,
>({
mutationFn,
queryKey,
updater,
invalidate,
}: OptimisticProps<TData, TVariables, TQueryKey, TInferredQueryFnData>) => {
const queryClient = useQueryClient();

return useMutation({
mutationFn,
async onMutate(variables) {
await queryClient.cancelQueries({
queryKey,
});

const snapshot = queryClient.getQueryData(queryKey);

queryClient.setQueryData<TInferredQueryFnData>(queryKey, (old) => {
return updater(old, variables);
});

return () => {
queryClient.setQueryData(queryKey, snapshot);
};
},
onError(_error, _variables, rollback) {
rollback?.();
},
onSettled() {
return invalidate();
},
});
};
12 changes: 0 additions & 12 deletions src/hooks/useSetGlobalCustomProperty.ts

This file was deleted.

11 changes: 11 additions & 0 deletions src/hooks/useSetRootCustomProperty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';

export const useSetRootCustomProperty = (name: string, value?: string) => {
React.useEffect(() => {
if (value) {
document.documentElement.style.setProperty(name, value);
} else {
document.documentElement.style.removeProperty(name);
}
}, [name, value]);
};
6 changes: 5 additions & 1 deletion src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React from 'react';
import { queryClient } from '@/tanstackQuery/@queryClient';
import { configContext as SvgUseConfigContext } from '@svg-use/react';
import { QueryClientProvider } from '@tanstack/react-query';
import { createRouter, RouterProvider } from '@tanstack/react-router';
import ReactDOM from 'react-dom/client';
import type { Config } from '@svg-use/react';
Expand All @@ -25,7 +27,9 @@ const config: Config = {
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<SvgUseConfigContext.Provider value={config}>
<RouterProvider router={router} />
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
</SvgUseConfigContext.Provider>
</React.StrictMode>
);
2 changes: 0 additions & 2 deletions src/modules/Home/components/Counter/index.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { memo, useCallback, useState } from 'react';
import React from 'react';
import clsx from 'clsx';
import s from './Counter.module.css';
import s from './style.module.css';

const Counter: React.FC = () => {
const [count, setCount] = useState(0);
const [count, setCount] = React.useState(0);

const increment = useCallback(() => {
const increment = React.useCallback(() => {
setCount((prev) => {
return prev + 1;
});
}, []);

const decrement = useCallback(() => {
const decrement = React.useCallback(() => {
setCount((prev) => {
return prev - 1;
});
Expand All @@ -34,4 +34,4 @@ const Counter: React.FC = () => {
);
};

export default memo(Counter);
export default React.memo(Counter);
2 changes: 1 addition & 1 deletion src/modules/Home/constants.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const CURRENT_YEAR = new Date().getFullYear();
export const EXAMPLE = 'Example';
1 change: 1 addition & 0 deletions src/modules/Home/hooks/useExampleHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const useExampleHook = () => {};
2 changes: 0 additions & 2 deletions src/modules/Home/index.ts

This file was deleted.

11 changes: 5 additions & 6 deletions src/modules/Home/Home.tsx → src/modules/Home/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { memo } from 'react';
import clsx from 'clsx';
import { CURRENT_YEAR } from './constants';
import type { HomeExampleProps } from './types';
import Counter from './components/Counter';
import { Component as PhenomenonMarkIcon } from '@/icons/phenomenon-mark.svg?svgUse';
import s from './Home.module.css';
import s from './style.module.css';

const Home: React.FC = () => {
const Home: React.FC<HomeExampleProps> = () => {
return (
<main className={clsx(s.wrap, 'full-height')}>
<div className={s.inner}>
Expand All @@ -24,9 +23,9 @@ const Home: React.FC = () => {
</p>
</section>
</div>
<footer className={s.footer}>&copy;&nbsp;{CURRENT_YEAR}, Phenomenon.studio</footer>
<footer className={s.footer}>&copy;&nbsp;{new Date().getFullYear()}, Phenomenon.studio</footer>
</main>
);
};

export default memo(Home);
export default Home;
8 changes: 8 additions & 0 deletions src/modules/Home/schemas/exampleSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { z } from 'zod';

export type ExampleSchema = z.infer<typeof exampleSchema>;

export const exampleSchema = z.object({
name: z.string(),
type: z.string(),
});
File renamed without changes.
4 changes: 4 additions & 0 deletions src/modules/Home/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type HomeExampleProps = {
id?: number;
name?: string;
};
1 change: 1 addition & 0 deletions src/modules/Home/utils/exampleUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const exampleUtil = () => {};
Loading