chore(frontend): scaffold Vite React TypeScript application
This commit is contained in:
@@ -38,6 +38,8 @@ node_modules/
|
|||||||
.npm/
|
.npm/
|
||||||
package-lock.json
|
package-lock.json
|
||||||
!speech_to_speech/package-lock.json
|
!speech_to_speech/package-lock.json
|
||||||
|
!frontend-s2s/package-lock.json
|
||||||
|
frontend-s2s/.eslintcache
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Existing s2s-client FastAPI server. Used only by Vite's server-side proxy.
|
||||||
|
S2S_BACKEND_URL=http://127.0.0.1:8420
|
||||||
|
# Comma-separated hostnames, only needed when accessing Vite through a remote host.
|
||||||
|
S2S_DEV_ALLOWED_HOSTS=
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
dist/
|
||||||
|
coverage/
|
||||||
|
node_modules/
|
||||||
|
package-lock.json
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Vocero React frontend
|
||||||
|
|
||||||
|
React, TypeScript, Tailwind CSS, and Vite client for the existing `s2s-client` FastAPI backend.
|
||||||
|
|
||||||
|
Requires Node.js 22.18+ or 24+ and npm. Run `npm ci`, then `npm run dev`.
|
||||||
|
The frontend listens on `http://127.0.0.1:5173` and proxies backend requests to
|
||||||
|
`http://127.0.0.1:8420`. Copy `.env.example` to `.env.local` to override that target.
|
||||||
|
|
||||||
|
`npm run check` runs formatting, strict ESLint, TypeScript, and Vitest checks.
|
||||||
|
`npm run build` typechecks and produces `dist/`. `npm run format` and `npm run lint:fix`
|
||||||
|
apply mechanical formatting and lint fixes.
|
||||||
|
|
||||||
|
The repository's `start_ui.sh` still serves the original UI. This project is started
|
||||||
|
separately during the frontend migration.
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import type { ProxyOptions } from "vite";
|
||||||
|
|
||||||
|
/** Preserve the public origin used by FastAPI to construct the speech WebSocket URL. */
|
||||||
|
export function createBackendProxy(target: string): Record<string, ProxyOptions> {
|
||||||
|
const http: ProxyOptions = { target, changeOrigin: false };
|
||||||
|
return {
|
||||||
|
"/api": http,
|
||||||
|
"/v1/realtime": { ...http, ws: true },
|
||||||
|
"/docs": http,
|
||||||
|
"/redoc": http,
|
||||||
|
"/openapi.json": http,
|
||||||
|
"/oauth": http,
|
||||||
|
"/project": http,
|
||||||
|
"/public/docs.html": http,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import js from "@eslint/js";
|
||||||
|
import { defineConfig, globalIgnores } from "eslint/config";
|
||||||
|
import prettier from "eslint-config-prettier/flat";
|
||||||
|
import reactHooks from "eslint-plugin-react-hooks";
|
||||||
|
import reactRefresh from "eslint-plugin-react-refresh";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
globalIgnores(["dist/**", "coverage/**", "node_modules/**"]),
|
||||||
|
{
|
||||||
|
files: ["**/*.{ts,tsx}"],
|
||||||
|
extends: [js.configs.recommended, tseslint.configs.strictTypeChecked, tseslint.configs.stylisticTypeChecked],
|
||||||
|
languageOptions: { parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname } },
|
||||||
|
rules: {
|
||||||
|
"@typescript-eslint/consistent-type-imports": ["error", { fixStyle: "inline-type-imports" }],
|
||||||
|
"@typescript-eslint/no-import-type-side-effects": "error",
|
||||||
|
"@typescript-eslint/no-unnecessary-condition": "off",
|
||||||
|
"@typescript-eslint/restrict-template-expressions": ["error", { allowNumber: true }],
|
||||||
|
"@typescript-eslint/prefer-nullish-coalescing": ["error", { ignorePrimitives: { string: true } }],
|
||||||
|
"no-console": ["error", { allow: ["warn", "error"] }],
|
||||||
|
"no-debugger": "error",
|
||||||
|
curly: ["error", "all"],
|
||||||
|
eqeqeq: ["error", "always", { null: "ignore" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["src/**/*.{ts,tsx}"],
|
||||||
|
plugins: { "react-hooks": { rules: reactHooks.rules }, "react-refresh": reactRefresh },
|
||||||
|
rules: {
|
||||||
|
...reactHooks.configs.recommended.rules,
|
||||||
|
"react-hooks/exhaustive-deps": "error",
|
||||||
|
"react-refresh/only-export-components": ["error", { allowConstantExport: true }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
prettier,
|
||||||
|
]);
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||||
|
<meta name="theme-color" content="#07080d" />
|
||||||
|
<title>Vocero · Conversación de voz</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Generated
+4609
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"name": "frontend-s2s",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"engines": {
|
||||||
|
"node": "^22.18.0 || >=24.0.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "npm run typecheck && vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"lint": "eslint . --cache --cache-strategy content --max-warnings 0",
|
||||||
|
"lint:fix": "npm run lint -- --fix",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"format:check": "prettier --check .",
|
||||||
|
"typecheck": "tsc -b",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest",
|
||||||
|
"check": "npm run format:check && npm run lint && npm run typecheck && npm test"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"dompurify": "^3.4.16",
|
||||||
|
"lucide-react": "^1.52.0",
|
||||||
|
"marked": "^18.1.0",
|
||||||
|
"react": "^19.2.8",
|
||||||
|
"react-dom": "^19.2.8",
|
||||||
|
"zod": "^4.6.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^10.0.1",
|
||||||
|
"@tailwindcss/vite": "^4.3.3",
|
||||||
|
"@testing-library/jest-dom": "^7.0.1",
|
||||||
|
"@testing-library/react": "^16.3.3",
|
||||||
|
"@testing-library/user-event": "^14.6.7",
|
||||||
|
"@types/node": "^24.13.3",
|
||||||
|
"@types/react": "^19.2.18",
|
||||||
|
"@types/react-dom": "^19.2.7",
|
||||||
|
"@vitejs/plugin-react": "^6.1.1",
|
||||||
|
"eslint": "^10.12.0",
|
||||||
|
"eslint-config-prettier": "^10.1.8",
|
||||||
|
"eslint-plugin-react-hooks": "^7.1.1",
|
||||||
|
"eslint-plugin-react-refresh": "^0.5.7",
|
||||||
|
"jiti": "^2.7.0",
|
||||||
|
"jsdom": "^30.1.2",
|
||||||
|
"prettier": "^3.9.9",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.8.1",
|
||||||
|
"tailwindcss": "^4.3.3",
|
||||||
|
"typescript": "~6.0.2",
|
||||||
|
"typescript-eslint": "^8.71.1",
|
||||||
|
"vite": "^8.3.0",
|
||||||
|
"vitest": "^5.0.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import type { Config } from "prettier";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
arrowParens: "always",
|
||||||
|
endOfLine: "lf",
|
||||||
|
plugins: ["prettier-plugin-tailwindcss"],
|
||||||
|
printWidth: 120,
|
||||||
|
semi: true,
|
||||||
|
singleQuote: false,
|
||||||
|
tabWidth: 2,
|
||||||
|
tailwindStylesheet: "./src/styles.css",
|
||||||
|
trailingComma: "all",
|
||||||
|
} satisfies Config & { tailwindStylesheet: string };
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export default function App() {
|
||||||
|
return <main>Vocero</main>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { StrictMode } from "react";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
import "./styles.css";
|
||||||
|
import App from "./App.tsx";
|
||||||
|
|
||||||
|
const root = document.getElementById("root");
|
||||||
|
if (!root) {
|
||||||
|
throw new Error("The application root was not found.");
|
||||||
|
}
|
||||||
|
createRoot(root).render(
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>,
|
||||||
|
);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
@layer theme, base, components, utilities;
|
||||||
|
@import "tailwindcss/theme.css" layer(theme);
|
||||||
|
@import "tailwindcss/utilities.css" layer(utilities);
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { createBackendProxy } from "../../config/backend-proxy";
|
||||||
|
|
||||||
|
describe("development backend proxy", () => {
|
||||||
|
it("keeps the public host and original paths for the FastAPI runtime URL", () => {
|
||||||
|
const proxy = createBackendProxy("http://127.0.0.1:8420");
|
||||||
|
expect(proxy["/api"]).toEqual({ target: "http://127.0.0.1:8420", changeOrigin: false });
|
||||||
|
expect(proxy["/v1/realtime"]).toEqual({ target: "http://127.0.0.1:8420", changeOrigin: false, ws: true });
|
||||||
|
expect(proxy["/openapi.json"]).toEqual(proxy["/api"]);
|
||||||
|
expect(proxy["/oauth"]).toEqual(proxy["/api"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import "@testing-library/jest-dom/vitest";
|
||||||
|
|
||||||
|
import { cleanup } from "@testing-library/react";
|
||||||
|
import { afterEach } from "vitest";
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
window.localStorage.clear();
|
||||||
|
window.sessionStorage.clear();
|
||||||
|
});
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"target": "es2023",
|
||||||
|
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "esnext",
|
||||||
|
"types": ["vite/client"],
|
||||||
|
"allowArbitraryExtensions": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "es2023",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"types": ["node"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["*.config.ts", "config/**/*.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
import { defineConfig, loadEnv } from "vite";
|
||||||
|
|
||||||
|
import { createBackendProxy } from "./config/backend-proxy.ts";
|
||||||
|
|
||||||
|
export default defineConfig(({ mode }) => {
|
||||||
|
// Read this project's configuration; the repository .env contains server secrets.
|
||||||
|
const env = loadEnv(mode, process.cwd(), "S2S_");
|
||||||
|
return {
|
||||||
|
plugins: [react(), tailwindcss()],
|
||||||
|
resolve: { alias: { "@": fileURLToPath(new URL("./src", import.meta.url)) } },
|
||||||
|
server: {
|
||||||
|
host: "127.0.0.1",
|
||||||
|
port: 5173,
|
||||||
|
strictPort: true,
|
||||||
|
allowedHosts: (env.S2S_DEV_ALLOWED_HOSTS ?? "")
|
||||||
|
.split(",")
|
||||||
|
.map((host) => host.trim())
|
||||||
|
.filter(Boolean),
|
||||||
|
proxy: createBackendProxy(env.S2S_BACKEND_URL ?? "http://127.0.0.1:8420"),
|
||||||
|
},
|
||||||
|
build: { target: "es2022" },
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
environment: "jsdom",
|
||||||
|
execArgv: process.allowedNodeEnvironmentFlags.has("--no-experimental-webstorage")
|
||||||
|
? ["--no-experimental-webstorage"]
|
||||||
|
: [],
|
||||||
|
setupFiles: ["./src/test/setup.ts"],
|
||||||
|
restoreMocks: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user