diff --git a/.github/workflows/deploy-test.yml b/.github/workflows/deploy-test.yml index 270c150..6cd7596 100644 --- a/.github/workflows/deploy-test.yml +++ b/.github/workflows/deploy-test.yml @@ -6,13 +6,17 @@ on: - main - 'feature/**' workflow_dispatch: + inputs: + branch: + description: "Branch to deploy" + required: true + default: main jobs: deploy: runs-on: ubuntu-latest env: - BRANCH_NAME: ${{ github.ref_name }} - + BRANCH_NAME: ${{ github.event.inputs.branch || github.head_ref || github.ref_name }} steps: - name: Set up SSH agent uses: webfactory/ssh-agent@v0.7.0 @@ -26,8 +30,8 @@ jobs: - name: Deploy ${{ env.BRANCH_NAME }} to test server run: | - echo "➡️ Starting remote deployment of branch '${BRANCH_NAME}'" + echo "➡️ Starting remote deployment of branch $BRANCH_NAME" ssh -o StrictHostKeyChecking=no \ ${{ secrets.TEST_SERVER_USER }}@${{ secrets.TEST_SERVER_HOST }} \ - "bash ~/deploy_recapp_to_test.sh \"${BRANCH_NAME}\"" - echo "✅ Remote deployment of branch '${BRANCH_NAME}' succeeded" + "export BRANCH_NAME='${BRANCH_NAME}'; bash ~/deploy_recapp_to_test.sh '${BRANCH_NAME}'" + echo "✅ Remote deployment of branch $BRANCH_NAME succeeded" diff --git a/NEWS.md b/NEWS.md index 4fe1125..a98b343 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,2 +1,25 @@ -# recapp 1.6.2 +## [Release] Merge main into production – 2025-06-11 +We have deployed a new version to production! This release merges the latest changes from the `main` branch, bringing new features, improvements, and bug fixes. + +### Highlights + +- **Deployment & Workflow** + - New workflow for deploying to the test server: `.github/workflows/deploy-test.yml` + - Old deployment check workflow removed: `.github/workflows/deploy-check.yml` + - Major refactor of `deployment.sh` for easier log management and improved robustness + +- **Backend** + - Backend Dockerfile now uses `node:20-slim` (was `node:20-alpine`) + - Added `wkhtmltopdf` and related font support to backend Docker image + - Backend version bumped to 1.0.1 + +- **Frontend** + - Improved token refresh and handling in `TokenActor.ts` for more reliable authentication + - Modernized app root handling and user experience (`Root.tsx`): better error handling and loading screen + - Only enable question stats in quiz tab if details are available + - Frontend version bumped to 1.6.3 + +--- + +For a complete list of changes, see the [commit history between `main` and `production`](https://github.com/ecomod-code/recapp/pull/93). diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile index d71929e..b3dd381 100644 --- a/packages/backend/Dockerfile +++ b/packages/backend/Dockerfile @@ -1,4 +1,16 @@ -FROM node:20-alpine +FROM node:20-slim + +# Install wkhtmltopdf + minimal deps, then clean up apt caches +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + wkhtmltopdf \ + fontconfig \ + fonts-dejavu-core \ + ca-certificates \ + libx11-6 \ + libxrender1 \ + libxext6 \ + && rm -rf /var/lib/apt/lists/* WORKDIR /app diff --git a/packages/backend/package.json b/packages/backend/package.json index 71182c2..b1c3403 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -48,5 +48,5 @@ "start": "ts-node ./src/index.ts", "test": "npm test" }, - "version": "1.0.0" + "version": "1.0.1" } diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 844d8fb..ee10955 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -1,7 +1,7 @@ { "name": "@recapp/frontend", "private": true, - "version": "1.6.2", + "version": "1.6.3", "type": "module", "scripts": { "dev": "vite", diff --git a/packages/frontend/src/components/navigation/Root.tsx b/packages/frontend/src/components/navigation/Root.tsx index 650abfb..234ac56 100644 --- a/packages/frontend/src/components/navigation/Root.tsx +++ b/packages/frontend/src/components/navigation/Root.tsx @@ -1,62 +1,105 @@ +// src/components/navigation/Root.tsx + +import React, { useEffect, useState } from "react"; import { Outlet, useNavigate } from "react-router-dom"; -import { useEffect, useState } from "react"; import { i18n } from "@lingui/core"; import { Trans } from "@lingui/react"; import { Layout } from "../../layout/Layout"; import { SystemContext } from "ts-actors-react"; - -import { Button, Modal } from "react-bootstrap"; +import { Button, Modal, Spinner } from "react-bootstrap"; import { cookie } from "../../utils"; import { system } from "../../system"; import { ActorSystem } from "ts-actors"; import { Try, fromError, fromValue } from "tsmonads"; -export const Root = () => { - const [init, setInit] = useState>(fromError(new Error())); - const [rpcError, setRpcError] = useState(""); - - const navigate = useNavigate(); - const onRpcError = () => { - setRpcError(""); - document.location.href = `${import.meta.env.VITE_BACKEND_URI}/auth/logout`; - }; - - useEffect(() => { - const run = async () => { - try { - const s: ActorSystem = await system; - setInit(fromValue(s)); - } catch (e) { - setInit(fromError(e as Error)); - } - }; - run(); - }, []); - - if (rpcError !== "") { - return ( - - {i18n._(rpcError + "-title")} - - - - - - - - ); - } - if (!init) { - return null; - } - if (!cookie("bearer")) { - navigate("/", { replace: true }); - } - return ( - - - - - - ); +export const Root: React.FC = () => { + // 1) Keep the monadic Try so it matches your context type + const [init, setInit] = useState>(fromError(new Error())); + const [rpcError, setRpcError] = useState(""); + const navigate = useNavigate(); + + const onRpcError = () => { + setRpcError(""); + document.location.href = `${import.meta.env.VITE_BACKEND_URI}/auth/logout`; + }; + + // 2) Bootstrap the actor system just once + useEffect(() => { + system + .then(s => setInit(fromValue(s))) + .catch(err => setInit(fromError(err))); + }, []); + + // 3) After a successful init, check for the bearer cookie and redirect if missing + useEffect(() => { + init.match( + // onSuccess + () => { + if (!cookie("bearer")) { + navigate("/", { replace: true }); + } + }, + // onFailure + () => { + /* do nothing on failure; modal handle below */ + } + ); + }, [init, navigate]); + + // 4) If we hit an RPC error, show the modal + if (rpcError) { + return ( + setRpcError("")}> + + + + + + + +
+ +
+ + + +
+ ); + } + + // 5) While initializing, show a centered spinner + const ready = init.match(() => true, () => false); + if (!ready) { + return ( +
+ + + + + +
+ ); + } + + // 6) Everything’s good: render your app + return ( + + + + + + ); }; diff --git a/packages/frontend/src/components/quiz-tabs/QuizStatsTab.tsx b/packages/frontend/src/components/quiz-tabs/QuizStatsTab.tsx index dceba79..7896421 100644 --- a/packages/frontend/src/components/quiz-tabs/QuizStatsTab.tsx +++ b/packages/frontend/src/components/quiz-tabs/QuizStatsTab.tsx @@ -185,8 +185,8 @@ export const QuizStatsTab: React.FC<{ quizData: CurrentQuizState }> = ({ quizDat textUnderlineOffset: "3px", // color: $primary }} - onClick={() => - tryActor.forEach(actor => + // Only activate question stats if there are details available + onClick={() => noDetails ? null : tryActor.forEach(actor => actor.send( actor, CurrentQuizMessages.ActivateQuestionStats(