=> {
+ setIsPosting(true);
+ try {
+ const response = await fetch(url, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ ...(options.headers || {}),
+ },
+ body: body ? JSON.stringify(body) : undefined,
+ ...options,
+ });
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ const result: T = await response.json();
+ return result;
+ } catch (err) {
+ setError(err as Error);
+ throw err;
+ } finally {
+ setIsPosting(false);
+ }
+ };
+
+ return { isPosting, error, postData };
+};
+
+export default usePost;
diff --git a/src/index.css b/src/index.css
index 4558f538..ea16ad99 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,13 +1,103 @@
+@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&display=swap");
+
:root {
margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
- "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
- sans-serif;
+ font-family: "IBM Plex Mono", "Courier New", "Courier", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
+ --black: #000;
+ --gray-700: #666;
+ --gray-500: #aaa;
+ --gray-200: #eee;
+ --red: #ff6b46;
+}
+
+*,
+*:before,
+*:after {
+ box-sizing: border-box;
+}
+
+*:focus-visible {
+ outline: 2px solid var(--red);
+ outline-offset: 2px;
+}
+
+.fade-enter {
+ opacity: 0;
+}
+.fade-enter-active {
+ opacity: 1;
+ transition: opacity 300ms ease-in;
+}
+.fade-exit {
+ opacity: 1;
+}
+.fade-exit-active {
+ opacity: 0;
+ transition: opacity 300ms ease-in;
+}
+
+.flex-1 {
+ flex: 1;
+}
+
+.skeleton-align-right {
+ text-align: right;
+}
+
+body {
+ margin: 0;
+ padding: 1rem;
+
+ @media (min-width: 768px) {
+ padding: 2rem;
+ }
+}
+
+#root {
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+
+ @media (min-width: 768px) {
+ flex-direction: row;
+ gap: 2rem;
+ }
+}
+
+section {
+ width: 100%;
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(17.5rem, 1fr));
+ gap: 1rem;
+
+ @media (min-width: 768px) {
+ gap: 2rem;
+ }
+}
+
+main {
+ flex-shrink: 1;
+ align-self: flex-start;
+ width: 100%;
+
+ @media (min-width: 768px) {
+ width: 32rem;
+ max-width: 100%;
+ }
+}
+
+h1 {
+ font-size: 1.375rem;
+ margin-bottom: 0.25rem;
+
+ @media (min-width: 768px) {
+ font-size: 1.625rem;
+ }
}
-code {
- font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
- monospace;
+h2 {
+ font-size: 1.125rem;
+ font-weight: 600;
}
diff --git a/src/main.jsx b/src/main.tsx
similarity index 61%
rename from src/main.jsx
rename to src/main.tsx
index 51294f39..fd7970f7 100644
--- a/src/main.jsx
+++ b/src/main.tsx
@@ -1,9 +1,9 @@
import React from "react";
import ReactDOM from "react-dom/client";
-import { App } from "./App.jsx";
+import { App } from "./App";
import "./index.css";
-ReactDOM.createRoot(document.getElementById("root")).render(
+ReactDOM.createRoot(document.getElementById("root")!).render(
diff --git a/src/utils/renderSkeletonLoader.tsx b/src/utils/renderSkeletonLoader.tsx
new file mode 100644
index 00000000..ba854619
--- /dev/null
+++ b/src/utils/renderSkeletonLoader.tsx
@@ -0,0 +1,25 @@
+import { ComponentType } from "react";
+import "react-loading-skeleton/dist/skeleton.css";
+
+type RenderSkeletonProps = {
+ Component: ComponentType
;
+ count: number;
+ props: P;
+};
+
+export const renderSkeletonLoader =
({
+ Component,
+ count,
+ props,
+}: RenderSkeletonProps
) => {
+ return (
+ <>
+ {Array.from({ length: count }, (_, index) => (
+
+ ))}
+ >
+ );
+};
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 00000000..575f6e05
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "baseUrl": ".",
+ "target": "esnext", // Use modern JavaScript features
+ "useDefineForClassFields": true, // Align with the modern JS class field proposal
+ "lib": ["dom", "dom.iterable", "esnext"], // Include DOM and modern JS APIs
+ "allowJs": false, // Allow or disallow JavaScript files
+ "skipLibCheck": true, // Skip type checking of declaration files for faster builds
+ "esModuleInterop": true, // Support for CommonJS modules
+ "allowSyntheticDefaultImports": true, // Allow default imports from modules without default exports
+ "strict": true, // Enable all strict type-checking options
+ "forceConsistentCasingInFileNames": true, // Enforce consistent file naming
+ "module": "esnext", // Use modern module resolution for ESM
+ "moduleResolution": "node", // Resolve modules using Node.js rules
+ "paths": {
+ "@/*": ["./src/*"]
+ },
+ "resolveJsonModule": true, // Allow importing JSON files
+ "isolatedModules": true, // Ensure compatibility with Vite's HMR
+ "noEmit": true, // Prevent emitting JavaScript files
+ "jsx": "react-jsx" // Use React 18+ JSX runtime
+ },
+ "include": ["src"], // Include the source directory
+ "exclude": ["node_modules"] // Exclude unnecessary files
+}