diff --git a/.gitignore b/.gitignore
index 7b512e91f..e051fd9fd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,7 +6,6 @@ uv.lock
.mypy_cache
.dmypy.json
.pytest_cache
-.yarn
*.env
!ci.env
@@ -15,7 +14,6 @@ node_modules
coverage.xml
coverage.json
-backend/yarn.lock
backend/static
/process-compose.yml
diff --git a/backend/yarn.lock b/backend/yarn.lock
deleted file mode 100644
index fb57ccd13..000000000
--- a/backend/yarn.lock
+++ /dev/null
@@ -1,4 +0,0 @@
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-
-
diff --git a/docs/.gitignore b/docs/.gitignore
index ed9b6db3e..5fdaaabb5 100644
--- a/docs/.gitignore
+++ b/docs/.gitignore
@@ -18,5 +18,3 @@ next-env.d.ts
_pagefind/
npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
diff --git a/docs/content/community/development.mdx b/docs/content/community/development.mdx
index ee6e633a4..2f9bb0065 100644
--- a/docs/content/community/development.mdx
+++ b/docs/content/community/development.mdx
@@ -35,7 +35,7 @@ The database URL can be specified per environment in the `.env` files (see
## Running the frontend and backend
-To run Bracket (frontend and backend) locally without Docker, one needs `yarn` and `uv`.
+To run Bracket (frontend and backend) locally without Docker, one needs `pnpm` and `uv`.
The following starts the frontend and backend for local development in the root
directory of Bracket:
diff --git a/docs/content/deployment/cloud-services.mdx b/docs/content/deployment/cloud-services.mdx
index 7a57182ad..ea61e0000 100644
--- a/docs/content/deployment/cloud-services.mdx
+++ b/docs/content/deployment/cloud-services.mdx
@@ -1,8 +1,34 @@
---
title: Cloud services
---
+import {Callout} from "nextra/components"
+
# Cloud services
+The frontend of Bracket is powered by Vite and can be simply statically hosted on any cloud
+service, such as Vercel or Cloudflare. Vite's [documentation](https://vite.dev/guide/static-deploy)
+provides detailed instructions on how to deploy your Vite application.
+
+
+Essentially, the only thing you (or a cloud provider) needs to do is to run `pnpm run build`
+to generate a production build, and then serve the contents of the `dist` directory to the internet.
+
+
+## Cloudflare
+
+To deploy the frontend to Cloudflare, go to `Workers & Pages` and click on
+`Import an existing Git repository`.
+
+Make sure to:
+
+- Select `React (Vite)` as framework
+- Select the `frontend` directory as root directory
+
+## GitHub Pages
+
+To deploy the frontend to GitHub Pages, follow the steps outlined in
+Vite's [docs](https://vite.dev/guide/static-deploy#github-pages).
+
## Vercel
To deploy the frontend to Vercel, use the following link:
@@ -11,4 +37,18 @@ To deploy the frontend to Vercel, use the following link:
https://vercel.com/new/project?template=https://github.com/evroon/bracket
```
-Make sure to select the `frontend` directory as root directory, and use Next.js as framework.
+Make sure to:
+
+- Select `Vite` as framework
+- Select the `frontend` directory as root directory
+
+This should work. If it fails, Vercel didn't automatically detect the right build settings.
+Change the following settings under `Build and Output settings`:
+
+- Set the build command to `pnpm run build`
+- Set the install command to `pnpm install`
+
+## Other cloud providers
+
+Vite's [documentation](https://vite.dev/guide/static-deploy)
+provides detailed instructions on how to deploy your Vite application.
diff --git a/docs/content/deployment/systemd.mdx b/docs/content/deployment/systemd.mdx
index 03ebb863a..b86151465 100644
--- a/docs/content/deployment/systemd.mdx
+++ b/docs/content/deployment/systemd.mdx
@@ -8,7 +8,7 @@ This section describes how to deploy Bracket (frontend and backend) as a Systemd
This assumes:
-- You have installed `yarn` and `pipenv`.
+- You have installed `pnpm` and `uv`.
- You have a PostgreSQL cluster running.
- You have cloned Bracket in `/var/lib/bracket`.
- You have created a new user called Bracket with the permissions to read
@@ -29,7 +29,7 @@ After=network.target
Type=simple
User=bracket
WorkingDirectory=/var/lib/bracket/backend
-ExecStart=pipenv run gunicorn -k uvicorn.workers.UvicornWorker bracket.app:app --bind localhost:8400 --workers 1
+ExecStart=uv run gunicorn -k uvicorn.workers.UvicornWorker bracket.app:app --bind localhost:8400 --workers 1
Environment=ENVIRONMENT=PRODUCTION
TimeoutSec=15
Restart=always
diff --git a/docs/content/running-bracket/configuration.mdx b/docs/content/running-bracket/configuration.mdx
index ac07f6719..9d8ec4050 100644
--- a/docs/content/running-bracket/configuration.mdx
+++ b/docs/content/running-bracket/configuration.mdx
@@ -22,7 +22,7 @@ Copy `ci.env` to `prod.env` and fill in the values:
- `ALLOW_INSECURE_HTTP_SSO`: Should not be used in production. Allows use of INSECURE requests for
SSO auth.
- `AUTO_RUN_MIGRATIONS`: Whether to run (alembic) migrations automatically on startup or not.
- Migrations can be applied manually using `pipenv run alembic upgrade head`.
+ Migrations can be applied manually using `uv run alembic upgrade head`.
### Backend: Example configuration file
diff --git a/docs/content/running-bracket/quickstart.mdx b/docs/content/running-bracket/quickstart.mdx
index bbb2b80f2..4c1410c0d 100644
--- a/docs/content/running-bracket/quickstart.mdx
+++ b/docs/content/running-bracket/quickstart.mdx
@@ -22,5 +22,5 @@ be able to view bracket at `http://localhost:3000`. You can log in with the foll
To insert dummy rows into the database, run:
```bash
-sudo docker exec bracket-backend pipenv run ./cli.py create-dev-db
+sudo docker exec bracket-backend uv run ./cli.py create-dev-db
```
diff --git a/docs/package.json b/docs/package.json
index 94dbbbdb7..5d311ad1a 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -8,7 +8,7 @@
"start": "next start",
"prettier:check": "prettier --check \"**/*.{js,jsx,ts,tsx}\"",
"prettier:write": "prettier --write \"**/*.{js,jsx,ts,tsx}\"",
- "test": "pnpm run prettier:write && pnpm markdownlint-cli2 --fix",
+ "test": "pnpm run prettier:write && pnpm markdownlint-cli2 --fix --config .markdownlint-cli2.mjs",
"test-check": "pnpm run prettier:check && pnpm markdownlint-cli2 --config .markdownlint-cli2.mjs",
"lint:markdown": "markdownlint-cli2",
"postbuild": "pagefind --site .next/server/app --output-path out/_pagefind && next-sitemap"
diff --git a/frontend/.gitignore b/frontend/.gitignore
index d838d0a51..5bde71803 100644
--- a/frontend/.gitignore
+++ b/frontend/.gitignore
@@ -22,8 +22,6 @@
# debug
npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
# local env files
.env.local
diff --git a/frontend/Caddyfile b/frontend/Caddyfile
new file mode 100644
index 000000000..46c8ad9a9
--- /dev/null
+++ b/frontend/Caddyfile
@@ -0,0 +1,5 @@
+:3000 {
+ root * /app/dist
+ file_server
+ try_files {path} /index.html
+}
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
index a57fa5347..02bcfc908 100644
--- a/frontend/Dockerfile
+++ b/frontend/Dockerfile
@@ -1,47 +1,23 @@
-# Install dependencies only when needed
-FROM node:22-alpine AS deps
-
-WORKDIR /app
-
-COPY pnpm-lock.yaml package.json ./
-
-RUN corepack enable && pnpm i
-
-# Rebuild the source code only when needed
+# Build static files
FROM node:22-alpine AS builder
WORKDIR /app
-COPY . .
-COPY --from=deps /app/node_modules ./node_modules
+ENV NODE_ENV=production
-RUN corepack enable
+COPY . .
-RUN VITE_API_BASE_URL=http://VITE_API_BASE_URL_PLACEHOLDER \
- VITE_HCAPTCHA_SITE_KEY=VITE_HCAPTCHA_SITE_KEY_PLACEHOLDER \
- pnpm build
+RUN corepack enable && pnpm install && pnpm build
-# Production image, copy all the files and run next
-FROM node:22-alpine AS runner
+# Production image, copy all the static files and run next
+FROM caddy:2-alpine AS runner
WORKDIR /app
-ENV NODE_ENV=production
-
-RUN addgroup -g 1001 --system nodejs && \
- adduser --system vite -u 1001 -G nodejs
-
-COPY --from=builder --chown=vite:nodejs /app/public ./public
-COPY --from=builder /app/node_modules ./node_modules
-COPY --from=builder /app/package.json ./package.json
-
-RUN apk add bash
-
-USER vite
+COPY --from=builder /app/dist /app/dist
+COPY --from=builder /app/Caddyfile /etc/caddy/Caddyfile
EXPOSE 3000
HEALTHCHECK --interval=10s --timeout=5s --retries=5 \
CMD ["wget", "--spider", "http://0.0.0.0:3000", "||", "exit", "1"]
-
-CMD ["pnpm", "start"]
diff --git a/frontend/LICENCE b/frontend/LICENCE
deleted file mode 100644
index 1a88111ae..000000000
--- a/frontend/LICENCE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2022 Vitaly Rtischev
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/frontend/src/components/utils/error_alert.tsx b/frontend/src/components/utils/error_alert.tsx
index 528dbaebf..89bcfb3d5 100644
--- a/frontend/src/components/utils/error_alert.tsx
+++ b/frontend/src/components/utils/error_alert.tsx
@@ -1,4 +1,4 @@
-import { Alert } from '@mantine/core';
+import { Alert, Center } from '@mantine/core';
import { IconAlertCircle } from '@tabler/icons-react';
import React from 'react';
@@ -10,6 +10,7 @@ export function ErrorAlert({ title, message }: { title: string; message: string
color="red"
radius="lg"
variant="outline"
+ w="40rem"
>
{message}
@@ -23,5 +24,9 @@ export default function RequestErrorAlert({ error }: any) {
: 'Error';
const message = `${status_code}: ${error.response ? error.response.data.detail : error.message}`;
- return ;
+ return (
+
+
+
+ );
}
diff --git a/frontend/src/pages/404.tsx b/frontend/src/pages/404.tsx
index c15ecaa16..70365d70e 100644
--- a/frontend/src/pages/404.tsx
+++ b/frontend/src/pages/404.tsx
@@ -2,6 +2,7 @@ import { Button, Container, Group, Text, Title } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router';
+import { tokenPresent } from '../services/local_storage';
import classes from './404.module.css';
export default function NotFoundPage() {
@@ -16,7 +17,11 @@ export default function NotFoundPage() {
{t('not_found_description')}
-
diff --git a/frontend/src/pages/user.tsx b/frontend/src/pages/user.tsx
index 03cece25c..8672b8077 100644
--- a/frontend/src/pages/user.tsx
+++ b/frontend/src/pages/user.tsx
@@ -1,7 +1,9 @@
import { Group, Stack, Title } from '@mantine/core';
+import React from 'react';
import { useTranslation } from 'react-i18next';
import UserForm from '../components/forms/user';
+import RequestErrorAlert from '../components/utils/error_alert';
import { TableSkeletonSingleColumn } from '../components/utils/skeletons';
import { checkForAuthError, getUser } from '../services/adapter';
import Layout from './_layout';
@@ -26,6 +28,7 @@ export default function UserPage() {
return (
{t('edit_profile_title')}
+ {swrUserResponse.error && }
{content}
);
diff --git a/frontend/src/services/local_storage.tsx b/frontend/src/services/local_storage.tsx
index 6f44a0207..012457c84 100644
--- a/frontend/src/services/local_storage.tsx
+++ b/frontend/src/services/local_storage.tsx
@@ -19,7 +19,7 @@ export function performLogoutAndRedirect(t: Translator, navigate: NavigateFuncti
message: '',
autoClose: 10000,
});
- navigate('/login');
+ navigate('/login', { replace: true });
}
export function getLogin() {
diff --git a/process-compose-example.yml b/process-compose-example.yml
index 68eead471..1b927355b 100644
--- a/process-compose-example.yml
+++ b/process-compose-example.yml
@@ -4,7 +4,7 @@ log_level: debug
processes:
frontend:
working_dir: "frontend"
- command: "yarn run dev"
+ command: "pnpm run dev"
availability:
restart: "on_failure"
readiness_probe:
@@ -19,7 +19,7 @@ processes:
backend:
working_dir: "backend"
- command: "pipenv run gunicorn -k bracket.uvicorn.RestartableUvicornWorker bracket.app:app --bind localhost:8400 --workers 1 --reload"
+ command: "uv run gunicorn -k bracket.uvicorn.RestartableUvicornWorker bracket.app:app --bind localhost:8400 --workers 1 --reload"
availability:
restart: "on_failure"
environment:
@@ -36,7 +36,7 @@ processes:
docs:
working_dir: "docs"
- command: "yarn run dev -p 3001"
+ command: "pnpm run dev -p 3001"
availability:
restart: "on_failure"
readiness_probe: