diff --git a/app/.env.development b/app/.env.development
new file mode 100644
index 00000000..8872edda
--- /dev/null
+++ b/app/.env.development
@@ -0,0 +1,2 @@
+NEXT_PUBLIC_API_URI=http://localhost:5623
+NEXT_PUBLIC_API_WS_URI=ws://localhost:5623
diff --git a/app/.env.example b/app/.env.example
deleted file mode 100644
index 18cee82e..00000000
--- a/app/.env.example
+++ /dev/null
@@ -1,2 +0,0 @@
-NEXT_PUBLIC_API_URI=
-NEXT_PUBLIC_API_WS_URI=
diff --git a/app/.env.production b/app/.env.production
new file mode 100644
index 00000000..8872edda
--- /dev/null
+++ b/app/.env.production
@@ -0,0 +1,2 @@
+NEXT_PUBLIC_API_URI=http://localhost:5623
+NEXT_PUBLIC_API_WS_URI=ws://localhost:5623
diff --git a/app/Dockerfile b/app/Dockerfile
new file mode 100644
index 00000000..a246bc8c
--- /dev/null
+++ b/app/Dockerfile
@@ -0,0 +1,27 @@
+FROM node:20-alpine AS base
+WORKDIR /app
+EXPOSE 3000
+
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS exporter
+WORKDIR /app
+COPY src .
+RUN dotnet run --project "Service.Orchestrator/src/HttpApi/Giantnodes.Service.Orchestrator.HttpApi.csproj" -- schema export --output /app/schema.graphql
+
+FROM base AS installer
+COPY /app/package.json /app/pnpm-lock.yaml* ./
+RUN corepack enable pnpm && pnpm i --frozen-lockfile
+
+FROM base AS builder
+COPY app .
+COPY --from=installer /app/node_modules ./node_modules
+COPY --from=exporter /app/schema.graphql ./
+ENV NODE_ENV production
+RUN corepack enable pnpm && pnpm run build
+
+FROM base AS runner
+COPY --from=builder /app/.next/standalone ./
+COPY --from=builder /app/.next/static ./.next/static
+ENV PORT 3000
+ENV NODE_ENV production
+ENV NEXT_PUBLIC_API_URI http://localhost:5263
+CMD HOSTNAME="0.0.0.0" node server.js
\ No newline at end of file
diff --git a/app/next.config.js b/app/next.config.js
index 394a415f..50ce4079 100644
--- a/app/next.config.js
+++ b/app/next.config.js
@@ -1,5 +1,6 @@
/** @type {import('next').NextConfig} */
const config = {
+ output: 'standalone',
reactStrictMode: true,
compiler: {
relay: {
diff --git a/app/package.json b/app/package.json
index 8757f00f..0da04527 100644
--- a/app/package.json
+++ b/app/package.json
@@ -30,6 +30,7 @@
},
"dependencies": {
"@giantnodes/react": "1.0.0-canary.18",
+ "@giantnodes/theme": "1.0.0-canary.18",
"@hookform/resolvers": "^3.4.0",
"@tabler/icons-react": "^3.4.0",
"clsx": "^2.1.1",
@@ -37,6 +38,7 @@
"filesize": "^10.1.2",
"graphql-ws": "^5.16.0",
"next": "^14.2.3",
+ "next-runtime-env": "^3.2.2",
"next-themes": "^0.3.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
diff --git a/app/pnpm-lock.yaml b/app/pnpm-lock.yaml
index 920b6c20..dac40bb7 100644
--- a/app/pnpm-lock.yaml
+++ b/app/pnpm-lock.yaml
@@ -1,4 +1,4 @@
-lockfileVersion: '6.1'
+lockfileVersion: '6.0'
settings:
autoInstallPeers: true
@@ -8,6 +8,9 @@ dependencies:
'@giantnodes/react':
specifier: 1.0.0-canary.18
version: 1.0.0-canary.18(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.3)
+ '@giantnodes/theme':
+ specifier: 1.0.0-canary.18
+ version: 1.0.0-canary.18(tailwindcss@3.4.3)
'@hookform/resolvers':
specifier: ^3.4.0
version: 3.4.0(react-hook-form@7.51.4)
@@ -29,6 +32,9 @@ dependencies:
next:
specifier: ^14.2.3
version: 14.2.3(react-dom@18.3.1)(react@18.3.1)
+ next-runtime-env:
+ specifier: ^3.2.2
+ version: 3.2.2(next@14.2.3)(react@18.3.1)
next-themes:
specifier: ^0.3.0
version: 0.3.0(react-dom@18.3.1)(react@18.3.1)
@@ -108,7 +114,7 @@ devDependencies:
version: 9.1.0(eslint@8.57.0)
eslint-plugin-import:
specifier: ^2.29.1
- version: 2.29.1(@typescript-eslint/parser@6.19.0)(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0)
+ version: 2.29.1(@typescript-eslint/parser@7.9.0)(eslint@8.57.0)
eslint-plugin-jsx-a11y:
specifier: ^6.8.0
version: 6.8.0(eslint@8.57.0)
@@ -3163,7 +3169,7 @@ packages:
dependencies:
confusing-browser-globals: 1.0.11
eslint: 8.57.0
- eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.19.0)(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0)
+ eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.9.0)(eslint@8.57.0)
object.assign: 4.1.4
object.entries: 1.1.6
semver: 6.3.1
@@ -3196,7 +3202,7 @@ packages:
dependencies:
eslint: 8.57.0
eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1)(eslint@8.57.0)
- eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.19.0)(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0)
+ eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.9.0)(eslint@8.57.0)
eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0)
eslint-plugin-react: 7.34.1(eslint@8.57.0)
eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0)
@@ -3302,6 +3308,35 @@ packages:
- supports-color
dev: true
+ /eslint-module-utils@2.8.0(@typescript-eslint/parser@7.9.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0):
+ resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: '*'
+ eslint-import-resolver-node: '*'
+ eslint-import-resolver-typescript: '*'
+ eslint-import-resolver-webpack: '*'
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+ eslint:
+ optional: true
+ eslint-import-resolver-node:
+ optional: true
+ eslint-import-resolver-typescript:
+ optional: true
+ eslint-import-resolver-webpack:
+ optional: true
+ dependencies:
+ '@typescript-eslint/parser': 7.9.0(eslint@8.57.0)(typescript@5.4.5)
+ debug: 3.2.7
+ eslint: 8.57.0
+ eslint-import-resolver-node: 0.3.9
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.19.0)(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0):
resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==}
engines: {node: '>=4'}
@@ -3337,6 +3372,41 @@ packages:
- supports-color
dev: true
+ /eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.9.0)(eslint@8.57.0):
+ resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+ dependencies:
+ '@typescript-eslint/parser': 7.9.0(eslint@8.57.0)(typescript@5.4.5)
+ array-includes: 3.1.7
+ array.prototype.findlastindex: 1.2.3
+ array.prototype.flat: 1.3.2
+ array.prototype.flatmap: 1.3.2
+ debug: 3.2.7
+ doctrine: 2.1.0
+ eslint: 8.57.0
+ eslint-import-resolver-node: 0.3.9
+ eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.9.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0)
+ hasown: 2.0.0
+ is-core-module: 2.13.1
+ is-glob: 4.0.3
+ minimatch: 3.1.2
+ object.fromentries: 2.0.7
+ object.groupby: 1.0.1
+ object.values: 1.1.7
+ semver: 6.3.1
+ tsconfig-paths: 3.15.0
+ transitivePeerDependencies:
+ - eslint-import-resolver-typescript
+ - eslint-import-resolver-webpack
+ - supports-color
+ dev: true
+
/eslint-plugin-jsx-a11y@6.8.0(eslint@8.57.0):
resolution: {integrity: sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==}
engines: {node: '>=4.0'}
@@ -4495,6 +4565,16 @@ packages:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
dev: true
+ /next-runtime-env@3.2.2(next@14.2.3)(react@18.3.1):
+ resolution: {integrity: sha512-S5S6NxIf3XeaVc9fLBN2L5Jzu+6dLYCXeOaPQa1RzKRYlG2BBayxXOj6A4VsciocyNkJMazW1VAibtbb1/ZjAw==}
+ peerDependencies:
+ next: ^14
+ react: ^18
+ dependencies:
+ next: 14.2.3(react-dom@18.3.1)(react@18.3.1)
+ react: 18.3.1
+ dev: false
+
/next-themes@0.3.0(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==}
peerDependencies:
diff --git a/app/src/app/layout.tsx b/app/src/app/layout.tsx
index 0896c8ac..4495a1eb 100644
--- a/app/src/app/layout.tsx
+++ b/app/src/app/layout.tsx
@@ -2,6 +2,7 @@ import '@/styles/global.css'
import { clsx } from 'clsx'
import { Inter } from 'next/font/google'
+import { PublicEnvScript } from 'next-runtime-env'
import React from 'react'
import ApplicationProviders from '@/app/provider'
@@ -17,7 +18,9 @@ type AppLayoutProps = React.PropsWithChildren & {
const AppLayout: React.FC