Initial commit
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.vite/
|
||||
*.log
|
||||
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="pl">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Polska Lokalnie</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+1296
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "polskalokalnie-frontend",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 0.0.0.0",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview --host 0.0.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/pdfmake": "^0.3.3",
|
||||
"pdfmake": "^0.3.11",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "18.3.12",
|
||||
"@types/react-dom": "18.3.1",
|
||||
"@vitejs/plugin-react": "6.0.2",
|
||||
"typescript": "5.6.3",
|
||||
"vite": "8.0.16"
|
||||
}
|
||||
}
|
||||
+18542
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 2.5 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
@@ -0,0 +1,193 @@
|
||||
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export type Role = 'USER' | 'ADMIN';
|
||||
export type AuthProviderName = 'LOCAL' | 'GOOGLE' | 'FACEBOOK';
|
||||
export type AccountType = 'PERSONAL' | 'COMPANY';
|
||||
export type ContactPreference = 'EMAIL' | 'PHONE' | 'EMAIL_AND_PHONE';
|
||||
export type PreferredLanguage = 'PL' | 'EN' | 'UK' | 'DE';
|
||||
|
||||
export type AuthUser = {
|
||||
id: number;
|
||||
email: string;
|
||||
fullName: string;
|
||||
role: Role;
|
||||
provider: AuthProviderName;
|
||||
accountType: AccountType;
|
||||
phone: string | null;
|
||||
address: string | null;
|
||||
contactPreference: ContactPreference;
|
||||
preferredLanguage: PreferredLanguage;
|
||||
nip: string | null;
|
||||
birthDate: string | null;
|
||||
verified: boolean;
|
||||
blocked: boolean;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
type AuthResponse = { token: string; user: AuthUser };
|
||||
|
||||
export type RegisterDetails = {
|
||||
accountType: AccountType;
|
||||
phone?: string;
|
||||
nip?: string;
|
||||
};
|
||||
|
||||
type AuthContextValue = {
|
||||
user: AuthUser | null;
|
||||
token: string | null;
|
||||
loading: boolean;
|
||||
login: (email: string, password: string) => Promise<AuthUser>;
|
||||
register: (email: string, password: string, fullName: string, details?: RegisterDetails) => Promise<AuthUser>;
|
||||
socialLogin: (provider: Exclude<AuthProviderName, 'LOCAL'>) => Promise<AuthUser>;
|
||||
updateProfile: (fullName: string, phone?: string, birthDate?: string, address?: string, contactPreference?: ContactPreference, preferredLanguage?: PreferredLanguage) => Promise<AuthUser>;
|
||||
logout: () => void;
|
||||
};
|
||||
|
||||
const TOKEN_KEY = 'polskalokalnie-auth-token';
|
||||
const API_BASE = '/api';
|
||||
|
||||
export function getToken(): string | null {
|
||||
return window.localStorage.getItem(TOKEN_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cienki wrapper na fetch: dokleja token Bearer i zamienia bledy backendu (pole "message")
|
||||
* na wyjatek z czytelnym komunikatem po polsku.
|
||||
*/
|
||||
export async function apiFetch<T>(path: string, options: RequestInit = {}): Promise<T> {
|
||||
const token = getToken();
|
||||
const headers = new Headers(options.headers);
|
||||
if (options.body && !headers.has('Content-Type')) {
|
||||
headers.set('Content-Type', 'application/json');
|
||||
}
|
||||
if (token) {
|
||||
headers.set('Authorization', `Bearer ${token}`);
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_BASE}${path}`, { ...options, headers });
|
||||
|
||||
if (!response.ok) {
|
||||
let message = 'Wystąpił błąd. Spróbuj ponownie.';
|
||||
try {
|
||||
const data = await response.json();
|
||||
if (data && typeof data.message === 'string' && data.message.trim()) {
|
||||
message = data.message;
|
||||
}
|
||||
} catch {
|
||||
// brak/niepoprawne cialo JSON - zostaje komunikat domyslny
|
||||
}
|
||||
const error = new Error(message) as Error & { status?: number };
|
||||
error.status = response.status;
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (response.status === 204) {
|
||||
return undefined as T;
|
||||
}
|
||||
return (await response.json()) as T;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextValue | null>(null);
|
||||
|
||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [token, setToken] = useState<string | null>(() => getToken());
|
||||
const [user, setUser] = useState<AuthUser | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(() => Boolean(getToken()));
|
||||
|
||||
// Po odswiezeniu strony: majac token w localStorage, pobierz aktualny profil.
|
||||
useEffect(() => {
|
||||
if (!token) {
|
||||
setUser(null);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
setLoading(true);
|
||||
apiFetch<AuthUser>('/auth/me')
|
||||
.then((me) => {
|
||||
if (!cancelled) {
|
||||
setUser(me);
|
||||
setLoading(false);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) {
|
||||
window.localStorage.removeItem(TOKEN_KEY);
|
||||
setToken(null);
|
||||
setUser(null);
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [token]);
|
||||
|
||||
const applyAuth = useCallback((auth: AuthResponse) => {
|
||||
window.localStorage.setItem(TOKEN_KEY, auth.token);
|
||||
setToken(auth.token);
|
||||
setUser(auth.user);
|
||||
return auth.user;
|
||||
}, []);
|
||||
|
||||
const login = useCallback(
|
||||
async (email: string, password: string) =>
|
||||
applyAuth(await apiFetch<AuthResponse>('/auth/login', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ email, password }),
|
||||
})),
|
||||
[applyAuth],
|
||||
);
|
||||
|
||||
const register = useCallback(
|
||||
async (email: string, password: string, fullName: string, details?: RegisterDetails) =>
|
||||
applyAuth(await apiFetch<AuthResponse>('/auth/register', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ email, password, fullName, ...details }),
|
||||
})),
|
||||
[applyAuth],
|
||||
);
|
||||
|
||||
const socialLogin = useCallback(
|
||||
async (provider: Exclude<AuthProviderName, 'LOCAL'>) =>
|
||||
applyAuth(await apiFetch<AuthResponse>('/auth/social', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ provider }),
|
||||
})),
|
||||
[applyAuth],
|
||||
);
|
||||
|
||||
const updateProfile = useCallback(
|
||||
async (fullName: string, phone?: string, birthDate?: string, address?: string, contactPreference?: ContactPreference, preferredLanguage?: PreferredLanguage) => {
|
||||
const updated = await apiFetch<AuthUser>('/auth/me', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ fullName, phone, birthDate, address, contactPreference, preferredLanguage }),
|
||||
});
|
||||
setUser(updated);
|
||||
return updated;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const logout = useCallback(() => {
|
||||
window.localStorage.removeItem(TOKEN_KEY);
|
||||
setToken(null);
|
||||
setUser(null);
|
||||
}, []);
|
||||
|
||||
const value = useMemo<AuthContextValue>(
|
||||
() => ({ user, token, loading, login, register, socialLogin, updateProfile, logout }),
|
||||
[user, token, loading, login, register, socialLogin, updateProfile, logout],
|
||||
);
|
||||
|
||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||
}
|
||||
|
||||
export function useAuth(): AuthContextValue {
|
||||
const ctx = useContext(AuthContext);
|
||||
if (!ctx) {
|
||||
throw new Error('useAuth must be used within an AuthProvider');
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
export type UiLanguage = 'PL' | 'EN' | 'UK' | 'DE';
|
||||
|
||||
const en: Record<string, string> = {
|
||||
'Kupuję': 'Buy',
|
||||
'Wynajmuję': 'Rent',
|
||||
'Jak sprzedawać': 'How to sell',
|
||||
'Wycena mieszkania': 'Property valuation',
|
||||
'Firmy i usługi': 'Companies and services',
|
||||
'Poradniki': 'Guides',
|
||||
'Dane i ustawienia': 'Data and settings',
|
||||
'Edytuj profil': 'Edit profile',
|
||||
'Dodatkowe informacje': 'Additional information',
|
||||
'Adres zamieszkania': 'Residential address',
|
||||
'Preferencje kontaktu': 'Contact preferences',
|
||||
'Język komunikacji': 'Communication language',
|
||||
'Nie podano': 'Not provided',
|
||||
'Polski': 'Polish',
|
||||
'Zapisz zmiany': 'Save changes',
|
||||
'Dodaj zdjęcie': 'Add photo',
|
||||
'Zmień zdjęcie': 'Change photo',
|
||||
'Usuń zdjęcie': 'Remove photo',
|
||||
'Personalizacja profilu': 'Profile personalization',
|
||||
'Widoczność profilu': 'Profile visibility',
|
||||
'Publiczna': 'Public',
|
||||
'Prywatna': 'Private',
|
||||
'Tylko kontakty': 'Contacts only',
|
||||
'Profil publiczny': 'Public profile',
|
||||
'Zobacz profil': 'View profile',
|
||||
'Usunięcie konta': 'Account deletion',
|
||||
'Usuń konto': 'Delete account',
|
||||
'Porównanie mieszkań': 'Apartment comparison',
|
||||
'Porównaj do 4 mieszkań i wybierz najlepszą ofertę dla siebie.': 'Compare up to 4 apartments and choose the best option for you.',
|
||||
'Dodaj mieszkania do porównania': 'Add apartments to compare',
|
||||
'Możesz porównać do 4 ofert jednocześnie.': 'You can compare up to 4 listings at once.',
|
||||
'Szukaj mieszkań': 'Search apartments',
|
||||
'Wszystkie typy': 'All types',
|
||||
'Mieszkanie': 'Apartment',
|
||||
'Dom': 'House',
|
||||
'Działka': 'Plot',
|
||||
'Lokal użytkowy': 'Commercial unit',
|
||||
'Pokój': 'Room',
|
||||
'Dodaj': 'Add',
|
||||
'Podsumowanie porównania': 'Comparison summary',
|
||||
'Cena': 'Price',
|
||||
'Cena całkowita': 'Total price',
|
||||
'Cena za m²': 'Price per m²',
|
||||
'Przelicznik ceny do metrażu': 'Price-to-area ratio',
|
||||
'Lokalizacja': 'Location',
|
||||
'Lokalizacja ogłoszenia': 'Listing location',
|
||||
'Metraż': 'Area',
|
||||
'Powierzchnia użytkowa': 'Usable area',
|
||||
'Liczba pokoi': 'Number of rooms',
|
||||
'Ilość pomieszczeń': 'Number of spaces',
|
||||
'Piętro': 'Floor',
|
||||
'Poziom mieszkania': 'Apartment floor',
|
||||
'Stan': 'Condition',
|
||||
'Stan techniczny': 'Technical condition',
|
||||
'Rok budowy': 'Year built',
|
||||
'Rok oddania do użytku': 'Year commissioned',
|
||||
'Czynsz': 'Rent',
|
||||
'Miesięczny czynsz': 'Monthly rent',
|
||||
'Parking': 'Parking',
|
||||
'Miejsce parkingowe': 'Parking space',
|
||||
'Balkon / Taras': 'Balcony / Terrace',
|
||||
'Dodatkowa przestrzeń': 'Additional space',
|
||||
'Dostępność': 'Availability',
|
||||
'Termin dostępności': 'Availability date',
|
||||
'Ocena ogólna': 'Overall score',
|
||||
'Na podstawie kluczowych czynników': 'Based on key factors',
|
||||
'Brak wybranych ogłoszeń': 'No selected listings',
|
||||
'Dodaj pierwsze mieszkanie, aby rozpocząć porównanie.': 'Add your first apartment to start comparison.',
|
||||
'Zobacz ogłoszenie': 'View listing',
|
||||
'Wczytuję ogłoszenia...': 'Loading listings...',
|
||||
'Udostępnij porównanie': 'Share comparison',
|
||||
'Wyczyść wszystko': 'Clear all',
|
||||
'Strona główna': 'Home page',
|
||||
'Porównywarka mieszkań': 'Apartment comparison tool',
|
||||
'Sprzedaż': 'Sale',
|
||||
'Wynajem': 'Rent',
|
||||
'Dodaj min. 2 oferty do porównania': 'Add at least 2 listings to compare',
|
||||
'Świetna': 'Excellent',
|
||||
'Bardzo dobra': 'Very good',
|
||||
'Dobra': 'Good',
|
||||
'Do poprawy': 'Needs improvement',
|
||||
'Od zaraz': 'Immediately',
|
||||
'Do uzgodnienia': 'To be agreed',
|
||||
'Brak': 'None',
|
||||
};
|
||||
|
||||
const uk: Record<string, string> = {
|
||||
'Kupuję': 'Купую',
|
||||
'Wynajmuję': 'Орендую',
|
||||
'Jak sprzedawać': 'Як продавати',
|
||||
'Wycena mieszkania': 'Оцінка нерухомості',
|
||||
'Firmy i usługi': 'Компанії та послуги',
|
||||
'Poradniki': 'Порадники',
|
||||
'Dane i ustawienia': 'Дані та налаштування',
|
||||
'Edytuj profil': 'Редагувати профіль',
|
||||
'Dodatkowe informacje': 'Додаткова інформація',
|
||||
'Adres zamieszkania': 'Адреса проживання',
|
||||
'Preferencje kontaktu': 'Параметри контакту',
|
||||
'Język komunikacji': 'Мова спілкування',
|
||||
'Nie podano': 'Не вказано',
|
||||
'Polski': 'Польська',
|
||||
'Zapisz zmiany': 'Зберегти зміни',
|
||||
'Dodaj zdjęcie': 'Додати фото',
|
||||
'Zmień zdjęcie': 'Змінити фото',
|
||||
'Usuń zdjęcie': 'Видалити фото',
|
||||
'Personalizacja profilu': 'Персоналізація профілю',
|
||||
'Widoczność profilu': 'Видимість профілю',
|
||||
'Publiczna': 'Публічний',
|
||||
'Prywatna': 'Приватний',
|
||||
'Tylko kontakty': 'Лише контакти',
|
||||
'Profil publiczny': 'Публічний профіль',
|
||||
'Zobacz profil': 'Переглянути профіль',
|
||||
'Usunięcie konta': 'Видалення облікового запису',
|
||||
'Usuń konto': 'Видалити обліковий запис',
|
||||
'Porównanie mieszkań': 'Порівняння квартир',
|
||||
'Porównaj do 4 mieszkań i wybierz najlepszą ofertę dla siebie.': 'Порівняйте до 4 квартир і оберіть найкращу пропозицію для себе.',
|
||||
'Dodaj mieszkania do porównania': 'Додайте квартири для порівняння',
|
||||
'Możesz porównać do 4 ofert jednocześnie.': 'Ви можете порівняти до 4 оголошень одночасно.',
|
||||
'Szukaj mieszkań': 'Пошук квартир',
|
||||
'Wszystkie typy': 'Усі типи',
|
||||
'Mieszkanie': 'Квартира',
|
||||
'Dom': 'Будинок',
|
||||
'Działka': 'Ділянка',
|
||||
'Lokal użytkowy': 'Комерційне приміщення',
|
||||
'Pokój': 'Кімната',
|
||||
'Dodaj': 'Додати',
|
||||
'Podsumowanie porównania': 'Підсумок порівняння',
|
||||
'Cena': 'Ціна',
|
||||
'Cena całkowita': 'Загальна ціна',
|
||||
'Cena za m²': 'Ціна за м²',
|
||||
'Lokalizacja': 'Локація',
|
||||
'Metraż': 'Площа',
|
||||
'Liczba pokoi': 'Кількість кімнат',
|
||||
'Piętro': 'Поверх',
|
||||
'Stan': 'Стан',
|
||||
'Rok budowy': 'Рік побудови',
|
||||
'Czynsz': 'Орендна плата',
|
||||
'Parking': 'Паркування',
|
||||
'Balkon / Taras': 'Балкон / Тераса',
|
||||
'Dostępność': 'Доступність',
|
||||
'Ocena ogólna': 'Загальна оцінка',
|
||||
'Brak wybranych ogłoszeń': 'Немає вибраних оголошень',
|
||||
'Zobacz ogłoszenie': 'Переглянути оголошення',
|
||||
'Udostępnij porównanie': 'Поділитися порівнянням',
|
||||
'Wyczyść wszystko': 'Очистити все',
|
||||
'Strona główna': 'Головна сторінка',
|
||||
'Porównywarka mieszkań': 'Порівняння квартир',
|
||||
'Sprzedaż': 'Продаж',
|
||||
'Wynajem': 'Оренда',
|
||||
'Brak': 'Немає',
|
||||
};
|
||||
|
||||
const de: Record<string, string> = {
|
||||
'Kupuję': 'Ich kaufe',
|
||||
'Wynajmuję': 'Ich miete',
|
||||
'Jak sprzedawać': 'Wie verkauft man',
|
||||
'Wycena mieszkania': 'Immobilienbewertung',
|
||||
'Firmy i usługi': 'Firmen und Dienstleistungen',
|
||||
'Poradniki': 'Ratgeber',
|
||||
'Dane i ustawienia': 'Daten und Einstellungen',
|
||||
'Edytuj profil': 'Profil bearbeiten',
|
||||
'Dodatkowe informacje': 'Zusätzliche Informationen',
|
||||
'Adres zamieszkania': 'Wohnadresse',
|
||||
'Preferencje kontaktu': 'Kontaktpräferenzen',
|
||||
'Język komunikacji': 'Kommunikationssprache',
|
||||
'Nie podano': 'Nicht angegeben',
|
||||
'Polski': 'Polnisch',
|
||||
'Zapisz zmiany': 'Änderungen speichern',
|
||||
'Dodaj zdjęcie': 'Foto hinzufügen',
|
||||
'Zmień zdjęcie': 'Foto ändern',
|
||||
'Usuń zdjęcie': 'Foto entfernen',
|
||||
'Personalizacja profilu': 'Profilpersonalisierung',
|
||||
'Widoczność profilu': 'Profilsichtbarkeit',
|
||||
'Publiczna': 'Öffentlich',
|
||||
'Prywatna': 'Privat',
|
||||
'Tylko kontakty': 'Nur Kontakte',
|
||||
'Profil publiczny': 'Öffentliches Profil',
|
||||
'Zobacz profil': 'Profil ansehen',
|
||||
'Usunięcie konta': 'Konto löschen',
|
||||
'Usuń konto': 'Konto entfernen',
|
||||
'Porównanie mieszkań': 'Wohnungsvergleich',
|
||||
'Porównaj do 4 mieszkań i wybierz najlepszą ofertę dla siebie.': 'Vergleichen Sie bis zu 4 Wohnungen und wählen Sie das beste Angebot.',
|
||||
'Dodaj mieszkania do porównania': 'Wohnungen zum Vergleich hinzufügen',
|
||||
'Możesz porównać do 4 ofert jednocześnie.': 'Sie können bis zu 4 Angebote gleichzeitig vergleichen.',
|
||||
'Szukaj mieszkań': 'Wohnungen suchen',
|
||||
'Wszystkie typy': 'Alle Typen',
|
||||
'Mieszkanie': 'Wohnung',
|
||||
'Dom': 'Haus',
|
||||
'Działka': 'Grundstück',
|
||||
'Lokal użytkowy': 'Gewerbeobjekt',
|
||||
'Pokój': 'Zimmer',
|
||||
'Dodaj': 'Hinzufügen',
|
||||
'Podsumowanie porównania': 'Vergleichszusammenfassung',
|
||||
'Cena': 'Preis',
|
||||
'Cena całkowita': 'Gesamtpreis',
|
||||
'Cena za m²': 'Preis pro m²',
|
||||
'Lokalizacja': 'Standort',
|
||||
'Metraż': 'Fläche',
|
||||
'Liczba pokoi': 'Zimmeranzahl',
|
||||
'Piętro': 'Etage',
|
||||
'Stan': 'Zustand',
|
||||
'Rok budowy': 'Baujahr',
|
||||
'Czynsz': 'Miete',
|
||||
'Parking': 'Parken',
|
||||
'Balkon / Taras': 'Balkon / Terrasse',
|
||||
'Dostępność': 'Verfügbarkeit',
|
||||
'Ocena ogólna': 'Gesamtbewertung',
|
||||
'Brak wybranych ogłoszeń': 'Keine ausgewählten Anzeigen',
|
||||
'Zobacz ogłoszenie': 'Anzeige ansehen',
|
||||
'Udostępnij porównanie': 'Vergleich teilen',
|
||||
'Wyczyść wszystko': 'Alles löschen',
|
||||
'Strona główna': 'Startseite',
|
||||
'Porównywarka mieszkań': 'Wohnungsvergleich',
|
||||
'Sprzedaż': 'Verkauf',
|
||||
'Wynajem': 'Miete',
|
||||
'Brak': 'Keine',
|
||||
};
|
||||
|
||||
const dictionaries: Record<Exclude<UiLanguage, 'PL'>, Record<string, string>> = {
|
||||
EN: en,
|
||||
UK: uk,
|
||||
DE: de,
|
||||
};
|
||||
|
||||
function normalizeKey(value: string): string {
|
||||
return value.replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
export function getManualTranslation(language: UiLanguage, source: string): string | null {
|
||||
if (language === 'PL') {
|
||||
return source;
|
||||
}
|
||||
const dictionary = dictionaries[language];
|
||||
if (!dictionary) {
|
||||
return null;
|
||||
}
|
||||
const exact = dictionary[source];
|
||||
if (exact) {
|
||||
return exact;
|
||||
}
|
||||
|
||||
const normalizedSource = normalizeKey(source);
|
||||
if (!normalizedSource) {
|
||||
return null;
|
||||
}
|
||||
const normalizedHit = dictionary[normalizedSource];
|
||||
return normalizedHit ?? null;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
import { AuthProvider } from './auth';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<AuthProvider>
|
||||
<App />
|
||||
</AuthProvider>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
declare module 'pdfmake/build/fonts/Roboto.js' {
|
||||
type PdfMakeFontContainer = {
|
||||
vfs: Record<string, { data: string; encoding?: string } | string>;
|
||||
fonts: Record<string, Record<string, string>>;
|
||||
};
|
||||
|
||||
const fontContainer: PdfMakeFontContainer;
|
||||
export default fontContainer;
|
||||
}
|
||||
+23240
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2020"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 5173,
|
||||
strictPort: true,
|
||||
allowedHosts: true,
|
||||
hmr: {
|
||||
overlay: false,
|
||||
},
|
||||
watch: {
|
||||
usePolling: true,
|
||||
interval: 120,
|
||||
awaitWriteFinish: {
|
||||
stabilityThreshold: 260,
|
||||
pollInterval: 80,
|
||||
},
|
||||
},
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:8080',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
preview: {
|
||||
host: '0.0.0.0',
|
||||
port: 4173,
|
||||
strictPort: true,
|
||||
allowedHosts: true,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user