convert setting modals + associate function

This commit is contained in:
p2405951
2026-01-30 16:35:48 +01:00
parent 62a88dab83
commit 2389ca88e4
19 changed files with 670 additions and 294 deletions
-16
View File
@@ -1,16 +0,0 @@
import styles from "./button.module.css";
const Button = ({ children, variant = "primary", onClick, className }) => {
let btnStyle;
if (variant === "primary") btnStyle = styles.primary;
else if (variant === "danger") btnStyle = styles.danger;
else if(variant === "transparent") btnStyle = styles.transparent;
else btnStyle = styles.default;
return <div className={`${styles.btn} ${btnStyle} ${className}`} onClick={onClick}>
{children}
</div>;
};
export default Button;
+53
View File
@@ -0,0 +1,53 @@
/*import styles from "./button.module.css";
const Button = ({ children, variant = "primary", onClick, className }) => {
let btnStyle;
if (variant === "primary") btnStyle = styles.primary;
else if (variant === "danger") btnStyle = styles.danger;
else if(variant === "transparent") btnStyle = styles.transparent;
else btnStyle = styles.default;
return <div className={`${styles.btn} ${btnStyle} ${className}`} onClick={onClick}>
{children}
</div>;
};
export default Button;
*/
import styles from "./button.module.css";
import { ReactNode, MouseEvent } from "react";
interface ButtonProps {
children: ReactNode;
variant?: "primary" | "danger" | "transparent" | "default";
onClick?: (event: MouseEvent<HTMLDivElement>) => void;
className?: string;
}
const Button = ({
children,
variant = "primary",
onClick,
className = "",
}: ButtonProps) => {
const variantStyles = {
primary: styles.primary,
danger: styles.danger,
transparent: styles.transparent,
default: styles.default,
};
const btnStyle = variantStyles[variant in variantStyles ? variant : "default"];
return (
<div
className={`${styles.btn} ${btnStyle} ${className}`}
onClick={onClick}
>
{children}
</div>
);
};
export default Button;
-38
View File
@@ -1,38 +0,0 @@
import styles from "./input.module.css";
import { useState } from "react";
const TextInput = ({ placeholder, onChange, value, borderStyle, password, className, ...props }) => {
const [showPassword, setShowPassword] = useState(false);
let inputBorderStyle = styles.square;
if (borderStyle === "square") inputBorderStyle = styles.square;
if(borderStyle === "rounded") inputBorderStyle = styles.rounded;
const toggleShowPassword = () => {
setShowPassword(!showPassword);
};
if(password) return (
<div className={styles.passwordContainer}>
<TextInput
type={showPassword ? "text" : "password"}
value={value}
placeholder={placeholder}
onChange={onChange}
className={styles.passwordInput}
/>
<button
type="button"
onClick={toggleShowPassword}
className={styles.passwordBtn}
>
{showPassword ? '👁️' : '🔒'}
</button>
</div>
);
return <input className={`${styles.input} ${inputBorderStyle} ${className}`} onChange={onChange} placeholder={placeholder} value={value} {...props} />
};
export default TextInput;
+107
View File
@@ -0,0 +1,107 @@
/*import styles from "./input.module.css";
import { useState } from "react";
const TextInput = ({ placeholder, onChange, value, borderStyle, password, className, ...props }) => {
const [showPassword, setShowPassword] = useState(false);
let inputBorderStyle = styles.square;
if (borderStyle === "square") inputBorderStyle = styles.square;
if(borderStyle === "rounded") inputBorderStyle = styles.rounded;
const toggleShowPassword = () => {
setShowPassword(!showPassword);
};
if(password) return (
<div className={styles.passwordContainer}>
<TextInput
type={showPassword ? "text" : "password"}
value={value}
placeholder={placeholder}
onChange={onChange}
className={styles.passwordInput}
/>
<button
type="button"
onClick={toggleShowPassword}
className={styles.passwordBtn}
>
{showPassword ? '👁️' : '🔒'}
</button>
</div>
);
return <input className={`${styles.input} ${inputBorderStyle} ${className}`} onChange={onChange} placeholder={placeholder} value={value} {...props} />
};
export default TextInput;*/
import styles from "./input.module.css";
import { useState, InputHTMLAttributes, ChangeEvent } from "react";
interface TextInputProps extends InputHTMLAttributes<HTMLInputElement> {
placeholder?: string;
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
value: string;
borderStyle?: "square" | "rounded";
password?: boolean;
className?: string;
}
const TextInput = ({
placeholder,
onChange,
value,
borderStyle = "square",
password = false,
className = "",
...props
}: TextInputProps) => {
const [showPassword, setShowPassword] = useState(false);
const borderStyles = {
square: styles.square,
rounded: styles.rounded,
};
const inputBorderStyle = borderStyles[borderStyle] || styles.square;
const toggleShowPassword = () => {
setShowPassword(!showPassword);
};
if (password) {
return (
<div className={styles.passwordContainer}>
<input
type={showPassword ? "text" : "password"}
value={value}
placeholder={placeholder}
onChange={onChange}
className={`${styles.input} ${styles.passwordInput} ${inputBorderStyle} ${className}`}
{...props}
/>
<button
type="button"
onClick={toggleShowPassword}
className={styles.passwordBtn}
>
{showPassword ? "👁️" : "🔒"}
</button>
</div>
);
}
return (
<input
className={`${styles.input} ${inputBorderStyle} ${className}`}
onChange={onChange}
placeholder={placeholder}
value={value}
{...props}
/>
);
};
export default TextInput;
-31
View File
@@ -1,31 +0,0 @@
import { createPortal } from "react-dom";
import styles from "./modal.module.css";
import Button from "../button/button.jsx";
const Modal = ({ open, onClose, children, title }) => {
if (!open) return null;
const handleOverlayClick = () => {
onClose();
};
const handleModalClick = (e) => {
e.stopPropagation();
};
return createPortal(
<div className={styles.overlay} onClick={handleOverlayClick}>
<div className={styles.modal} onClick={handleModalClick}>
{title && <h2>{title}</h2>}
{children}
<div className={styles.modalFooter}>
<Button onClick={onClose}>Fermer</Button>
</div>
</div>
</div>,
document.body
);
};
export default Modal;
+71
View File
@@ -0,0 +1,71 @@
/*import { createPortal } from "react-dom";
import styles from "./modal.module.css";
import Button from "../button/button.jsx";
const Modal = ({ open, onClose, children, title }) => {
if (!open) return null;
const handleOverlayClick = () => {
onClose();
};
const handleModalClick = (e) => {
e.stopPropagation();
};
return createPortal(
<div className={styles.overlay} onClick={handleOverlayClick}>
<div className={styles.modal} onClick={handleModalClick}>
{title && <h2>{title}</h2>}
{children}
<div className={styles.modalFooter}>
<Button onClick={onClose}>Fermer</Button>
</div>
</div>
</div>,
document.body
);
};
export default Modal;*/
import { createPortal } from "react-dom";
import styles from "./modal.module.css";
import Button from "../button/button";
import { ReactNode, MouseEvent } from "react";
interface ModalProps {
open: boolean;
onClose: () => void;
children: ReactNode;
title?: string;
}
const Modal = ({ open, onClose, children, title }: ModalProps) => {
if (!open) return null;
const handleOverlayClick = () => {
onClose();
};
const handleModalClick = (e: MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
};
return createPortal(
<div className={styles.overlay} onClick={handleOverlayClick}>
<div className={styles.modal} onClick={handleModalClick}>
{title && <h2>{title}</h2>}
{children}
<div className={styles.modalFooter}>
<Button onClick={onClose}>Fermer</Button>
</div>
</div>
</div>,
document.body
);
};
export default Modal;
@@ -1,32 +0,0 @@
import Button from "../button/button.jsx";
import { useState, useEffect } from "react";
import styles from "./ThemeSwitcher.module.css";
export default function ThemeSwitcher() {
const [theme, setTheme] = useState("");
useEffect(() => {
const t = localStorage.getItem("theme");
if(t) setTheme(t);
else setTheme("light");
}, []);
const switchTheme = () => {
let newTheme = theme === "light" ? "dark" : "light";
if(theme === null || theme === "") newTheme = "dark";
localStorage.setItem("theme", newTheme);
setTheme(newTheme);
window.dispatchEvent(new Event("theme-changed"));
};
return <Button variant={"default"} onClick={switchTheme} className={styles.themeBtn}>
<img src={`/icons/theme/${theme}.svg`} alt={`${theme} icon`} className={styles.themeIcon} />
<p>Changer de thème </p>
</Button>
}
@@ -0,0 +1,73 @@
/*import Button from "../button/button.jsx";
import { useState, useEffect } from "react";
import styles from "./ThemeSwitcher.module.css";
export default function ThemeSwitcher() {
const [theme, setTheme] = useState("");
useEffect(() => {
const t = localStorage.getItem("theme");
if(t) setTheme(t);
else setTheme("light");
}, []);
const switchTheme = () => {
let newTheme = theme === "light" ? "dark" : "light";
if(theme === null || theme === "") newTheme = "dark";
localStorage.setItem("theme", newTheme);
setTheme(newTheme);
window.dispatchEvent(new Event("theme-changed"));
};
return <Button variant={"default"} onClick={switchTheme} className={styles.themeBtn}>
<img src={`/icons/theme/${theme}.svg`} alt={`${theme} icon`} className={styles.themeIcon} />
<p>Changer de thème </p>
</Button>
}*/
import Button from "../button/button";
import { useState, useEffect } from "react";
import styles from "./ThemeSwitcher.module.css";
type Theme = "light" | "dark";
export default function ThemeSwitcher() {
const [theme, setTheme] = useState<Theme | "">("");
useEffect(() => {
const savedTheme = localStorage.getItem("theme") as Theme | null;
if (savedTheme) {
setTheme(savedTheme);
} else {
setTheme("light");
}
}, []);
const switchTheme = () => {
const newTheme: Theme = theme === "light" ? "dark" : "light";
localStorage.setItem("theme", newTheme);
setTheme(newTheme);
window.dispatchEvent(new Event("theme-changed"));
};
return (
<Button
variant="default"
onClick={switchTheme}
className={styles.themeBtn}
>
<img
src={`/icons/theme/${theme}.svg`}
alt={`${theme} icon`}
className={styles.themeIcon}
/>
<p>Changer de thème</p>
</Button>
);
}
@@ -1,86 +0,0 @@
import Modal from "../../../../components/ui/modal/modal.jsx";
import Button from "../../../../components/ui/button/button.jsx";
import styles from "./SettingsModal.module.css"
import { useContext, useState } from "react";
import ThemeSwitcher from "../../../../components/ui/themeSwitcher/ThemeSwitcher.jsx";
import { AuthContext } from "../../../../contexts/auth/AuthContext.js";
import TextInput from "../../../../components/ui/input/input.jsx";
import updateUser from "../../../../utils/users/updateUser.js";
import deleteUser from "../../../../utils/users/deleteUser.js";
export default function SettingsModal() {
const { user, logout, update } = useContext(AuthContext);
const [open, setOpen] = useState(false);
const [isDelete, setIsDelete] = useState(false);
const [name, setName] = useState(user?.name);
const [lastname, setLastName] = useState(user?.lastname);
const [phone, setPhone] = useState(user?.phone);
const handleLogout = () => {
logout();
}
const handleSubmit = async () => {
await updateUser(name, lastname, phone);
update();
}
const handleDelete = async () => {
await deleteUser();
update();
}
return (
<>
<Button variant={"transparent"} onClick={() => setOpen(!open)}>
<img src={"/icons/settings-wheel.svg"} alt={"Modify btn"} className={styles.modifyIcon}/>
</Button>
<Modal title={"Paramètres"} open={open} onClose={() => setOpen(false)}>
<div className={styles.content}>
<div className={styles.section}>
<h3>Compte</h3>
<Button variant={"danger"} onClick={handleLogout}> Déconnexion </Button>
<Button variant={"danger"} onClick={() => setIsDelete(true)}> Supprimer le compte </Button>
<div className={styles.section}>
<h4>Changer de Prénom</h4>
<TextInput value={name} onChange={e => setName(e.target.value)} type={"text"} />
</div>
<div className={styles.section}>
<h4>Changer de nom</h4>
<TextInput value={lastname} onChange={e => setLastName(e.target.value)} type={"text"} />
</div>
<div className={styles.section}>
<h4>Changer de numéro de téléphone</h4>
<TextInput value={phone} onChange={e => setPhone(e.target.value)} type={"text"} />
</div>
<Button className={styles.submitBtn} onClick={handleSubmit}>Confirmer</Button>
</div>
<div className={styles.section}>
<h3>Apparence</h3>
<ThemeSwitcher />
</div>
</div>
</Modal>
<Modal title={"Validation"} open={isDelete} onClose={() => setIsDelete(false)}>
<div className={styles.section}>
<p className={styles.alignText}> Êtes vous vraiment sûr de vouloir supprimer votre compte ?</p>
<Button variant={"danger"} className={styles.submitBtn} onClick={handleDelete}>Confirmer</Button>
</div>
</Modal>
</>
)
}
@@ -0,0 +1,187 @@
/*import Modal from "../../../../components/ui/modal/modal.jsx";
import Button from "../../../../components/ui/button/button.jsx";
import styles from "./SettingsModal.module.css"
import { useContext, useState } from "react";
import ThemeSwitcher from "../../../../components/ui/themeSwitcher/ThemeSwitcher.jsx";
import { AuthContext } from "../../../../contexts/auth/AuthContext.js";
import TextInput from "../../../../components/ui/input/input.jsx";
import updateUser from "../../../../utils/users/updateUser.js";
import deleteUser from "../../../../utils/users/deleteUser.js";
export default function SettingsModal() {
const { user, logout, update } = useContext(AuthContext);
const [open, setOpen] = useState(false);
const [isDelete, setIsDelete] = useState(false);
const [name, setName] = useState(user?.name);
const [lastname, setLastName] = useState(user?.lastname);
const [phone, setPhone] = useState(user?.phone);
const handleLogout = () => {
logout();
}
const handleSubmit = async () => {
await updateUser(name, lastname, phone);
update();
}
const handleDelete = async () => {
await deleteUser();
update();
}
return (
<>
<Button variant={"transparent"} onClick={() => setOpen(!open)}>
<img src={"/icons/settings-wheel.svg"} alt={"Modify btn"} className={styles.modifyIcon}/>
</Button>
<Modal title={"Paramètres"} open={open} onClose={() => setOpen(false)}>
<div className={styles.content}>
<div className={styles.section}>
<h3>Compte</h3>
<Button variant={"danger"} onClick={handleLogout}> Déconnexion </Button>
<Button variant={"danger"} onClick={() => setIsDelete(true)}> Supprimer le compte </Button>
<div className={styles.section}>
<h4>Changer de Prénom</h4>
<TextInput value={name} onChange={e => setName(e.target.value)} type={"text"} />
</div>
<div className={styles.section}>
<h4>Changer de nom</h4>
<TextInput value={lastname} onChange={e => setLastName(e.target.value)} type={"text"} />
</div>
<div className={styles.section}>
<h4>Changer de numéro de téléphone</h4>
<TextInput value={phone} onChange={e => setPhone(e.target.value)} type={"text"} />
</div>
<Button className={styles.submitBtn} onClick={handleSubmit}>Confirmer</Button>
</div>
<div className={styles.section}>
<h3>Apparence</h3>
<ThemeSwitcher />
</div>
</div>
</Modal>
<Modal title={"Validation"} open={isDelete} onClose={() => setIsDelete(false)}>
<div className={styles.section}>
<p className={styles.alignText}> Êtes vous vraiment sûr de vouloir supprimer votre compte ?</p>
<Button variant={"danger"} className={styles.submitBtn} onClick={handleDelete}>Confirmer</Button>
</div>
</Modal>
</>
)
}*/
import Modal from "../../../../components/ui/modal/modal";
import Button from "../../../../components/ui/button/button";
import styles from "./SettingsModal.module.css";
import { useContext, useState } from "react";
import ThemeSwitcher from "../../../../components/ui/themeSwitcher/ThemeSwitcher";
import { AuthContext } from "../../../../contexts/auth/AuthContext";
import TextInput from "../../../../components/ui/input/input";
import updateUser from "../../../../utils/users/updateUser";
import deleteUser from "../../../../utils/users/deleteUser";
interface User {
name: string;
lastname: string;
phone: string | null;
}
interface AuthContextType {
user: User | null;
logout: () => void;
update: () => void;
}
export default function SettingsModal() {
const { user, logout, update } = useContext(AuthContext) as AuthContextType;
const [open, setOpen] = useState<boolean>(false);
const [isDelete, setIsDelete] = useState<boolean>(false);
const [name, setName] = useState<string>(user?.name || "");
const [lastname, setLastName] = useState<string>(user?.lastname || "");
const [phone, setPhone] = useState<string | null>(user?.phone || null);
const handleLogout = () => {
logout();
};
const handleSubmit = async () => {
await updateUser(name, lastname, phone);
update();
};
const handleDelete = async () => {
await deleteUser();
update();
};
return (
<>
<Button variant={"transparent"} onClick={() => setOpen(!open)}>
<img src={"/icons/settings-wheel.svg"} alt={"Modify btn"} className={styles.modifyIcon} />
</Button>
<Modal title={"Paramètres"} open={open} onClose={() => setOpen(false)}>
<div className={styles.content}>
<div className={styles.section}>
<h3>Compte</h3>
<Button variant={"danger"} onClick={handleLogout}>
Déconnexion
</Button>
<Button variant={"danger"} onClick={() => setIsDelete(true)}>
Supprimer le compte
</Button>
<div className={styles.section}>
<h4>Changer de Prénom</h4>
<TextInput value={name} onChange={(e) => setName(e.target.value)} type={"text"} />
</div>
<div className={styles.section}>
<h4>Changer de nom</h4>
<TextInput value={lastname} onChange={(e) => setLastName(e.target.value)} type={"text"} />
</div>
<div className={styles.section}>
<h4>Changer de numéro de téléphone</h4>
<TextInput value={phone || ""} onChange={(e) => setPhone(e.target.value)} type={"text"} />
</div>
<Button className={styles.submitBtn} onClick={handleSubmit}>
Confirmer
</Button>
</div>
<div className={styles.section}>
<h3>Apparence</h3>
<ThemeSwitcher />
</div>
</div>
</Modal>
<Modal title={"Validation"} open={isDelete} onClose={() => setIsDelete(false)}>
<div className={styles.section}>
<p className={styles.alignText}>Êtes-vous vraiment sûr de vouloir supprimer votre compte ?</p>
<Button variant={"danger"} className={styles.submitBtn} onClick={handleDelete}>
Confirmer
</Button>
</div>
</Modal>
</>
);
}
-45
View File
@@ -1,45 +0,0 @@
export default async function fetchWrapper(
path,
data = null,
method = "GET",
headers = {}
) {
try {
const res = await fetch(
`${import.meta.env.VITE_API_URL}${path}`,
{
method,
credentials: "include",
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
...headers,
},
body: data ? JSON.stringify(data) : null,
}
);
let responseData = null;
try {
responseData = await res.json();
} catch (_) {
responseData = null;
}
return {
status: res.status,
data: responseData,
};
} catch (err) {
console.error("❌ Erreur réseau :", err);
return {
status: 0,
data: {
message: "Network error",
},
};
}
}
+92
View File
@@ -0,0 +1,92 @@
/*export default async function fetchWrapper(path,data = null,method = "GET",headers = {}) {
try {
const res = await fetch(
`${import.meta.env.VITE_API_URL}${path}`,
{
method,
credentials: "include",
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
...headers,
},
body: data ? JSON.stringify(data) : null,
}
);
let responseData = null;
try {
responseData = await res.json();
} catch (_) {
responseData = null;
}
return {
status: res.status,
data: responseData,
};
} catch (err) {
console.error("❌ Erreur réseau :", err);
return {
status: 0,
data: {
message: "Network error",
},
};
}
}*/
interface FetchResponse<T = any> {
status: number;
data: T | null;
}
export default async function fetchWrapper(
path: string,
data: any = null,
method: string = "GET",
headers: Record<string, string> = {}
): Promise<FetchResponse> {
try {
const res = await fetch(
`${import.meta.env.VITE_API_URL}${path}`,
{
method,
credentials: "include",
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
...headers,
},
body: data ? JSON.stringify(data) : null,
}
);
let responseData = null;
try {
responseData = await res.json();
} catch (_) {
responseData = null;
}
return {
status: res.status,
data: responseData,
};
} catch (err) {
console.error("❌ Erreur réseau :", err);
return {
status: 0,
data: {
message: "Network error",
},
};
}
}
-19
View File
@@ -1,19 +0,0 @@
export default async function getXSRFToken() {
const response = await fetch(`${import.meta.env.VITE_API_URL}/sanctum/csrf-cookie`, {
method: 'GET',
credentials: 'include'
});
if (!response.ok && response.status !== 204) {
throw new Error(`Error : ${response.status}`);
}
const name = 'XSRF-TOKEN';
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) {
return decodeURIComponent(parts.pop().split(';').shift());
}
throw new Error('Error: Invalid XSRF-TOKEN');
}
+40
View File
@@ -0,0 +1,40 @@
/*export default async function getXSRFToken() {
const response = await fetch(`${import.meta.env.VITE_API_URL}/sanctum/csrf-cookie`, {
method: 'GET',
credentials: 'include'
});
if (!response.ok && response.status !== 204) {
throw new Error(`Error : ${response.status}`);
}
const name = 'XSRF-TOKEN';
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) {
return decodeURIComponent(parts.pop().split(';').shift());
}
throw new Error('Error: Invalid XSRF-TOKEN');
}*/
export default async function getXSRFToken(): Promise<string> {
const response = await fetch(`${import.meta.env.VITE_API_URL}/sanctum/csrf-cookie`, {
method: 'GET',
credentials: 'include'
});
if (!response.ok && response.status !== 204) {
throw new Error(`Error : ${response.status}`);
}
const name = 'XSRF-TOKEN';
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) {
return decodeURIComponent(parts.pop()!.split(';').shift()!);
}
throw new Error('Error: Invalid XSRF-TOKEN');
}
-13
View File
@@ -1,13 +0,0 @@
import fetchWrapper from "../fetchWrapper.js";
import getXSRFToken from "../getXSRF.js";
export default async function deleteUser() {
const csrfToken = await getXSRFToken();
return fetchWrapper(
"/api/users",
null,
"DELETE",
{ "X-XSRF-TOKEN": csrfToken }
);
}
+13
View File
@@ -0,0 +1,13 @@
import fetchWrapper from "../fetchWrapper";
import getXSRFToken from "../getXSRF";
export default async function deleteUser(): Promise<{status: number;data: any;}> {
const csrfToken: string = await getXSRFToken();
return fetchWrapper(
"/api/users",
null,
"DELETE",
{ "X-XSRF-TOKEN": csrfToken }
);
}
-13
View File
@@ -1,13 +0,0 @@
import fetchWrapper from "../fetchWrapper.js";
import getXSRFToken from "../getXSRF.js";
export default async function updateUser(name, lastname, phone) {
const csrfToken = await getXSRFToken();
return fetchWrapper(
"/api/users/update",
{ name, lastname, phone },
"POST",
{ "X-XSRF-TOKEN": csrfToken }
);
}
+33
View File
@@ -0,0 +1,33 @@
/*import fetchWrapper from "../fetchWrapper.js";
import getXSRFToken from "../getXSRF.js";
export default async function updateUser(name, lastname, phone) {
const csrfToken = await getXSRFToken();
return fetchWrapper(
"/api/users/update",
{ name, lastname, phone },
"POST",
{ "X-XSRF-TOKEN": csrfToken }
);
}*/
import fetchWrapper from "../fetchWrapper";
import getXSRFToken from "../getXSRF";
interface UserUpdateData {
name: string;
lastname: string;
phone: string | null;
}
interface RequestHeaders {
"X-XSRF-TOKEN": string;
}
export default async function updateUser(name: string,lastname: string,phone: string | null): Promise<Response> {
const csrfToken: string = await getXSRFToken();
const userData: UserUpdateData = {name,lastname,phone,};
const headers: RequestHeaders = {"X-XSRF-TOKEN": csrfToken,};
return fetchWrapper("/api/users/update",userData,"POST",headers);
}
+1 -1
View File
@@ -54,7 +54,7 @@
"isolatedModules": true, "isolatedModules": true,
"noUncheckedIndexedAccess": true, "noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true, "exactOptionalPropertyTypes": false,
"resolveJsonModule": true, "resolveJsonModule": true,
"skipLibCheck": true "skipLibCheck": true