diff --git a/.github/workflows/web-deploy-accounts.yml b/.github/workflows/web-deploy-accounts.yml
index 8164aea44..61411cac6 100644
--- a/.github/workflows/web-deploy-accounts.yml
+++ b/.github/workflows/web-deploy-accounts.yml
@@ -24,7 +24,7 @@ jobs:
with:
node-version: 20
cache: "yarn"
- cache-dependency-path: "docs/yarn.lock"
+ cache-dependency-path: "web/yarn.lock"
- name: Install dependencies
run: yarn install
diff --git a/.github/workflows/web-deploy-auth.yml b/.github/workflows/web-deploy-auth.yml
index 63a56b95b..d195b62f8 100644
--- a/.github/workflows/web-deploy-auth.yml
+++ b/.github/workflows/web-deploy-auth.yml
@@ -24,7 +24,7 @@ jobs:
with:
node-version: 20
cache: "yarn"
- cache-dependency-path: "docs/yarn.lock"
+ cache-dependency-path: "web/yarn.lock"
- name: Install dependencies
run: yarn install
diff --git a/.github/workflows/web-deploy-cast.yml b/.github/workflows/web-deploy-cast.yml
index be4861c71..c5bbca954 100644
--- a/.github/workflows/web-deploy-cast.yml
+++ b/.github/workflows/web-deploy-cast.yml
@@ -24,7 +24,7 @@ jobs:
with:
node-version: 20
cache: "yarn"
- cache-dependency-path: "docs/yarn.lock"
+ cache-dependency-path: "web/yarn.lock"
- name: Install dependencies
run: yarn install
diff --git a/.github/workflows/web-deploy-payments.yml b/.github/workflows/web-deploy-payments.yml
index 8f4aeae85..367e1db18 100644
--- a/.github/workflows/web-deploy-payments.yml
+++ b/.github/workflows/web-deploy-payments.yml
@@ -24,7 +24,7 @@ jobs:
with:
node-version: 20
cache: "yarn"
- cache-dependency-path: "docs/yarn.lock"
+ cache-dependency-path: "web/yarn.lock"
- name: Install dependencies
run: yarn install
diff --git a/.github/workflows/web-deploy-photos.yml b/.github/workflows/web-deploy-photos.yml
index 64a88421d..cb3a9db86 100644
--- a/.github/workflows/web-deploy-photos.yml
+++ b/.github/workflows/web-deploy-photos.yml
@@ -24,7 +24,7 @@ jobs:
with:
node-version: 20
cache: "yarn"
- cache-dependency-path: "docs/yarn.lock"
+ cache-dependency-path: "web/yarn.lock"
- name: Install dependencies
run: yarn install
diff --git a/.github/workflows/web-deploy-staff.yml b/.github/workflows/web-deploy-staff.yml
new file mode 100644
index 000000000..4d386344d
--- /dev/null
+++ b/.github/workflows/web-deploy-staff.yml
@@ -0,0 +1,48 @@
+name: "Deploy (staff)"
+
+on:
+ # Run on every push to main that changes web/apps/staff/
+ push:
+ branches: [main]
+ paths:
+ - "web/apps/staff/**"
+ - ".github/workflows/web-deploy-staff.yml"
+ # Also allow manually running the workflow
+ workflow_dispatch:
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+
+ defaults:
+ run:
+ working-directory: web
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - name: Setup node and enable yarn caching
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: "yarn"
+ cache-dependency-path: "web/yarn.lock"
+
+ - name: Install dependencies
+ run: yarn install
+
+ - name: Build staff
+ run: yarn build:staff
+
+ - name: Publish staff
+ uses: cloudflare/pages-action@1
+ with:
+ accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
+ apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+ projectName: ente
+ branch: deploy/staff
+ directory: web/apps/staff/dist
+ wranglerVersion: "3"
diff --git a/.github/workflows/web-nightly.yml b/.github/workflows/web-nightly.yml
index 89d5ecaa5..b5d8c411f 100644
--- a/.github/workflows/web-nightly.yml
+++ b/.github/workflows/web-nightly.yml
@@ -34,7 +34,7 @@ jobs:
with:
node-version: 20
cache: "yarn"
- cache-dependency-path: "docs/yarn.lock"
+ cache-dependency-path: "web/yarn.lock"
- name: Install dependencies
run: yarn install
diff --git a/.github/workflows/web-preview.yml b/.github/workflows/web-preview.yml
index 2ad73b7a1..8f39c0247 100644
--- a/.github/workflows/web-preview.yml
+++ b/.github/workflows/web-preview.yml
@@ -34,7 +34,7 @@ jobs:
with:
node-version: 20
cache: "yarn"
- cache-dependency-path: "docs/yarn.lock"
+ cache-dependency-path: "web/yarn.lock"
- name: Install dependencies
run: yarn install
diff --git a/web/apps/payments/README.md b/web/apps/payments/README.md
index eeac0a397..959bedabe 100644
--- a/web/apps/payments/README.md
+++ b/web/apps/payments/README.md
@@ -1,3 +1,5 @@
+# Payments
+
Code that runs on `payments.ente.io`. It brokers between our services and
Stripe's API for payments.
diff --git a/web/apps/staff/.eslintrc.cjs b/web/apps/staff/.eslintrc.cjs
new file mode 100644
index 000000000..99b4b9226
--- /dev/null
+++ b/web/apps/staff/.eslintrc.cjs
@@ -0,0 +1,3 @@
+module.exports = {
+ extends: ["@/build-config/eslintrc-vite"],
+};
diff --git a/web/apps/staff/README.md b/web/apps/staff/README.md
new file mode 100644
index 000000000..e54b674d3
--- /dev/null
+++ b/web/apps/staff/README.md
@@ -0,0 +1,8 @@
+## Staff dashboard
+
+Web app for staff members to help with support etc.
+
+### Deployment
+
+The app gets redeployed whenever a PR is merged into main. See
+[docs/deploy.md](../../docs/deploy.md) for more details.
diff --git a/web/apps/staff/index.html b/web/apps/staff/index.html
new file mode 100644
index 000000000..5600e3a53
--- /dev/null
+++ b/web/apps/staff/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Staff | ente.io
+
+
+
+
+
+
diff --git a/web/apps/staff/package.json b/web/apps/staff/package.json
new file mode 100644
index 000000000..2c7c01998
--- /dev/null
+++ b/web/apps/staff/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "staff",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "build": "tsc && vite build",
+ "dev": "vite",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^18",
+ "react-dom": "^18"
+ },
+ "devDependencies": {
+ "@/build-config": "*",
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
+ "@vitejs/plugin-react": "^4.2",
+ "vite": "^5.2"
+ }
+}
diff --git a/web/apps/staff/src/App.tsx b/web/apps/staff/src/App.tsx
new file mode 100644
index 000000000..f3f0a9f15
--- /dev/null
+++ b/web/apps/staff/src/App.tsx
@@ -0,0 +1,11 @@
+import React from "react";
+import S from "./utils/strings";
+
+export const App: React.FC = () => {
+ return (
+
+ );
+};
diff --git a/web/apps/staff/src/components/Container.tsx b/web/apps/staff/src/components/Container.tsx
new file mode 100644
index 000000000..bf8c57b5d
--- /dev/null
+++ b/web/apps/staff/src/components/Container.tsx
@@ -0,0 +1,5 @@
+import React from "react";
+
+export const Container: React.FC = ({ children }) => (
+ {children}
+);
diff --git a/web/apps/staff/src/main.tsx b/web/apps/staff/src/main.tsx
new file mode 100644
index 000000000..4ed8c3205
--- /dev/null
+++ b/web/apps/staff/src/main.tsx
@@ -0,0 +1,13 @@
+import React from "react";
+import ReactDOM from "react-dom/client";
+import { App } from "./App";
+import "./styles/globals.css";
+
+const root = document.getElementById("root");
+if (!root) throw new Error("Could not load root element to render onto");
+
+ReactDOM.createRoot(root).render(
+
+
+ ,
+);
diff --git a/web/apps/staff/src/services/support-service.ts b/web/apps/staff/src/services/support-service.ts
new file mode 100644
index 000000000..4898e1ee5
--- /dev/null
+++ b/web/apps/staff/src/services/support-service.ts
@@ -0,0 +1,17 @@
+const apiOrigin = import.meta.env.VITE_ENTE_ENDPOINT ?? "https://api.ente.io";
+
+/** Fetch details of the user associated with the given {@link authToken}. */
+export const getUserDetails = async (authToken: string) => {
+ const url = `${apiOrigin}/users/details/v2`;
+ const res = await fetch(url, {
+ headers: {
+ "X-Auth-Token": authToken,
+ },
+ });
+ if (!res.ok) throw new Error(`Failed to fetch ${url}: HTTP ${res.status}`);
+ const json: unknown = await res.json();
+ if (json && typeof json === "object") {
+ return json;
+ }
+ throw new Error(`Unexpected response for ${url}: ${JSON.stringify(json)}`);
+};
diff --git a/web/apps/staff/src/styles/globals.css b/web/apps/staff/src/styles/globals.css
new file mode 100644
index 000000000..158d0e10c
--- /dev/null
+++ b/web/apps/staff/src/styles/globals.css
@@ -0,0 +1,38 @@
+:root {
+ color-scheme: light dark;
+ color: #213547;
+ background-color: #ffffff;
+}
+
+body {
+ font-family: system-ui, sans-serif;
+
+ margin: 0;
+ display: flex;
+ justify-content: center;
+ text-align: center;
+ min-height: 100svh;
+}
+
+a {
+ color: green;
+}
+
+a:hover {
+ color: darkgreen;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+ }
+
+ a {
+ color: lightgreen;
+ }
+
+ a:hover {
+ color: chartreuse;
+ }
+}
diff --git a/web/apps/staff/src/utils/strings.ts b/web/apps/staff/src/utils/strings.ts
new file mode 100644
index 000000000..39b22fcd7
--- /dev/null
+++ b/web/apps/staff/src/utils/strings.ts
@@ -0,0 +1,13 @@
+/**
+ * User facing strings in the app.
+ *
+ * By keeping them separate, we make our lives easier if/when we need to
+ * localize the corresponding pages. Right now, these are just the values in the
+ * default language, English.
+ */
+const S = {
+ hello: "Hello Ente!",
+ error_generic: "Oops, something went wrong.",
+};
+
+export default S;
diff --git a/web/apps/staff/src/vite-env.d.ts b/web/apps/staff/src/vite-env.d.ts
new file mode 100644
index 000000000..b49bd06d0
--- /dev/null
+++ b/web/apps/staff/src/vite-env.d.ts
@@ -0,0 +1,18 @@
+/* Type shims provided by vite, e.g. for asset imports
+ https://vitejs.dev/guide/features.html#client-types */
+
+///
+
+/** Types for the vite injected environment variables */
+interface ImportMetaEnv {
+ /**
+ * Override the origin (scheme://host:port) of Ente's API to connect to.
+ *
+ * This is useful when testing or connecting to alternative installations.
+ */
+ readonly VITE_ENTE_ENDPOINT: string | undefined;
+}
+
+interface ImportMeta {
+ env: ImportMetaEnv;
+}
diff --git a/web/apps/staff/tsconfig.json b/web/apps/staff/tsconfig.json
new file mode 100644
index 000000000..291fed6ca
--- /dev/null
+++ b/web/apps/staff/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "extends": "@/build-config/tsconfig-vite.json",
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/web/apps/staff/tsconfig.node.json b/web/apps/staff/tsconfig.node.json
new file mode 100644
index 000000000..a8d6e3fc8
--- /dev/null
+++ b/web/apps/staff/tsconfig.node.json
@@ -0,0 +1,4 @@
+{
+ "extends": "@/build-config/tsconfig-vite.node.json",
+ "include": ["vite.config.ts"]
+}
diff --git a/web/apps/staff/vite.config.ts b/web/apps/staff/vite.config.ts
new file mode 100644
index 000000000..d89c4f445
--- /dev/null
+++ b/web/apps/staff/vite.config.ts
@@ -0,0 +1,7 @@
+import react from "@vitejs/plugin-react";
+import { defineConfig } from "vite";
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+});
diff --git a/web/docs/deploy.md b/web/docs/deploy.md
index 6b51a0199..6358cb87f 100644
--- a/web/docs/deploy.md
+++ b/web/docs/deploy.md
@@ -29,21 +29,22 @@ and publish to [web.ente.io](https://web.ente.io).
Here is a list of all the deployments, whether or not they are production
deployments, and the action that triggers them:
-| URL | Type | Deployment action |
-| -------------------------------------------- | ---------- | ------------------------------------------- |
-| [web.ente.io](https://web.ente.io) | Production | Push to `deploy/photos` |
-| [photos.ente.io](https://photos.ente.io) | Production | Alias of [web.ente.io](https://web.ente.io) |
-| [auth.ente.io](https://auth.ente.io) | Production | Push to `deploy/auth` |
-| [accounts.ente.io](https://accounts.ente.io) | Production | Push to `deploy/accounts` |
-| [cast.ente.io](https://cast.ente.io) | Production | Push to `deploy/cast` |
-| [payments.ente.io](https://payments.ente.io) | Production | Push to `deploy/payments` |
-| [help.ente.io](https://help.ente.io) | Production | Push to `main` + changes in `docs/` |
-| [accounts.ente.sh](https://accounts.ente.sh) | Preview | Nightly deploy of `main` |
-| [auth.ente.sh](https://auth.ente.sh) | Preview | Nightly deploy of `main` |
-| [cast.ente.sh](https://cast.ente.sh) | Preview | Nightly deploy of `main` |
-| [payments.ente.sh](https://payments.ente.sh) | Preview | Nightly deploy of `main` |
-| [photos.ente.sh](https://photos.ente.sh) | Preview | Nightly deploy of `main` |
-| [preview.ente.sh](https://preview.ente.sh) | Preview | Manually triggered |
+| URL | Type | Deployment action |
+| -------------------------------------------- | ---------- | -------------------------------------------- |
+| [web.ente.io](https://web.ente.io) | Production | Push to `deploy/photos` |
+| [photos.ente.io](https://photos.ente.io) | Production | Alias of [web.ente.io](https://web.ente.io) |
+| [auth.ente.io](https://auth.ente.io) | Production | Push to `deploy/auth` |
+| [accounts.ente.io](https://accounts.ente.io) | Production | Push to `deploy/accounts` |
+| [cast.ente.io](https://cast.ente.io) | Production | Push to `deploy/cast` |
+| [payments.ente.io](https://payments.ente.io) | Production | Push to `deploy/payments` |
+| [help.ente.io](https://help.ente.io) | Production | Push to `main` + changes in `docs/` |
+| [staff.ente.sh](https://staff.ente.sh) | Production | Push to `main` + changes in `web/apps/staff` |
+| [accounts.ente.sh](https://accounts.ente.sh) | Preview | Nightly deploy of `main` |
+| [auth.ente.sh](https://auth.ente.sh) | Preview | Nightly deploy of `main` |
+| [cast.ente.sh](https://cast.ente.sh) | Preview | Nightly deploy of `main` |
+| [payments.ente.sh](https://payments.ente.sh) | Preview | Nightly deploy of `main` |
+| [photos.ente.sh](https://photos.ente.sh) | Preview | Nightly deploy of `main` |
+| [preview.ente.sh](https://preview.ente.sh) | Preview | Manually triggered |
### Other subdomains
@@ -54,8 +55,8 @@ Apart from this, there are also some other deployments:
`albums.ente.io`, it redirects to the `/shared-albums` page (Enhancement:
serve it as a separate app with a smaller bundle size).
-- `family.ente.io` is currently in a separate repositories (Enhancement: bring
- them in here).
+- `family.ente.io` is currently in a separate repository (Enhancement: bring
+ it in here).
### Preview deployments
diff --git a/web/package.json b/web/package.json
index d27f9a6cb..3b8697bd8 100644
--- a/web/package.json
+++ b/web/package.json
@@ -13,6 +13,7 @@
"build:cast": "yarn workspace cast next build",
"build:payments": "yarn workspace payments build",
"build:photos": "yarn workspace photos next build",
+ "build:staff": "yarn workspace staff build",
"deploy:accounts": "open 'https://github.com/ente-io/ente/compare/deploy/accounts...main?quick_pull=1&title=[web]+Deploy+accounts&body=Deploy+accounts.ente.io'",
"deploy:auth": "open 'https://github.com/ente-io/ente/compare/deploy/auth...main?quick_pull=1&title=[web]+Deploy+auth&body=Deploy+auth.ente.io'",
"deploy:cast": "open 'https://github.com/ente-io/ente/compare/deploy/cast...main?quick_pull=1&title=[web]+Deploy+cast&body=Deploy+cast.ente.io'",
@@ -25,6 +26,7 @@
"dev:cast": "yarn workspace cast next dev -p 3001",
"dev:payments": "yarn workspace payments dev",
"dev:photos": "yarn workspace photos next dev",
+ "dev:staff": "yarn workspace staff dev",
"lint": "yarn prettier --check . && yarn workspaces run eslint --report-unused-disable-directives",
"lint-fix": "yarn prettier --write . && yarn workspaces run eslint --fix .",
"preview": "yarn preview:photos",
@@ -32,7 +34,8 @@
"preview:auth": "yarn build:auth && python3 -m http.server -d apps/auth/out 3000",
"preview:cast": "yarn build:cast && python3 -m http.server -d apps/accounts/out 3001",
"preview:payments": "yarn workspace payments preview",
- "preview:photos": "yarn build:photos && python3 -m http.server -d apps/photos/out 3000"
+ "preview:photos": "yarn build:photos && python3 -m http.server -d apps/photos/out 3000",
+ "preview:staff": "yarn workspace staff preview"
},
"resolutions": {
"libsodium": "0.7.9"
diff --git a/web/packages/build-config/eslintrc-vite.js b/web/packages/build-config/eslintrc-vite.js
index 131d8902d..37032546b 100644
--- a/web/packages/build-config/eslintrc-vite.js
+++ b/web/packages/build-config/eslintrc-vite.js
@@ -1,5 +1,5 @@
/* eslint-env node */
module.exports = {
- extends: ["./eslintrc-react.js"],
+ extends: ["./eslintrc-react.js", "plugin:react/jsx-runtime"],
ignorePatterns: [".eslintrc.cjs", "vite.config.ts", "dist"],
};