feat: initial commit

This commit is contained in:
Iain Learmonth 2026-01-08 16:19:27 +00:00
commit 98355eceb5
18 changed files with 5416 additions and 0 deletions

4
.dockerignore Normal file
View file

@ -0,0 +1,4 @@
.react-router
build
node_modules
README.md

8
.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
.DS_Store
.env
/node_modules/
# React Router
/.react-router/
/build/
/.idea

22
Dockerfile Normal file
View file

@ -0,0 +1,22 @@
FROM node:20-alpine AS development-dependencies-env
COPY . /app
WORKDIR /app
RUN npm ci
FROM node:20-alpine AS production-dependencies-env
COPY ./package.json package-lock.json /app/
WORKDIR /app
RUN npm ci --omit=dev
FROM node:20-alpine AS build-env
COPY . /app/
COPY --from=development-dependencies-env /app/node_modules /app/node_modules
WORKDIR /app
RUN npm run build
FROM node:20-alpine
COPY ./package.json package-lock.json /app/
COPY --from=production-dependencies-env /app/node_modules /app/node_modules
COPY --from=build-env /app/build /app/build
WORKDIR /app
CMD ["npm", "run", "start"]

87
README.md Normal file
View file

@ -0,0 +1,87 @@
# Welcome to React Router!
A modern, production-ready template for building full-stack React applications using React Router.
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/remix-run/react-router-templates/tree/main/default)
## Features
- 🚀 Server-side rendering
- ⚡️ Hot Module Replacement (HMR)
- 📦 Asset bundling and optimization
- 🔄 Data loading and mutations
- 🔒 TypeScript by default
- 🎉 TailwindCSS for styling
- 📖 [React Router docs](https://reactrouter.com/)
## Getting Started
### Installation
Install the dependencies:
```bash
npm install
```
### Development
Start the development server with HMR:
```bash
npm run dev
```
Your application will be available at `http://localhost:5173`.
## Building for Production
Create a production build:
```bash
npm run build
```
## Deployment
### Docker Deployment
To build and run using Docker:
```bash
docker build -t my-app .
# Run the container
docker run -p 3000:3000 my-app
```
The containerized application can be deployed to any platform that supports Docker, including:
- AWS ECS
- Google Cloud Run
- Azure Container Apps
- Digital Ocean App Platform
- Fly.io
- Railway
### DIY Deployment
If you're familiar with deploying Node applications, the built-in app server is production-ready.
Make sure to deploy the output of `npm run build`
```
├── package.json
├── package-lock.json (or pnpm-lock.yaml, or bun.lockb)
├── build/
│ ├── client/ # Static assets
│ └── server/ # Server-side code
```
## Styling
This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever CSS framework you prefer.
---
Built with ❤️ using React Router.

19
app/app.css Normal file
View file

@ -0,0 +1,19 @@
html,
body {
margin: 0;
padding: 0;
}
.logo {
margin: 0;
padding: 5px;
color: white;
display: flex;
align-items: center;
font-size: large;
cursor: pointer;
img {
width: 48px;
padding: 5px 10px 5px 5px;
}
}

77
app/root.tsx Normal file
View file

@ -0,0 +1,77 @@
import {
isRouteErrorResponse,
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "react-router";
import { ConfigProvider } from 'antd';
import type { Route } from "./+types/root";
import "./app.css";
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
export default function App() {
return <ConfigProvider
theme={{
token: {
// Seed Token
colorPrimary: "#00b353",
borderRadius: 2,
// Alias Token
// colorBgContainer: '#f6ffed',
},
}}
>
<Outlet />
</ConfigProvider>
}
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
let message = "Oops!";
let details = "An unexpected error occurred.";
let stack: string | undefined;
if (isRouteErrorResponse(error)) {
message = error.status === 404 ? "404" : "Error";
details =
error.status === 404
? "The requested page could not be found."
: error.statusText || details;
} else if (import.meta.env.DEV && error && error instanceof Error) {
details = error.message;
stack = error.stack;
}
return (
<>
<h1>{message}</h1>
<p>{details}</p>
{stack && (
<pre className="w-full p-4 overflow-x-auto">
<code>{stack}</code>
</pre>
)}
</>
);
}

12
app/routes.ts Normal file
View file

@ -0,0 +1,12 @@
import {type RouteConfig, index, layout, route} from "@react-router/dev/routes";
export default [
layout("routes/base_layout.tsx",
[
index("routes/home.tsx"),
route("proxy", "routes/proxy.tsx", [
index("routes/proxy/overview.tsx"),
route("origins", "routes/proxy/origins.tsx"),
])
]),
] satisfies RouteConfig;

View file

@ -0,0 +1,74 @@
import React from 'react';
import {CloudServerOutlined, DashboardOutlined, GlobalOutlined, HomeOutlined} from '@ant-design/icons';
import {Layout, Menu} from 'antd';
import {Outlet, useNavigate} from "react-router";
const {Content, Footer, Sider} = Layout;
const items = [{
key: "home", icon: React.createElement(HomeOutlined), label: "Home", "data-navigate": "/",
}, {
key: "proxy", icon: React.createElement(CloudServerOutlined), label: "Smart Proxy", children: [{
key: "proxy/overview",
icon: React.createElement(DashboardOutlined),
label: "Overview",
"data-navigate": "/proxy",
}, {
key: "proxy/origins",
icon: React.createElement(GlobalOutlined),
label: "Web Origins",
"data-navigate": "/proxy/origins",
}]
}];
export default function BaseLayout() {
const navigate = useNavigate();
// @ts-ignore TODO
const addOnClick = (items) => {
// @ts-ignore TODO
return items.map((item) => {
return item ? {
...item,
children: item.children ? addOnClick(item.children) : null,
onClick: item["data-navigate"] ? () => {
navigate(item["data-navigate"]);
} : undefined
} : null;
})
};
const menuItems = addOnClick(items);
return (<Layout>
<Sider
breakpoint="lg"
collapsedWidth="0"
onBreakpoint={(broken) => {
console.log(broken);
}}
onCollapse={(collapsed, type) => {
console.log(collapsed, type);
}}
style={{height: "100vh"}}
>
<h1 className="logo" onClick={() => navigate("/")}>
<img src="https://jasima.app/img/logo.png" alt=""/>
jasima.app
</h1>
<Menu theme="dark" mode="inline" defaultSelectedKeys={['a']} items={menuItems}/>
</Sider>
<Layout>
<Content style={{margin: '24px 16px 0'}}>
<div
style={{
padding: 24, minHeight: 360, background: "white",
}}
>
<Outlet/>
</div>
</Content>
<Footer style={{textAlign: 'center'}}>
Copyright © 2021-{new Date().getFullYear()} SR2 Communications Limited.
</Footer>
</Layout>
</Layout>)
}

7
app/routes/home.tsx Normal file
View file

@ -0,0 +1,7 @@
import {Typography} from "antd";
const {Paragraph} = Typography;
export default function Home() {
return <Paragraph>hi</Paragraph>;
}

21
app/routes/proxy.tsx Normal file
View file

@ -0,0 +1,21 @@
import type { Route } from "../+types/root";
import {CloudServerOutlined} from "@ant-design/icons";
import {Breadcrumb, Typography} from "antd";
import {Outlet} from "react-router";
const { Title } = Typography;
export function meta({}: Route.MetaArgs) {
return [
{ title: "Smart Proxy Administration" },
{ name: "description", content: "Hi!" },
];
}
export default function Proxy() {
return <>
<Title><CloudServerOutlined /> Smart Proxy</Title>
<Breadcrumb items={[{title: "Smart Proxy", href: "/proxy"}]} />
<Outlet />
</>;
}

View file

@ -0,0 +1,4 @@
export default function Origins() {
return "hello origins";
}

View file

@ -0,0 +1,7 @@
import {Typography} from "antd";
const {Paragraph} = Typography;
export default function Overview() {
return <Paragraph>hi</Paragraph>;
}

5000
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

32
package.json Normal file
View file

@ -0,0 +1,32 @@
{
"name": "frontend",
"private": true,
"type": "module",
"scripts": {
"build": "react-router build",
"dev": "react-router dev",
"start": "react-router-serve ./build/server/index.js",
"typecheck": "react-router typegen && tsc"
},
"dependencies": {
"@ant-design/icons": "^6.1.0",
"@react-router/node": "7.10.1",
"@react-router/serve": "7.10.1",
"antd": "^6.1.4",
"isbot": "^5.1.31",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"react-router": "7.10.1"
},
"devDependencies": {
"@react-router/dev": "7.10.1",
"@tailwindcss/vite": "^4.1.13",
"@types/node": "^22",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"tailwindcss": "^4.1.13",
"typescript": "^5.9.2",
"vite": "^7.1.7",
"vite-tsconfig-paths": "^5.1.4"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

7
react-router.config.ts Normal file
View file

@ -0,0 +1,7 @@
import type { Config } from "@react-router/dev/config";
export default {
// Config options...
// Server-side render by default, to enable SPA mode set this to `false`
ssr: false,
} satisfies Config;

27
tsconfig.json Normal file
View file

@ -0,0 +1,27 @@
{
"include": [
"**/*",
"**/.server/**/*",
"**/.client/**/*",
".react-router/types/**/*"
],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["node", "vite/client"],
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"rootDirs": [".", "./.react-router/types"],
"baseUrl": ".",
"paths": {
"~/*": ["./app/*"]
},
"esModuleInterop": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true
}
}

8
vite.config.ts Normal file
View file

@ -0,0 +1,8 @@
import { reactRouter } from "@react-router/dev/vite";
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
plugins: [tailwindcss(), reactRouter(), tsconfigPaths()],
});