Compare commits
5 Commits
0f211115cf
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66af185358 | ||
|
|
4b869ffe7a | ||
|
|
67fa902c75 | ||
|
|
2d2ed497ca | ||
|
|
05b5843c08 |
@@ -26,6 +26,6 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
config: {
|
||||
'nav2.api': '/api',
|
||||
"nav2.api": "/api"
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,39 +1,45 @@
|
||||
import globals from "globals"
|
||||
import pluginJs from "@eslint/js"
|
||||
import tseslint from "typescript-eslint"
|
||||
import pluginReact from "eslint-plugin-react"
|
||||
import globals from 'globals'
|
||||
import pluginJs from '@eslint/js'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import pluginReact from 'eslint-plugin-react'
|
||||
import stylistic from '@stylistic/eslint-plugin'
|
||||
|
||||
export default [
|
||||
{ files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"] },
|
||||
{ files: ['**/*.{js,mjs,cjs,ts,jsx,tsx}'] },
|
||||
{ languageOptions: { globals: globals.browser } },
|
||||
pluginJs.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
pluginReact.configs.flat.recommended,
|
||||
{
|
||||
plugins: {
|
||||
'@stylistic': stylistic
|
||||
'@stylistic': stylistic,
|
||||
},
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
"react/prop-types": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn", // or "error"
|
||||
rules: {
|
||||
'no-unused-vars': 'off',
|
||||
'react/prop-types': 'off',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn', // or "error"
|
||||
{
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_",
|
||||
"caughtErrorsIgnorePattern": "^_"
|
||||
}
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
caughtErrorsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
"sort-imports": ["error", {
|
||||
"ignoreCase": false,
|
||||
"ignoreDeclarationSort": true,
|
||||
"ignoreMemberSort": false,
|
||||
"memberSyntaxSortOrder": ["none", "all", "multiple", "single"],
|
||||
"allowSeparatedGroups": true
|
||||
}],
|
||||
semi: ["error", "never"],
|
||||
'sort-imports': [
|
||||
'error',
|
||||
{
|
||||
ignoreCase: false,
|
||||
ignoreDeclarationSort: true,
|
||||
ignoreMemberSort: false,
|
||||
memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'],
|
||||
allowSeparatedGroups: true,
|
||||
},
|
||||
],
|
||||
semi: ['error', 'never'],
|
||||
'@stylistic/indent': ['error', 2],
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
ignores: ['stubs/'],
|
||||
},
|
||||
]
|
||||
|
||||
117
package-lock.json
generated
117
package-lock.json
generated
@@ -12,6 +12,8 @@
|
||||
"@brojs/cli": "1.6.1",
|
||||
"@chakra-ui/react": "^2.10.3",
|
||||
"@eslint/js": "^9.11.0",
|
||||
"@redux-devtools/extension": "^3.3.0",
|
||||
"@reduxjs/toolkit": "^2.5.0",
|
||||
"@stylistic/eslint-plugin": "^2.8.0",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
@@ -24,7 +26,10 @@
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.53.1",
|
||||
"react-icons": "^5.3.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"redux": "^5.0.1",
|
||||
"redux-thunk": "^3.1.0",
|
||||
"typescript-eslint": "^8.6.0"
|
||||
}
|
||||
},
|
||||
@@ -2435,6 +2440,53 @@
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@redux-devtools/extension": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@redux-devtools/extension/-/extension-3.3.0.tgz",
|
||||
"integrity": "sha512-X34S/rC8S/M1BIrkYD1mJ5f8vlH0BDqxXrs96cvxSBo4FhMdbhU+GUGsmNYov1xjSyLMHgo8NYrUG8bNX7525g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2",
|
||||
"immutable": "^4.3.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"redux": "^3.1.0 || ^4.0.0 || ^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@reduxjs/toolkit": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.5.0.tgz",
|
||||
"integrity": "sha512-awNe2oTodsZ6LmRqmkFhtb/KH03hUhxOamEQy411m3Njj3BbFvoBovxo4Q1cBWnV1ErprVj9MlF0UPXkng0eyg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"immer": "^10.0.3",
|
||||
"redux": "^5.0.1",
|
||||
"redux-thunk": "^3.1.0",
|
||||
"reselect": "^5.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
|
||||
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-redux": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@reduxjs/toolkit/node_modules/immer": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
|
||||
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/immer"
|
||||
}
|
||||
},
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz",
|
||||
@@ -2566,6 +2618,12 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/use-sync-external-store": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
||||
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz",
|
||||
@@ -6340,6 +6398,12 @@
|
||||
"url": "https://opencollective.com/immer"
|
||||
}
|
||||
},
|
||||
"node_modules/immutable": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz",
|
||||
"integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||
@@ -8392,6 +8456,29 @@
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-redux": {
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/use-sync-external-store": "^0.0.6",
|
||||
"use-sync-external-store": "^1.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.2.25 || ^19",
|
||||
"react": "^18.0 || ^19",
|
||||
"redux": "^5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"redux": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-remove-scroll": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz",
|
||||
@@ -8564,6 +8651,21 @@
|
||||
"recursive-watch": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/redux": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/redux-thunk": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
|
||||
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"redux": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/reflect.getprototypeof": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz",
|
||||
@@ -8680,6 +8782,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/reselect": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.8",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||
@@ -9976,6 +10084,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
|
||||
"integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
"@brojs/cli": "1.6.1",
|
||||
"@chakra-ui/react": "^2.10.3",
|
||||
"@eslint/js": "^9.11.0",
|
||||
"@redux-devtools/extension": "^3.3.0",
|
||||
"@reduxjs/toolkit": "^2.5.0",
|
||||
"@stylistic/eslint-plugin": "^2.8.0",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
@@ -31,7 +33,10 @@
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.53.1",
|
||||
"react-icons": "^5.3.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"redux": "^5.0.1",
|
||||
"redux-thunk": "^3.1.0",
|
||||
"typescript-eslint": "^8.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
26
src/__data__/actions/stars.ts
Normal file
26
src/__data__/actions/stars.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { createAction } from '@reduxjs/toolkit'
|
||||
|
||||
import * as types from '../const'
|
||||
|
||||
export const setStars = createAction<{ id: string, value: number }>(types.SET_STARS)
|
||||
export const incrementStars = createAction<{ id: string }>(types.INCREMENT_STARS)
|
||||
export const decrementStars = createAction<{ id: string }>(types.DECREMENT_STARS)
|
||||
|
||||
// export const setStarsOld = (payload) => ({
|
||||
// type: types.SET_STARS,
|
||||
// payload,
|
||||
// })
|
||||
|
||||
// export const incrementStart = (userId: string) => ({
|
||||
// type: types.INCREMENT_STARS,
|
||||
// payload: {
|
||||
// id: userId
|
||||
// }
|
||||
// })
|
||||
|
||||
// export const decrementStart = (userId: string) => ({
|
||||
// type: types.INCREMENT_STARS,
|
||||
// payload: {
|
||||
// id: userId
|
||||
// }
|
||||
// })
|
||||
33
src/__data__/actions/users.ts
Normal file
33
src/__data__/actions/users.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import * as types from '../const'
|
||||
import { starsSlice } from '../slices/stars'
|
||||
|
||||
import { setStars } from './stars'
|
||||
|
||||
const createAction = <Payload>(type: string) => (payload?: Payload) => ({ type, payload })
|
||||
|
||||
const fetchUsers = createAction(types.USER_FETCH)
|
||||
const success = createAction(types.USER_FETCH_SUCCESS)
|
||||
const error = createAction<string>(types.USER_FETCH_ERROR)
|
||||
export const reset = createAction(types.USER_FETCH_RESET)
|
||||
|
||||
export const getUsers = () => async (dispatch, getState) => {
|
||||
dispatch(fetchUsers())
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/users/')
|
||||
|
||||
if (response.ok) {
|
||||
const data = (await response.json()).body
|
||||
dispatch(success(data))
|
||||
|
||||
Object.keys(data).forEach(userId => {
|
||||
dispatch(starsSlice.actions.setStars({ id: userId, value: data[userId].rated }))
|
||||
})
|
||||
} else {
|
||||
throw 'Что-то пошло не так'
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
dispatch(error('Что-то пошло не так'))
|
||||
}
|
||||
}
|
||||
8
src/__data__/const.ts
Normal file
8
src/__data__/const.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export const SET_STARS = 'SET_STARS'
|
||||
export const INCREMENT_STARS = 'INCREMENT_STARS'
|
||||
export const DECREMENT_STARS = 'DECREMENT_STARS'
|
||||
|
||||
export const USER_FETCH = 'USER_FETCH'
|
||||
export const USER_FETCH_SUCCESS = 'USER_FETCH_SUCCESS'
|
||||
export const USER_FETCH_ERROR = 'USER_FETCH_ERROR'
|
||||
export const USER_FETCH_RESET = 'USER_FETCH_RESET'
|
||||
15
src/__data__/model/index.ts
Normal file
15
src/__data__/model/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export interface BaseResponse<Body> {
|
||||
success: boolean;
|
||||
body: Body;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
age: number;
|
||||
surname: null;
|
||||
email: null;
|
||||
rated: number;
|
||||
avatar: string;
|
||||
friends: User[];
|
||||
}
|
||||
48
src/__data__/reducers/stars.ts
Normal file
48
src/__data__/reducers/stars.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
import * as types from '../const'
|
||||
|
||||
const initialState = {
|
||||
friends: {} as Record<string, number>,
|
||||
user: {},
|
||||
}
|
||||
|
||||
const setStars = (state, action) => ({
|
||||
...state,
|
||||
friends: {
|
||||
...state.friends,
|
||||
[action.payload.id]: action.payload.value,
|
||||
},
|
||||
})
|
||||
|
||||
const changeStars = (value) => (state, action) => ({
|
||||
...state,
|
||||
friends: {
|
||||
...state.friends,
|
||||
[action.payload.id]: state.friends[action.payload.id] + value,
|
||||
},
|
||||
})
|
||||
|
||||
const createReducer = <State>(initialState: State, reducers) => (state = initialState, action): State => {
|
||||
const reducer = reducers[action.type]
|
||||
if (!reducer) return state
|
||||
return reducer(state, action)
|
||||
}
|
||||
|
||||
export const starsReducer = createReducer(initialState, {
|
||||
[types.SET_STARS]: setStars,
|
||||
[types.INCREMENT_STARS]: changeStars(1),
|
||||
[types.DECREMENT_STARS]: changeStars(-1),
|
||||
})
|
||||
|
||||
const slice = createSlice({
|
||||
name: 'stars',
|
||||
initialState: {},
|
||||
reducers: {
|
||||
setStars(state, action) {
|
||||
state[action.payload.id] = action.payload.value
|
||||
},
|
||||
changeStars,
|
||||
},
|
||||
|
||||
})
|
||||
50
src/__data__/reducers/users.ts
Normal file
50
src/__data__/reducers/users.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import * as types from '../const'
|
||||
|
||||
const createReducer = <State>(initialState: State, reducers) => (state = initialState, action): State => {
|
||||
const reducer = reducers[action.type]
|
||||
if (!reducer) return state
|
||||
return reducer(state, action)
|
||||
}
|
||||
|
||||
export enum Statuses {
|
||||
IDLE = 'IDLE',
|
||||
FETCHING = 'FETCHING',
|
||||
SUCCESS = 'SUCCESS',
|
||||
ERROR = 'ERROR',
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
data: null,
|
||||
status: Statuses.IDLE,
|
||||
error: null as null | string,
|
||||
}
|
||||
|
||||
const startFetching = (state) => ({
|
||||
...state,
|
||||
error: null,
|
||||
status: Statuses.FETCHING,
|
||||
})
|
||||
|
||||
const fetchSuccess = (state, action) => ({
|
||||
...state,
|
||||
error: null,
|
||||
data: action.payload,
|
||||
status: Statuses.SUCCESS,
|
||||
})
|
||||
|
||||
const fetchError = (state, action) => ({
|
||||
...state,
|
||||
error: action.payload,
|
||||
status: Statuses.ERROR,
|
||||
})
|
||||
|
||||
const reset = () => ({
|
||||
...initialState
|
||||
})
|
||||
|
||||
export const reducer = createReducer(initialState, {
|
||||
[types.USER_FETCH]: startFetching,
|
||||
[types.USER_FETCH_SUCCESS]: fetchSuccess,
|
||||
[types.USER_FETCH_ERROR]: fetchError,
|
||||
[types.USER_FETCH_RESET]: reset,
|
||||
})
|
||||
1
src/__data__/selectors/index.ts
Normal file
1
src/__data__/selectors/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * as userSelectors from './users'
|
||||
8
src/__data__/selectors/rootSelector.ts
Normal file
8
src/__data__/selectors/rootSelector.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { createSelector } from '@reduxjs/toolkit'
|
||||
|
||||
import { StoreType } from '../store'
|
||||
|
||||
export const rootSelector = createSelector(
|
||||
(state: StoreType) => state,
|
||||
(state: StoreType) => state
|
||||
)
|
||||
12
src/__data__/selectors/users.ts
Normal file
12
src/__data__/selectors/users.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { createSelector } from '@reduxjs/toolkit'
|
||||
|
||||
import { StoreType } from '../store'
|
||||
import { Statuses } from '../reducers/users'
|
||||
|
||||
import { rootSelector } from './rootSelector'
|
||||
|
||||
const usersRootSelector = createSelector(rootSelector, (state: StoreType) => state.user)
|
||||
|
||||
export const isLoading = createSelector(usersRootSelector, (state) => state.status === Statuses.FETCHING)
|
||||
export const data = createSelector(usersRootSelector, (state) => state.data)
|
||||
export const error = createSelector(usersRootSelector, (state) => state.error)
|
||||
32
src/__data__/service/main.ts
Normal file
32
src/__data__/service/main.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
|
||||
import { getConfigValue } from '@brojs/cli'
|
||||
import { BaseResponse, User } from '../model'
|
||||
|
||||
export const api = createApi({
|
||||
reducerPath: 'api',
|
||||
baseQuery: fetchBaseQuery({
|
||||
baseUrl: getConfigValue('nav2.api'),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}),
|
||||
tagTypes: ['Users'],
|
||||
endpoints: (builder) => ({
|
||||
users: builder.query<Record<string, User>, void>({
|
||||
query: () => ({
|
||||
url: '/users',
|
||||
}),
|
||||
providesTags: ['Users'],
|
||||
transformResponse: (response: BaseResponse<User[]>) =>
|
||||
response.body.reduce((acc, user) => ({ ...acc, [user.id]: user }), {}),
|
||||
}),
|
||||
updateRating: builder.mutation<void, { userId: string, rating: number }>({
|
||||
query: ({ userId, rating }) => ({
|
||||
url: `/user/rate/${userId}`,
|
||||
method: 'POST',
|
||||
body: { rating },
|
||||
}),
|
||||
invalidatesTags: ['Users'],
|
||||
}),
|
||||
}),
|
||||
})
|
||||
23
src/__data__/slices/stars.ts
Normal file
23
src/__data__/slices/stars.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { type PayloadAction, createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
const initialState = {
|
||||
friends: {} as Record<string, number>,
|
||||
user: {},
|
||||
}
|
||||
|
||||
export const starsSlice = createSlice({
|
||||
name: 'stars',
|
||||
initialState,
|
||||
reducers: {
|
||||
setStars(state, action) {
|
||||
state.friends[action.payload.id] = action.payload.value
|
||||
},
|
||||
increment(state, action: PayloadAction<{ id: string }>) {
|
||||
state.friends[action.payload.id] += 1
|
||||
},
|
||||
decrement(state, action) {
|
||||
state.friends[action.payload.id] -= 1
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
23
src/__data__/store.ts
Normal file
23
src/__data__/store.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { configureStore } from '@reduxjs/toolkit'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
import { reducer as usersFetchReducer } from './reducers/users'
|
||||
import { starsSlice } from './slices/stars'
|
||||
import { api } from './service/main'
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
user: usersFetchReducer,
|
||||
[starsSlice.reducerPath]: starsSlice.reducer,
|
||||
[api.reducerPath]: api.reducer,
|
||||
},
|
||||
devTools: {
|
||||
name: 'stars',
|
||||
},
|
||||
middleware: (getDefaultMiddleware) => getDefaultMiddleware()
|
||||
.concat(api.middleware),
|
||||
})
|
||||
|
||||
export type StoreType = ReturnType<typeof store.getState>
|
||||
|
||||
export const useAppSelector = useSelector<StoreType>
|
||||
17
src/app.tsx
17
src/app.tsx
@@ -1,10 +1,14 @@
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
import { ChakraProvider } from '@chakra-ui/react'
|
||||
import { Provider } from 'react-redux'
|
||||
import { ApiProvider } from '@reduxjs/toolkit/query/react'
|
||||
|
||||
import { Dashboard } from './dashboard'
|
||||
import { stars as starsContext } from './__data__/context'
|
||||
import { users } from './__data__/users'
|
||||
import { store } from './__data__/store'
|
||||
import { api } from './__data__/service/main'
|
||||
|
||||
const App = () => {
|
||||
const [stars, setStar] = useState(
|
||||
@@ -14,8 +18,11 @@ const App = () => {
|
||||
),
|
||||
)
|
||||
|
||||
const updateStar = useCallback((userId: string, rate: number) =>
|
||||
setStar((state) => ({ ...state, [userId]: rate })), [setStar])
|
||||
const updateStar = useCallback(
|
||||
(userId: string, rate: number) =>
|
||||
setStar((state) => ({ ...state, [userId]: rate })),
|
||||
[setStar],
|
||||
)
|
||||
return (
|
||||
<starsContext.Provider
|
||||
value={{
|
||||
@@ -25,7 +32,11 @@ const App = () => {
|
||||
>
|
||||
<ChakraProvider>
|
||||
<BrowserRouter>
|
||||
<Dashboard />
|
||||
<Provider store={store}>
|
||||
{/* <ApiProvider api={api}> */}
|
||||
<Dashboard />
|
||||
{/* </ApiProvider> */}
|
||||
</Provider>
|
||||
</BrowserRouter>
|
||||
</ChakraProvider>
|
||||
</starsContext.Provider>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { useForm, Controller } from 'react-hook-form'
|
||||
import { Box, Input } from '@chakra-ui/react'
|
||||
import { Box, Button, Input, Stack } from '@chakra-ui/react'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { useDispatch } from 'react-redux'
|
||||
|
||||
type Inputs = {
|
||||
name: string
|
||||
age: string
|
||||
}
|
||||
|
||||
export const FormTest = () => {
|
||||
export const FormTest = ({ name, age, onOpenModal, onCancel }) => {
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
@@ -16,45 +17,69 @@ export const FormTest = () => {
|
||||
reset,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm<Inputs>()
|
||||
|
||||
const [name, setName] = React.useState('')
|
||||
const [age, setAge] = React.useState('12')
|
||||
const ageRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
ageRef.current.focus()
|
||||
}, [])
|
||||
} = useForm<Inputs>({
|
||||
mode: 'onChange',
|
||||
defaultValues: {
|
||||
name: name,
|
||||
age: age,
|
||||
},
|
||||
})
|
||||
|
||||
const onSibmit = ({ name, age }) => {
|
||||
// console.log(1111111, name, age)
|
||||
// console.log(name, age)
|
||||
}
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const handleOpenModalClick = () => {
|
||||
const data = watch()
|
||||
dispatch({ type: 'OPEN_MODAL', payload: data })
|
||||
onOpenModal()
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
as="form"
|
||||
flexDirection="column"
|
||||
display="flex"
|
||||
onSubmit={handleSubmit(onSibmit)}
|
||||
>
|
||||
<label>
|
||||
Name:
|
||||
<Controller
|
||||
control={control}
|
||||
name="name"
|
||||
rules={{
|
||||
required: 'required 4 now',
|
||||
minLength: { value: 4, message: 'min 4 now' },
|
||||
}}
|
||||
render={({ field, fieldState, formState }) => <Input {...field} />}
|
||||
/>
|
||||
</label>
|
||||
<>
|
||||
<Box
|
||||
as="form"
|
||||
flexDirection="column"
|
||||
display="flex"
|
||||
onSubmit={handleSubmit(console.log)}
|
||||
>
|
||||
<label>
|
||||
Name:
|
||||
<Controller
|
||||
control={control}
|
||||
name="name"
|
||||
rules={{
|
||||
required: 'required 4 now',
|
||||
minLength: { value: 4, message: 'min 4 now' },
|
||||
}}
|
||||
render={({ field }) => <Input {...field} />}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Age:
|
||||
<input type="number" ref={ageRef} defaultValue={age} />
|
||||
</label>
|
||||
<button type="submit">Submit</button>
|
||||
</Box>
|
||||
<label>
|
||||
Age:
|
||||
<Controller
|
||||
control={control}
|
||||
name="age"
|
||||
render={({ field }) => (
|
||||
<Input type="number" {...field} />
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
<Stack gap={4} pt={8}>
|
||||
<Button variant="solid" type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
<Button variant="outline" type="button" onClick={handleOpenModalClick}>
|
||||
Продолжить в модальном окне
|
||||
</Button>
|
||||
<Button variant="ghost" type="button" onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -20,14 +20,12 @@ import { FormTest } from './from'
|
||||
import { Stars } from '../stars'
|
||||
import { stars as starsContext } from '../../__data__/context'
|
||||
import { useUsers } from '../../hooks'
|
||||
|
||||
type User = Record<string, unknown> & {
|
||||
id: string
|
||||
name: string
|
||||
avatar?: string
|
||||
rated: number
|
||||
friends?: User[]
|
||||
}
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { decrementStars, incrementStars } from '../../__data__/actions/stars'
|
||||
import { useAppSelector } from '../../__data__/store'
|
||||
import { starsSlice } from '../../__data__/slices/stars'
|
||||
import { User } from '../../__data__/model'
|
||||
import { api } from '../../__data__/service/main'
|
||||
|
||||
const features = getFeatures('nav2')
|
||||
|
||||
@@ -35,16 +33,29 @@ export const Profile = ({
|
||||
user,
|
||||
isLink,
|
||||
title,
|
||||
onOpenModal,
|
||||
}: {
|
||||
user: User
|
||||
isLink?: boolean
|
||||
title?: string
|
||||
onOpenModal?: () => void
|
||||
}) => {
|
||||
// const [rated, setRated] = useState(user.rated || 0)
|
||||
const [editProfile, setEditProfile] = useState(false)
|
||||
|
||||
return (
|
||||
<Box mt={3} borderWidth="1px" p={3} overflowX="hidden">
|
||||
{!isLink && <FormTest />}
|
||||
{!isLink && editProfile && (
|
||||
<FormTest
|
||||
onCancel={() => setEditProfile(false)}
|
||||
onOpenModal={onOpenModal}
|
||||
age={user.age}
|
||||
name={user.name}
|
||||
/>
|
||||
)}
|
||||
{onOpenModal && !editProfile && (
|
||||
<Button onClick={() => setEditProfile(true)}>Редактировать</Button>
|
||||
)}
|
||||
<Heading as="h2">{title || 'Данные профиля'}</Heading>
|
||||
<Box m={3}>
|
||||
<Card width={'fit-content'} shadow="2xl">
|
||||
@@ -59,7 +70,7 @@ export const Profile = ({
|
||||
<CardFooter>
|
||||
{features['stars'] && (
|
||||
<Stars
|
||||
count={Number(features['stars']?.value) * 2}
|
||||
count={Number(features['stars']?.value)}
|
||||
userId={user.id}
|
||||
// rated={rated}
|
||||
// setRated={setRated}
|
||||
@@ -68,15 +79,15 @@ export const Profile = ({
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</Box>
|
||||
{!isLink &&
|
||||
{/* {!isLink &&
|
||||
features['buttons'] &&
|
||||
user.friends?.map((friend) => (
|
||||
<Counter
|
||||
key={friend.id}
|
||||
// value={rated} setValue={setRated}
|
||||
userId={friend.id}
|
||||
/>
|
||||
))}
|
||||
user.friends?.map((friend) => ( */}
|
||||
<Counter
|
||||
key={user.id}
|
||||
// value={rated} setValue={setRated}
|
||||
userId={user.id}
|
||||
/>
|
||||
{/* ))} */}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -125,43 +136,56 @@ const Form = memo<{ initialState: string; onSubmit(value: string): void }>(
|
||||
|
||||
Form.displayName = 'FormMemo'
|
||||
|
||||
const withStars =
|
||||
(Component) =>
|
||||
({ userId }) => {
|
||||
const { stars, setStar } = useContext(starsContext)
|
||||
// const withStars =
|
||||
// (Component) =>
|
||||
// ( { userId }) => {
|
||||
// const { stars, setStar } = useContext(starsContext)
|
||||
|
||||
const addStar = useCallback(
|
||||
() => setStar(userId, stars[userId] + 1),
|
||||
[userId, setStar],
|
||||
)
|
||||
const subStar = useCallback(
|
||||
() => setStar(userId, stars[userId] - 1),
|
||||
[userId, setStar],
|
||||
)
|
||||
// const addStar = useCallback(
|
||||
// () => setStar(userId, stars[userId] + 1),
|
||||
// [userId, setStar],
|
||||
// )
|
||||
// const subStar = useCallback(
|
||||
// () => setStar(userId, stars[userId] - 1),
|
||||
// [userId, setStar],
|
||||
// )
|
||||
|
||||
return (
|
||||
<Component
|
||||
userId={userId}
|
||||
stars={stars[userId]}
|
||||
addStar={addStar}
|
||||
subStar={subStar}
|
||||
/>
|
||||
)
|
||||
// return (
|
||||
// <Component
|
||||
// userId={userId}
|
||||
// stars={stars[userId]}
|
||||
// addStar={addStar}
|
||||
// subStar={subStar}
|
||||
// />
|
||||
// )
|
||||
// }
|
||||
|
||||
const Counter = ({
|
||||
// stars, addStar, subStar,
|
||||
userId,
|
||||
}: {
|
||||
userId: string
|
||||
}) => {
|
||||
// const { rate, setUserRate } = useUsers((state) => state[userId].rated)
|
||||
// const rate = useSelector((store) => store.stars.friends)
|
||||
const { data, error, isLoading } = api.useUsersQuery()
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const handleIncrement = () => {
|
||||
dispatch(starsSlice.actions.increment({ id: userId }))
|
||||
}
|
||||
|
||||
const Counter = ({ stars, addStar, subStar, userId }: { userId: 'some-user-id' | '2'}) => {
|
||||
const { rate, setUserRate } = useUsers((state) => state[userId].rated)
|
||||
|
||||
return (
|
||||
<Center>
|
||||
<VStack>
|
||||
<Heading>{rate}</Heading>
|
||||
<Heading>{data?.[userId]?.rated}</Heading>
|
||||
|
||||
<Button onClick={() => setUserRate(userId, rate + 1)}>+</Button>
|
||||
<Button onClick={() => setUserRate(userId, rate - 1)}>-</Button>
|
||||
<Button onClick={handleIncrement}>+</Button>
|
||||
<Button onClick={() => dispatch(starsSlice.actions.decrement({ id: userId }))}>-</Button>
|
||||
</VStack>
|
||||
</Center>
|
||||
)
|
||||
}
|
||||
|
||||
const CounterWithStars = withStars(Counter)
|
||||
// const CounterWithStars = withStars(Counter)
|
||||
|
||||
1
src/components/profileEdit/index.ts
Normal file
1
src/components/profileEdit/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { ProfileEditModal } from './profileEdit'
|
||||
114
src/components/profileEdit/profileEdit.tsx
Normal file
114
src/components/profileEdit/profileEdit.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import { Box, Button, Input, Stack } from '@chakra-ui/react'
|
||||
import React from 'react'
|
||||
import { Controller, useForm } from 'react-hook-form'
|
||||
import {
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
} from '@chakra-ui/react'
|
||||
|
||||
type Inputs = {
|
||||
name: string
|
||||
lastName: string
|
||||
|
||||
age: string
|
||||
}
|
||||
|
||||
export const ProfileEditModal = ({ onClose }) => {
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
handleSubmit,
|
||||
watch,
|
||||
reset,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm<Inputs>({
|
||||
mode: 'onChange',
|
||||
defaultValues: {
|
||||
name: 'name',
|
||||
lastName: 'string',
|
||||
age: '12',
|
||||
},
|
||||
})
|
||||
|
||||
const onSibmit = ({ name, age }) => {
|
||||
// console.log(1111111, name, age)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen onClose={onClose}>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>Modal Title</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<Box
|
||||
as="form"
|
||||
flexDirection="column"
|
||||
display="flex"
|
||||
gap={3}
|
||||
onSubmit={handleSubmit(onSibmit)}
|
||||
>
|
||||
<label>
|
||||
Name:
|
||||
<Controller
|
||||
control={control}
|
||||
name="name"
|
||||
rules={{
|
||||
required: 'required 4 now',
|
||||
minLength: { value: 4, message: 'min 4 now' },
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<Input isInvalid={Boolean(errors.name)} {...field} />
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Age:
|
||||
<Controller
|
||||
control={control}
|
||||
name="age"
|
||||
rules={{
|
||||
required: 'required 4 now',
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
isInvalid={Boolean(errors.age)}
|
||||
type="number"
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
lastName:
|
||||
<Controller
|
||||
control={control}
|
||||
name="lastName"
|
||||
render={({ field }) => (
|
||||
<Input type="number" {...field} />
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
<Stack gap={4} pt={8}>
|
||||
<Button variant="solid" type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
<ModalFooter>
|
||||
<Button variant="outline" type="button" onClick={onClose}>
|
||||
Отмена
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
import { HStack, Icon } from '@chakra-ui/react'
|
||||
import { HStack, Icon, transform } from '@chakra-ui/react'
|
||||
import { FaRegStar, FaStar } from 'react-icons/fa6'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { getConfigValue } from '@brojs/cli'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
import { stars } from '../__data__/context'
|
||||
import { useUsers } from '../hooks'
|
||||
import { setStars } from '../__data__/actions/stars'
|
||||
import { api } from '../__data__/service/main'
|
||||
|
||||
|
||||
const useStars = (currentRated, starsCount) => {
|
||||
const [rated, setRated] = useState(currentRated)
|
||||
@@ -25,7 +31,7 @@ export const Stars = ({
|
||||
userId,
|
||||
}: {
|
||||
count: number
|
||||
userId: 'some-user-id' | '2'
|
||||
userId: string
|
||||
// rated: number
|
||||
// setRated: (rate: number) => void
|
||||
}) => {
|
||||
@@ -33,7 +39,29 @@ export const Stars = ({
|
||||
// stars,
|
||||
// setRated: starsSetRated
|
||||
// } = useStars(rated, count)
|
||||
const { rate, setUserRate } = useUsers(state => state[userId].rated)
|
||||
// const { setUserRate } = useUsers(state => state[userId].rated)
|
||||
// const rate = useSelector((store) => store.stars.friends[userId as '2']) ?? 0
|
||||
const dispatch = useDispatch()
|
||||
const { data, error, isLoading } = api.useUsersQuery(void 0)
|
||||
const [updateRating, updateRatingRequest] = api.useUpdateRatingMutation()
|
||||
|
||||
const rate = data?.[userId]?.rated ?? 0
|
||||
|
||||
const handleStarsClick = (value: number) => {
|
||||
// dispatch(setStars({ id: userId, value }))
|
||||
updateRating({ userId, rating: value })
|
||||
// setUserRate(userId, stars)
|
||||
// fetch(getConfigValue('dry-wash.api.url') + '/user-rate', {
|
||||
// method: 'POST',
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json',
|
||||
// },
|
||||
// body: JSON.stringify({
|
||||
// userId,
|
||||
// stars,
|
||||
// }),
|
||||
// })
|
||||
}
|
||||
|
||||
return (
|
||||
<HStack>
|
||||
@@ -43,7 +71,7 @@ export const Stars = ({
|
||||
key={index}
|
||||
color="orange.400"
|
||||
cursor="pointer"
|
||||
onClick={() => setUserRate(userId, index + 1)}
|
||||
onClick={() => handleStarsClick(index + 1)}
|
||||
>
|
||||
<FaRegStar />
|
||||
</Icon>
|
||||
@@ -52,7 +80,7 @@ export const Stars = ({
|
||||
key={index}
|
||||
color="orange.400"
|
||||
cursor="pointer"
|
||||
onClick={() => setUserRate(userId, index + 1)}
|
||||
onClick={() => handleStarsClick(index + 1)}
|
||||
>
|
||||
<FaStar />
|
||||
</Icon>
|
||||
|
||||
@@ -20,4 +20,4 @@ export const Dashboard = () => {
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@ export default () => <App/>
|
||||
|
||||
let rootElement: ReactDOM.Root
|
||||
|
||||
export const mount = async (Сomponent, element = document.getElementById('app')) => {
|
||||
export const mount = async (Сomponent, element = document.getElementById('app'), extraData) => {
|
||||
await config
|
||||
rootElement = ReactDOM.createRoot(element)
|
||||
rootElement.render(<Сomponent/>)
|
||||
rootElement.render(<Сomponent />)
|
||||
|
||||
if(module.hot) {
|
||||
module.hot.accept('./app', ()=> {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Container, Heading } from '@chakra-ui/react'
|
||||
import React, { useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import { Profile } from '../components'
|
||||
@@ -47,6 +47,24 @@ const users = {
|
||||
|
||||
export const ByPage = () => {
|
||||
const params = useParams()
|
||||
const [isLoading, setIsLoading] = useState(null)
|
||||
const [data, setData] = useState(null)
|
||||
const [error, setError] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
const getUser = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/users/' + params.userId)
|
||||
|
||||
const data = await response.json()
|
||||
} catch (e) {
|
||||
alert(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
getUser()
|
||||
}, [])
|
||||
|
||||
|
||||
if (!users[params.userId]) {
|
||||
return <Heading as="h2">Пользователь не найден</Heading>
|
||||
|
||||
@@ -1,15 +1,44 @@
|
||||
import { HStack } from '@chakra-ui/react'
|
||||
import React, { memo } from 'react'
|
||||
import React, { memo, useEffect, useState } from 'react'
|
||||
import { useDispatch } from 'react-redux'
|
||||
|
||||
import { Profile } from '../components'
|
||||
import { users } from '../__data__/users'
|
||||
import { setStars } from '../__data__/actions/stars'
|
||||
import { ProfileEditModal } from '../components/profileEdit'
|
||||
import { getUsers } from '../__data__/actions/users'
|
||||
import { UnknownAction } from 'redux'
|
||||
import { useAppSelector } from '../__data__/store'
|
||||
import { Statuses } from '../__data__/reducers/users'
|
||||
import { userSelectors } from '../__data__/selectors'
|
||||
import { api } from '../__data__/service/main'
|
||||
|
||||
export const Friends = memo(() => {
|
||||
// const [isLoading, setIsLoading] = useState(false)
|
||||
// const [data, setData] = useState(null)
|
||||
// const [error, setError] = useState(null)
|
||||
// const dispatch = useDispatch()
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const { data, error, isLoading } = api.useUsersQuery({})
|
||||
|
||||
// const isLoading = useAppSelector(userSelectors.isLoading)
|
||||
// const data = useAppSelector(userSelectors.data)
|
||||
// const error = useAppSelector(userSelectors.error)
|
||||
|
||||
// useEffect(() => {
|
||||
// dispatch(getUsers() as unknown as UnknownAction)
|
||||
// }, [])
|
||||
|
||||
if(!data || isLoading) return <h1>loading...</h1>
|
||||
|
||||
return (
|
||||
<HStack>
|
||||
<Profile user={users['some-user-id']} />
|
||||
<Profile user={users['2']} />
|
||||
</HStack>
|
||||
<>
|
||||
{showModal && <ProfileEditModal onClose={() => setShowModal(false)} />}
|
||||
<HStack>
|
||||
<Profile onOpenModal={() => setShowModal(true)} user={data['some-user-id']} />
|
||||
<Profile onOpenModal={() => setShowModal(true)} user={data['2']} />
|
||||
</HStack>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -1,8 +1,52 @@
|
||||
const router = require('express').Router();
|
||||
const router2 = require('express').Router();
|
||||
const path = require('node:path')
|
||||
const fs = require('node:fs')
|
||||
|
||||
const timer = (time = 300) => (req, res, next) => setTimeout(next, time);
|
||||
let stubs = {
|
||||
users: 'success'
|
||||
}
|
||||
|
||||
router.use(timer());
|
||||
const timer = (time) => (req, res, next) => {
|
||||
setTimeout(next, time)
|
||||
}
|
||||
|
||||
timer.slow = timer(5000)
|
||||
timer.fast = timer(300)
|
||||
|
||||
// router.use(timer.fast)
|
||||
|
||||
router.post('/user-rate', (req, res) => {
|
||||
res.status(500).send({ ok: false })
|
||||
})
|
||||
router.use('/admin', router2)
|
||||
|
||||
router.get('/users',
|
||||
(req, res, next) => {
|
||||
res.status(stubs.users.includes('error') ? 400 : 200).send(require(`../json/users/${stubs.users}.json`))
|
||||
})
|
||||
|
||||
router.post('/user/rate/:userId', (req, res) => {
|
||||
stubs.users = 'success-incremented'
|
||||
|
||||
res.send({ ok: true })
|
||||
})
|
||||
|
||||
router2.get('/', (req, res) => {
|
||||
res.send(`
|
||||
<h2>Users</h2>
|
||||
<ul>
|
||||
<li><button onclick="fetch('/api/admin/users/success')" style="background-color: ${stubs.users === 'success' ? 'green' : '#ccc'}">success</button></li>
|
||||
<li><button onclick="fetch('/api/admin/users/success-incremented')" style="background-color: ${stubs.users === 'success-incremented' ? 'green' : '#ccc'}">success-incremented</button></li>
|
||||
<li><button onclick="fetch('/api/admin/users/error')" style="background-color: ${stubs.users === 'error' ? 'green' : '#ccc'}">error</button></li>
|
||||
</ul>
|
||||
`)
|
||||
})
|
||||
|
||||
router2.get('/:stubName/:value', (req, res) => {
|
||||
const { stubName, value } = req.params
|
||||
|
||||
stubs[stubName] = value
|
||||
})
|
||||
|
||||
module.exports = router;
|
||||
|
||||
6
stubs/json/users/empty.json
Normal file
6
stubs/json/users/empty.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"success": false,
|
||||
"body": {
|
||||
|
||||
}
|
||||
}
|
||||
43
stubs/json/users/success-incremented.json
Normal file
43
stubs/json/users/success-incremented.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"success": false,
|
||||
"body": [
|
||||
{
|
||||
"id": "some-user-id",
|
||||
"name": "alexandr",
|
||||
"age": 38,
|
||||
"surname": null,
|
||||
"email": null,
|
||||
"rated": 5,
|
||||
"avatar": "https://www.gravatar.com/avatar/6529e885535ef67a3fad810ad71167c2c03f79480936e9b3a714731753cbb47e?d=robohash",
|
||||
"friends": [
|
||||
{
|
||||
"id": "2",
|
||||
"name": "not alexandr",
|
||||
"surname": null,
|
||||
"email": null,
|
||||
"rated": 2,
|
||||
"avatar": "https://www.gravatar.com/avatar/6e?d=robohash"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"name": "not alexandr",
|
||||
"surname": null,
|
||||
"email": null,
|
||||
"age": 24,
|
||||
"rated": 2,
|
||||
"avatar": "https://www.gravatar.com/avatar/6e?d=robohash",
|
||||
"friends": [
|
||||
{
|
||||
"id": "some-user-id",
|
||||
"name": "alexandr",
|
||||
"surname": null,
|
||||
"email": null,
|
||||
"rated": 3,
|
||||
"avatar": "https://www.gravatar.com/avatar/6529e885535ef67a3fad810ad71167c2c03f79480936e9b3a714731753cbb47e?d=robohash"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
43
stubs/json/users/success.json
Normal file
43
stubs/json/users/success.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"success": false,
|
||||
"body": [
|
||||
{
|
||||
"id": "some-user-id",
|
||||
"name": "alexandr",
|
||||
"age": 38,
|
||||
"surname": null,
|
||||
"email": null,
|
||||
"rated": 4,
|
||||
"avatar": "https://www.gravatar.com/avatar/6529e885535ef67a3fad810ad71167c2c03f79480936e9b3a714731753cbb47e?d=robohash",
|
||||
"friends": [
|
||||
{
|
||||
"id": "2",
|
||||
"name": "not alexandr",
|
||||
"surname": null,
|
||||
"email": null,
|
||||
"rated": 2,
|
||||
"avatar": "https://www.gravatar.com/avatar/6e?d=robohash"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"name": "not alexandr",
|
||||
"surname": null,
|
||||
"email": null,
|
||||
"age": 24,
|
||||
"rated": 5,
|
||||
"avatar": "https://www.gravatar.com/avatar/6e?d=robohash",
|
||||
"friends": [
|
||||
{
|
||||
"id": "some-user-id",
|
||||
"name": "alexandr",
|
||||
"surname": null,
|
||||
"email": null,
|
||||
"rated": 3,
|
||||
"avatar": "https://www.gravatar.com/avatar/6529e885535ef67a3fad810ad71167c2c03f79480936e9b3a714731753cbb47e?d=robohash"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user