chore: init
This commit is contained in:
34
frontend/.gitignore
vendored
Normal file
34
frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# dependencies (bun install)
|
||||
node_modules
|
||||
|
||||
# output
|
||||
out
|
||||
dist
|
||||
*.tgz
|
||||
|
||||
# code coverage
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# logs
|
||||
logs
|
||||
_.log
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# caches
|
||||
.eslintcache
|
||||
.cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
21
frontend/README.md
Normal file
21
frontend/README.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# bun-react-tailwind-template
|
||||
|
||||
To install dependencies:
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
To start a development server:
|
||||
|
||||
```bash
|
||||
bun dev
|
||||
```
|
||||
|
||||
To run for production:
|
||||
|
||||
```bash
|
||||
bun start
|
||||
```
|
||||
|
||||
This project was created using `bun init` in bun v1.3.0. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
|
||||
149
frontend/build.ts
Normal file
149
frontend/build.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env bun
|
||||
import plugin from "bun-plugin-tailwind";
|
||||
import { existsSync } from "fs";
|
||||
import { rm } from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
||||
console.log(`
|
||||
🏗️ Bun Build Script
|
||||
|
||||
Usage: bun run build.ts [options]
|
||||
|
||||
Common Options:
|
||||
--outdir <path> Output directory (default: "dist")
|
||||
--minify Enable minification (or --minify.whitespace, --minify.syntax, etc)
|
||||
--sourcemap <type> Sourcemap type: none|linked|inline|external
|
||||
--target <target> Build target: browser|bun|node
|
||||
--format <format> Output format: esm|cjs|iife
|
||||
--splitting Enable code splitting
|
||||
--packages <type> Package handling: bundle|external
|
||||
--public-path <path> Public path for assets
|
||||
--env <mode> Environment handling: inline|disable|prefix*
|
||||
--conditions <list> Package.json export conditions (comma separated)
|
||||
--external <list> External packages (comma separated)
|
||||
--banner <text> Add banner text to output
|
||||
--footer <text> Add footer text to output
|
||||
--define <obj> Define global constants (e.g. --define.VERSION=1.0.0)
|
||||
--help, -h Show this help message
|
||||
|
||||
Example:
|
||||
bun run build.ts --outdir=dist --minify --sourcemap=linked --external=react,react-dom
|
||||
`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const toCamelCase = (str: string): string => str.replace(/-([a-z])/g, g => g[1].toUpperCase());
|
||||
|
||||
const parseValue = (value: string): any => {
|
||||
if (value === "true") return true;
|
||||
if (value === "false") return false;
|
||||
|
||||
if (/^\d+$/.test(value)) return parseInt(value, 10);
|
||||
if (/^\d*\.\d+$/.test(value)) return parseFloat(value);
|
||||
|
||||
if (value.includes(",")) return value.split(",").map(v => v.trim());
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
function parseArgs(): Partial<Bun.BuildConfig> {
|
||||
const config: Partial<Bun.BuildConfig> = {};
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
if (arg === undefined) continue;
|
||||
if (!arg.startsWith("--")) continue;
|
||||
|
||||
if (arg.startsWith("--no-")) {
|
||||
const key = toCamelCase(arg.slice(5));
|
||||
config[key] = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!arg.includes("=") && (i === args.length - 1 || args[i + 1]?.startsWith("--"))) {
|
||||
const key = toCamelCase(arg.slice(2));
|
||||
config[key] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
let key: string;
|
||||
let value: string;
|
||||
|
||||
if (arg.includes("=")) {
|
||||
[key, value] = arg.slice(2).split("=", 2) as [string, string];
|
||||
} else {
|
||||
key = arg.slice(2);
|
||||
value = args[++i] ?? "";
|
||||
}
|
||||
|
||||
key = toCamelCase(key);
|
||||
|
||||
if (key.includes(".")) {
|
||||
const [parentKey, childKey] = key.split(".");
|
||||
config[parentKey] = config[parentKey] || {};
|
||||
config[parentKey][childKey] = parseValue(value);
|
||||
} else {
|
||||
config[key] = parseValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
const formatFileSize = (bytes: number): string => {
|
||||
const units = ["B", "KB", "MB", "GB"];
|
||||
let size = bytes;
|
||||
let unitIndex = 0;
|
||||
|
||||
while (size >= 1024 && unitIndex < units.length - 1) {
|
||||
size /= 1024;
|
||||
unitIndex++;
|
||||
}
|
||||
|
||||
return `${size.toFixed(2)} ${units[unitIndex]}`;
|
||||
};
|
||||
|
||||
console.log("\n🚀 Starting build process...\n");
|
||||
|
||||
const cliConfig = parseArgs();
|
||||
const outdir = cliConfig.outdir || path.join(process.cwd(), "dist");
|
||||
|
||||
if (existsSync(outdir)) {
|
||||
console.log(`🗑️ Cleaning previous build at ${outdir}`);
|
||||
await rm(outdir, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
const start = performance.now();
|
||||
|
||||
const entrypoints = [...new Bun.Glob("**.html").scanSync("src")]
|
||||
.map(a => path.resolve("src", a))
|
||||
.filter(dir => !dir.includes("node_modules"));
|
||||
console.log(`📄 Found ${entrypoints.length} HTML ${entrypoints.length === 1 ? "file" : "files"} to process\n`);
|
||||
|
||||
const result = await Bun.build({
|
||||
entrypoints,
|
||||
outdir,
|
||||
plugins: [plugin],
|
||||
minify: true,
|
||||
target: "browser",
|
||||
sourcemap: "linked",
|
||||
define: {
|
||||
"process.env.NODE_ENV": JSON.stringify("production"),
|
||||
},
|
||||
...cliConfig,
|
||||
});
|
||||
|
||||
const end = performance.now();
|
||||
|
||||
const outputTable = result.outputs.map(output => ({
|
||||
File: path.relative(process.cwd(), output.path),
|
||||
Type: output.kind,
|
||||
Size: formatFileSize(output.size),
|
||||
}));
|
||||
|
||||
console.table(outputTable);
|
||||
const buildTime = (end - start).toFixed(2);
|
||||
|
||||
console.log(`\n✅ Build completed in ${buildTime}ms\n`);
|
||||
17
frontend/bun-env.d.ts
vendored
Normal file
17
frontend/bun-env.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// Generated by `bun init`
|
||||
|
||||
declare module "*.svg" {
|
||||
/**
|
||||
* A path to the SVG file
|
||||
*/
|
||||
const path: `${string}.svg`;
|
||||
export = path;
|
||||
}
|
||||
|
||||
declare module "*.module.css" {
|
||||
/**
|
||||
* A record of class names to their corresponding CSS module classes
|
||||
*/
|
||||
const classes: { readonly [key: string]: string };
|
||||
export = classes;
|
||||
}
|
||||
68
frontend/bun.lock
Normal file
68
frontend/bun.lock
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "bun-react-template",
|
||||
"dependencies": {
|
||||
"bun-plugin-tailwind": "^0.1.2",
|
||||
"react": "^19",
|
||||
"react-dom": "^19",
|
||||
"tailwindcss": "^4.1.11",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.3.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-WeXSaL29ylJEZMYHHW28QZ6rgAbxQ1KuNSZD9gvd3fPlo0s6s2PglvPArjjP07nmvIK9m4OffN0k4M98O7WmAg=="],
|
||||
|
||||
"@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.3.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-CFKjoUWQH0Oz3UHYfKbdKLq0wGryrFsTJEYq839qAwHQSECvVZYAnxVVDYUDa0yQFonhO2qSHY41f6HK+b7xtw=="],
|
||||
|
||||
"@oven/bun-darwin-x64-baseline": ["@oven/bun-darwin-x64-baseline@1.3.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-+FSr/ub5vA/EkD3fMhHJUzYioSf/sXd50OGxNDAntVxcDu4tXL/81Ka3R/gkZmjznpLFIzovU/1Ts+b7dlkrfw=="],
|
||||
|
||||
"@oven/bun-linux-aarch64": ["@oven/bun-linux-aarch64@1.3.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-WHthS/eLkCNcp9pk4W8aubRl9fIUgt2XhHyLrP0GClB1FVvmodu/zIOtG0NXNpzlzB8+gglOkGo4dPjfVf4Z+g=="],
|
||||
|
||||
"@oven/bun-linux-aarch64-musl": ["@oven/bun-linux-aarch64-musl@1.3.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-HT5sr7N8NDYbQRjAnT7ISpx64y+ewZZRQozOJb0+KQObKvg4UUNXGm4Pn1xA4/WPMZDDazjO8E2vtOQw1nJlAQ=="],
|
||||
|
||||
"@oven/bun-linux-x64": ["@oven/bun-linux-x64@1.3.0", "", { "os": "linux", "cpu": "x64" }, "sha512-sGEWoJQXO4GDr0x4t/yJQ/Bq1yNkOdX9tHbZZ+DBGJt3z3r7jeb4Digv8xQUk6gdTFC9vnGHuin+KW3/yD1Aww=="],
|
||||
|
||||
"@oven/bun-linux-x64-baseline": ["@oven/bun-linux-x64-baseline@1.3.0", "", { "os": "linux", "cpu": "x64" }, "sha512-OmlEH3nlxQyv7HOvTH21vyNAZGv9DIPnrTznzvKiOQxkOphhCyKvPTlF13ydw4s/i18iwaUrhHy+YG9HSSxa4Q=="],
|
||||
|
||||
"@oven/bun-linux-x64-musl": ["@oven/bun-linux-x64-musl@1.3.0", "", { "os": "linux", "cpu": "x64" }, "sha512-rtzUEzCynl3Rhgn/iR9DQezSFiZMcAXAbU+xfROqsweMGKwvwIA2ckyyckO08psEP8XcUZTs3LT9CH7PnaMiEA=="],
|
||||
|
||||
"@oven/bun-linux-x64-musl-baseline": ["@oven/bun-linux-x64-musl-baseline@1.3.0", "", { "os": "linux", "cpu": "x64" }, "sha512-hrr7mDvUjMX1tuJaXz448tMsgKIqGJBY8+rJqztKOw1U5+a/v2w5HuIIW1ce7ut0ZwEn+KIDvAujlPvpH33vpQ=="],
|
||||
|
||||
"@oven/bun-windows-x64": ["@oven/bun-windows-x64@1.3.0", "", { "os": "win32", "cpu": "x64" }, "sha512-xXwtpZVVP7T+vkxcF/TUVVOGRjEfkByO4mKveKYb4xnHWV4u4NnV0oNmzyMKkvmj10to5j2h0oZxA4ZVVv4gfA=="],
|
||||
|
||||
"@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.3.0", "", { "os": "win32", "cpu": "x64" }, "sha512-/jVZ8eYjpYHLDFNoT86cP+AjuWvpkzFY+0R0a1bdeu0sQ6ILuy1FV6hz1hUAP390E09VCo5oP76fnx29giHTtA=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.0", "", { "dependencies": { "bun-types": "1.3.0" } }, "sha512-+lAGCYjXjip2qY375xX/scJeVRmZ5cY0wyHYyCYxNcdEXrQ4AOe3gACgd4iQ8ksOslJtW4VNxBJ8llUwc3a6AA=="],
|
||||
|
||||
"@types/node": ["@types/node@24.8.0", "", { "dependencies": { "undici-types": "~7.14.0" } }, "sha512-5x08bUtU8hfboMTrJ7mEO4CpepS9yBwAqcL52y86SWNmbPX8LVbNs3EP4cNrIZgdjk2NAlP2ahNihozpoZIxSg=="],
|
||||
|
||||
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
|
||||
|
||||
"@types/react-dom": ["@types/react-dom@19.2.2", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw=="],
|
||||
|
||||
"bun": ["bun@1.3.0", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.3.0", "@oven/bun-darwin-x64": "1.3.0", "@oven/bun-darwin-x64-baseline": "1.3.0", "@oven/bun-linux-aarch64": "1.3.0", "@oven/bun-linux-aarch64-musl": "1.3.0", "@oven/bun-linux-x64": "1.3.0", "@oven/bun-linux-x64-baseline": "1.3.0", "@oven/bun-linux-x64-musl": "1.3.0", "@oven/bun-linux-x64-musl-baseline": "1.3.0", "@oven/bun-windows-x64": "1.3.0", "@oven/bun-windows-x64-baseline": "1.3.0" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-YI7mFs7iWc/VsGsh2aw6eAPD2cjzn1j+LKdYVk09x1CrdTWKYIHyd+dG5iQoN9//3hCDoZj8U6vKpZzEf5UARA=="],
|
||||
|
||||
"bun-plugin-tailwind": ["bun-plugin-tailwind@0.1.2", "", { "peerDependencies": { "bun": ">=1.0.0" } }, "sha512-41jNC1tZRSK3s1o7pTNrLuQG8kL/0vR/JgiTmZAJ1eHwe0w5j6HFPKeqEk0WAD13jfrUC7+ULuewFBBCoADPpg=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.0", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ=="],
|
||||
|
||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||
|
||||
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
|
||||
|
||||
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
|
||||
|
||||
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||
|
||||
"tailwindcss": ["tailwindcss@4.1.14", "", {}, "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA=="],
|
||||
|
||||
"undici-types": ["undici-types@7.14.0", "", {}, "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA=="],
|
||||
}
|
||||
}
|
||||
4
frontend/bunfig.toml
Normal file
4
frontend/bunfig.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
[serve.static]
|
||||
plugins = ["bun-plugin-tailwind"]
|
||||
env = "BUN_PUBLIC_*"
|
||||
22
frontend/package.json
Normal file
22
frontend/package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "bun-react-template",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "bun --hot src/index.tsx",
|
||||
"start": "NODE_ENV=production bun src/index.tsx",
|
||||
"build": "bun run build.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"bun-plugin-tailwind": "^0.1.2",
|
||||
"react": "^19",
|
||||
"react-dom": "^19",
|
||||
"tailwindcss": "^4.1.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@types/bun": "latest"
|
||||
}
|
||||
}
|
||||
60
frontend/src/APITester.tsx
Normal file
60
frontend/src/APITester.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { useRef, type FormEvent } from "react";
|
||||
|
||||
export function APITester() {
|
||||
const responseInputRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
const testEndpoint = async (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
const form = e.currentTarget;
|
||||
const formData = new FormData(form);
|
||||
const endpoint = formData.get("endpoint") as string;
|
||||
const url = new URL(endpoint, "http://localhost:8912");
|
||||
const method = formData.get("method") as string;
|
||||
const res = await fetch(url, { method });
|
||||
|
||||
const data = await res.json();
|
||||
responseInputRef.current!.value = JSON.stringify(data, null, 2);
|
||||
} catch (error) {
|
||||
responseInputRef.current!.value = String(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-8 mx-auto w-full max-w-2xl text-left flex flex-col gap-4">
|
||||
<form
|
||||
onSubmit={testEndpoint}
|
||||
className="flex items-center gap-2 bg-[#1a1a1a] p-3 rounded-xl font-mono border-2 border-[#fbf0df] transition-colors duration-300 focus-within:border-[#f3d5a3] w-full"
|
||||
>
|
||||
<select
|
||||
name="method"
|
||||
className="bg-[#fbf0df] text-[#1a1a1a] py-1.5 px-3 rounded-lg font-bold text-sm min-w-[0px] appearance-none cursor-pointer hover:bg-[#f3d5a3] transition-colors duration-100"
|
||||
>
|
||||
<option value="GET" className="py-1">
|
||||
GET
|
||||
</option>
|
||||
</select>
|
||||
<input
|
||||
type="text"
|
||||
name="endpoint"
|
||||
defaultValue="/api/v1/ping"
|
||||
className="w-full flex-1 bg-transparent border-0 text-[#fbf0df] font-mono text-base py-1.5 px-2 outline-none focus:text-white placeholder-[#fbf0df]/40"
|
||||
placeholder="/api/v1/ping"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-[#fbf0df] text-[#1a1a1a] border-0 px-5 py-1.5 rounded-lg font-bold transition-all duration-100 hover:bg-[#f3d5a3] hover:-translate-y-px cursor-pointer whitespace-nowrap"
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
</form>
|
||||
<textarea
|
||||
ref={responseInputRef}
|
||||
readOnly
|
||||
placeholder="Response will appear here..."
|
||||
className="w-full min-h-[140px] bg-[#1a1a1a] border-2 border-[#fbf0df] rounded-xl p-3 text-[#fbf0df] font-mono resize-y focus:border-[#f3d5a3] placeholder-[#fbf0df]/40"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
16
frontend/src/App.tsx
Normal file
16
frontend/src/App.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { APITester } from "./APITester";
|
||||
import "./index.css";
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto p-8 text-center relative z-10">
|
||||
<div className="flex justify-center items-center gap-8 mb-8">
|
||||
</div>
|
||||
|
||||
<h1 className="text-5xl font-bold my-4 leading-tight">React + Gin</h1>
|
||||
<APITester />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
20
frontend/src/frontend.tsx
Normal file
20
frontend/src/frontend.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* This file is the entry point for the React app, it sets up the root
|
||||
* element and renders the App component to the DOM.
|
||||
*
|
||||
* It is included in `src/index.html`.
|
||||
*/
|
||||
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { App } from "./App";
|
||||
|
||||
function start() {
|
||||
const root = createRoot(document.getElementById("root")!);
|
||||
root.render(<App />);
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", start);
|
||||
} else {
|
||||
start();
|
||||
}
|
||||
49
frontend/src/index.css
Normal file
49
frontend/src/index.css
Normal file
@@ -0,0 +1,49 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
@apply text-[rgba(255,255,255,0.87)] bg-[#242424] font-sans;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply grid place-items-center min-w-[320px] min-h-screen relative m-0;
|
||||
}
|
||||
}
|
||||
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
opacity: 0.05;
|
||||
background-size: 256px;
|
||||
transform: rotate(-12deg) scale(1.35);
|
||||
animation: slide 30s linear infinite;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes slide {
|
||||
from {
|
||||
background-position: 0 0;
|
||||
}
|
||||
to {
|
||||
background-position: 256px 224px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
*,
|
||||
::before,
|
||||
::after {
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
12
frontend/src/index.html
Normal file
12
frontend/src/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>React + Gin</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./frontend.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
19
frontend/src/index.tsx
Normal file
19
frontend/src/index.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { serve } from "bun";
|
||||
import index from "./index.html";
|
||||
|
||||
const server = serve({
|
||||
routes: {
|
||||
// Serve index.html for all unmatched routes.
|
||||
"/*": index,
|
||||
},
|
||||
|
||||
development: process.env.NODE_ENV !== "production" && {
|
||||
// Enable browser hot reloading in development
|
||||
hmr: true,
|
||||
|
||||
// Echo console logs from the browser to the server
|
||||
console: true,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Server running at ${server.url}`);
|
||||
1
frontend/tmp/build-errors.log
Normal file
1
frontend/tmp/build-errors.log
Normal file
@@ -0,0 +1 @@
|
||||
exit status 1
|
||||
36
frontend/tsconfig.json
Normal file
36
frontend/tsconfig.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Environment setup & latest features
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"target": "ESNext",
|
||||
"module": "Preserve",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
},
|
||||
|
||||
"exclude": ["dist", "node_modules"]
|
||||
}
|
||||
Reference in New Issue
Block a user