факит
This commit is contained in:
241
StaticHelper/source/docs.md
Normal file
241
StaticHelper/source/docs.md
Normal file
@@ -0,0 +1,241 @@
|
||||
# Требования к коду
|
||||
|
||||
Используется библиотека `react-router-dom` и `react=markdown`.
|
||||
|
||||
Все компоненты должны быть функциональными.
|
||||
props должны быть сразу распакованы.
|
||||
export без default
|
||||
|
||||
Пример:
|
||||
|
||||
```tsx
|
||||
export function ProfessionPage() {
|
||||
const {professionId} = useParams<{ professionId: string }>();
|
||||
}
|
||||
```
|
||||
|
||||
Используйте современные практики React (например, хуки).
|
||||
При создании новых компонентов добавьте комментарии.
|
||||
|
||||
Если для задачи необходимы новые типы, интерфейсы или вспомогательные компоненты - создавай их в соответвующих файлах.
|
||||
|
||||
# Документария
|
||||
|
||||
## Существующие типы
|
||||
|
||||
`types/api.ts`
|
||||
```ts
|
||||
export interface ApiResponse {
|
||||
body?: object,
|
||||
success: boolean
|
||||
}
|
||||
export interface PageResponse<T>{
|
||||
count: number;
|
||||
next?: string;
|
||||
previous?: string;
|
||||
results: T[]
|
||||
}
|
||||
```
|
||||
|
||||
`types/common.ts`
|
||||
```ts
|
||||
export interface UniqueItem{
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface PageControl<T>{
|
||||
items: T[] | null;
|
||||
hasNext: boolean;
|
||||
hasPrevious: boolean;
|
||||
nextPage(): void;
|
||||
previousPage(): void;
|
||||
}
|
||||
```
|
||||
`types/survey.ts`
|
||||
```ts
|
||||
import {UniqueItem} from "./common.ts";
|
||||
|
||||
export interface Survey extends UniqueItem{
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
interface Statement extends UniqueItem{
|
||||
text: string;
|
||||
score_variable: number;
|
||||
}
|
||||
export enum QuestionType{
|
||||
QuestionPair,
|
||||
AgreementQuestion
|
||||
}
|
||||
export interface Question extends UniqueItem{
|
||||
questionType: QuestionType
|
||||
}
|
||||
export interface QuestionPair extends Question{
|
||||
statements: Statement[]
|
||||
}
|
||||
export interface AgreementQuestion extends Question{
|
||||
question: string;
|
||||
score_variable: number;
|
||||
}
|
||||
export interface ScoreVariable {
|
||||
id: string;
|
||||
name: string;
|
||||
title: string;
|
||||
description: string;
|
||||
survey: number;
|
||||
}
|
||||
export interface Profession extends UniqueItem {
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
export interface Specialty extends UniqueItem {
|
||||
name: string;
|
||||
institution: number;
|
||||
link?: string;
|
||||
}
|
||||
export interface Institution extends UniqueItem {
|
||||
name: string;
|
||||
}
|
||||
```
|
||||
`types/users.ts`
|
||||
```ts
|
||||
import {UniqueItem} from "./common.ts";
|
||||
|
||||
export type UserContextType = {
|
||||
user: Account | undefined;
|
||||
login: (credentials: TokenObtainPair) => Promise<void>;
|
||||
logout: () => void;
|
||||
};
|
||||
export interface ContactData{
|
||||
first_name: string;
|
||||
last_name?: string;
|
||||
middle_name?: string;
|
||||
phone?: string;
|
||||
email?:string;
|
||||
}
|
||||
interface Role extends UniqueItem{
|
||||
title: string;
|
||||
}
|
||||
export interface Account extends UniqueItem, ContactData{
|
||||
login: string;
|
||||
role?: Role;
|
||||
is_staff: boolean;
|
||||
is_active: boolean;
|
||||
}
|
||||
export interface TokenObtainPair {
|
||||
login: string;
|
||||
password: string;
|
||||
}
|
||||
export interface TokenRefresh {
|
||||
refresh: string;
|
||||
}
|
||||
export interface TokenVerify {
|
||||
token: string;
|
||||
}
|
||||
export interface JwtTokenResponse {
|
||||
access: string;
|
||||
refresh: string;
|
||||
}
|
||||
```
|
||||
|
||||
## Способы запросов
|
||||
|
||||
Вместо fetch запросы должны проходить при помощи специальных функций/функции
|
||||
|
||||
`API/common.ts`
|
||||
```ts
|
||||
async function getFetch(path: string) // Get method
|
||||
async function postFetch(
|
||||
path: string,
|
||||
body: object | FormData
|
||||
) // Post method
|
||||
async function patchFetch(
|
||||
path: string,
|
||||
body: object | FormData
|
||||
) // Patch method
|
||||
async function deleteFetch(path: string) // Delete method
|
||||
```
|
||||
|
||||
## Хуки
|
||||
|
||||
### Для загрузки данных
|
||||
|
||||
Для упрощения работы с api созданы следующие хуки
|
||||
|
||||
`API/hooks.ts`
|
||||
```ts
|
||||
function useDataPage<T>(application:string, endpoint: string, params:{[key:string]:string|number}={}, needLoad:boolean=true):PageControl<T>
|
||||
function useData<T>(application:string, endpoint: string, id: string|number, needLoad:boolean=true): T|null|undefined
|
||||
function useCachedData<T>(application: string, endpoint: string): (id: string | number, needLoad: boolean = true)=> T|undefined|null
|
||||
```
|
||||
- application - Название модуля
|
||||
- endpoint - Название объекта над которым идёт работа
|
||||
- id - id искомого объекта
|
||||
- params - дополнительные параметры (например фильтрация)
|
||||
- needLoad - указывает нужно ли загружать список/объект прямо сейчас
|
||||
|
||||
|
||||
- null - загрузка еще не происходила
|
||||
- undefined - не найдено
|
||||
- T (объект)
|
||||
|
||||
### Для ленивой загрузки элементов
|
||||
|
||||
`components/LoadingData.tsx`
|
||||
```tsx
|
||||
function LoadingData({data, children}: {data: object|null|undefined, children: (ReactElement | null | undefined | boolean)[]})
|
||||
```
|
||||
|
||||
Для загрузки отдельного элемента.
|
||||
Если data
|
||||
- null - Возвращает <div className={"loading"}>Идёт загрузка...</div>
|
||||
- undefined - <Link className={"not-found"} to={"/"}>404 НЕ НАЙДЕНО</Link>
|
||||
- иначе возвращает children
|
||||
|
||||
`components/LoadingList.tsx`
|
||||
```tsx
|
||||
function LoadingList({data, listElement}: {data: UniqueItem[]|null, listElement(e:UniqueItem, i:number):unknown})
|
||||
```
|
||||
|
||||
Для загрузки списка. Если data не null - то возвращает элементы результаты `listElement`
|
||||
|
||||
### Примеры
|
||||
|
||||
```tsx
|
||||
export function ProfessionPage() {
|
||||
const { professionId } = useParams<{ professionId: string }>();
|
||||
const profession = useData<Profession>('surveys', 'professions', professionId || 0);
|
||||
const specialties = useDataPage<Specialty>('surveys', 'specialties', { profession: professionId || 0 }, !!profession);
|
||||
|
||||
const institutions = useCachedData<Institution>("surveys", "institutions");
|
||||
|
||||
return (
|
||||
<div>
|
||||
<LoadingData data={profession}>
|
||||
<h1>{profession?.name}</h1>
|
||||
<div className="description">
|
||||
<ReactMarkdown>{profession?.description || ''}</ReactMarkdown>
|
||||
</div>
|
||||
<div className="specialties-block">
|
||||
{specialties.items != null ? <h2>Где можно обучиться</h2>:null}
|
||||
<LoadingList
|
||||
data={specialties.items}
|
||||
listElement={specialty => (
|
||||
<SpecialtyBlock
|
||||
key={specialty.id}
|
||||
specialty={specialty as Specialty}
|
||||
institution={institutions((specialty as Specialty).institution)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</LoadingData>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Авторизация
|
||||
|
||||
Везде доступен хук `useUser():UserContextType | undefined `
|
||||
14
StaticHelper/source/header.md
Normal file
14
StaticHelper/source/header.md
Normal file
@@ -0,0 +1,14 @@
|
||||
Ты Senior разработчик React.
|
||||
|
||||
Твоя задача - писать код под задачи (отвечай ТОЛЬКО кодом).
|
||||
|
||||
Я могу кидать тебе просто код или информацию. Если я не прошу писать код - отвечай только "Понял".
|
||||
Код, по возможности, только тот который необходимо внести в проект. Может быть работа сразу над несколькими файлами
|
||||
|
||||
Шаблон ответа кодом:
|
||||
|
||||
`Путь и название файла`
|
||||
```ts
|
||||
// Код который должен быть в этом файле
|
||||
```
|
||||
|
||||
0
StaticHelper/source/old_input.md
Normal file
0
StaticHelper/source/old_input.md
Normal file
95
StaticHelper/source/working_file.md
Normal file
95
StaticHelper/source/working_file.md
Normal file
@@ -0,0 +1,95 @@
|
||||
`API/users.ts`
|
||||
```ts
|
||||
|
||||
export async function login(data: TokenObtainPair): Promise<JwtTokenResponse | null>
|
||||
|
||||
export async function refreshToken(data: TokenRefresh): Promise<JwtTokenResponse | null>
|
||||
|
||||
export async function verifyToken(data: TokenVerify): Promise<boolean>
|
||||
```
|
||||
|
||||
`utils/users/UserProvider.tsx`
|
||||
```tsx
|
||||
export function UserProvider({ children }: { children: ReactNode }) {
|
||||
const [user, setUser] = useState<Account | undefined>(() => {
|
||||
const storedUser = localStorage.getItem('user');
|
||||
return storedUser ? JSON.parse(storedUser) : undefined;
|
||||
});
|
||||
const [tokens, setTokens] = useState<JwtTokenResponse | undefined>(() => {
|
||||
const storedTokens = localStorage.getItem('tokens');
|
||||
return storedTokens ? JSON.parse(storedTokens) : undefined;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (tokens?.access) {
|
||||
localStorage.setItem('tokens', JSON.stringify(tokens));
|
||||
} else {
|
||||
localStorage.removeItem('tokens');
|
||||
}
|
||||
}, [tokens]);
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
localStorage.setItem('user', JSON.stringify(user));
|
||||
} else {
|
||||
localStorage.removeItem('user');
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
const handleLogin = async (credentials: TokenObtainPair) => {
|
||||
const tokens = await apiLogin(credentials);
|
||||
if (tokens) {
|
||||
setTokens(tokens);
|
||||
// Здесь можно добавить запрос для получения данных пользователя
|
||||
setUser({ id: 1, login: credentials.login, is_staff: false, is_active: true } as Account); // TODO
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
setUser(undefined);
|
||||
setTokens(undefined);
|
||||
};
|
||||
|
||||
const validateToken = async () => {
|
||||
if (tokens?.access) {
|
||||
const isValid = await verifyToken({ token: tokens.access });
|
||||
if (!isValid && tokens.refresh) {
|
||||
const newTokens = await refreshToken({ refresh: tokens.refresh });
|
||||
if (newTokens) {
|
||||
setTokens(newTokens);
|
||||
} else {
|
||||
handleLogout();
|
||||
}
|
||||
} else if (!isValid) {
|
||||
handleLogout();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
validateToken().then();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<UserContext.Provider value={{ user, login: handleLogin, logout: handleLogout }}>
|
||||
{children}
|
||||
</UserContext.Provider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
В частности обрати внимание на эту функцию
|
||||
```ts
|
||||
const handleLogin = async (credentials: TokenObtainPair) => {
|
||||
const tokens = await apiLogin(credentials);
|
||||
if (tokens) {
|
||||
setTokens(tokens);
|
||||
// Здесь можно добавить запрос для получения данных пользователя
|
||||
setUser({ id: 1, login: credentials.login, is_staff: false, is_active: true } as Account); // TODO
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Воспользуйся эндпоинтами /users/auth/users/me/ для получения id текущего пользователя
|
||||
|
||||
Reference in New Issue
Block a user