diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 87fec01..736f589 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -96,6 +96,8 @@ jobs: NPM_AUTH_TOKEN: ${{ steps.app-token.outputs.token }} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + VITE_APP_VERSION: ${{ github.ref_name }} + VITE_GIT_SHA: ${{ github.sha }} run: ./ci/deploy-client.sh trigger-deploy: diff --git a/apps/client/src/components/Footer.tsx b/apps/client/src/components/Footer.tsx new file mode 100644 index 0000000..2f0b8f9 --- /dev/null +++ b/apps/client/src/components/Footer.tsx @@ -0,0 +1,87 @@ +import { useHealthQuery } from "@/generated/graphql"; + +const REPO_URL = "https://github.com/riotbyte-com/cv-generator"; + +const SPA_VERSION = import.meta.env["VITE_APP_VERSION"] ?? "dev"; +const SPA_GIT_SHA = import.meta.env["VITE_GIT_SHA"] ?? "unknown"; +const SPA_BUILD_DATE = import.meta.env["VITE_BUILD_DATE"] ?? ""; + +const isoDate = (iso: string | null | undefined): string => + iso ? iso.slice(0, 10) : ""; + +const tagUrl = (tag: string): string => `${REPO_URL}/releases/tag/${tag}`; +const commitUrl = (sha: string): string => `${REPO_URL}/commit/${sha}`; + +type BuildBadgeProps = { + label: string; + version: string; + gitSha: string; + buildDate: string; +}; + +const BuildBadge = ({ label, version, gitSha, buildDate }: BuildBadgeProps) => { + const shaShort = gitSha && gitSha !== "unknown" ? gitSha.slice(0, 7) : null; + const versionIsTag = version !== "dev" && version !== "?"; + + return ( + + {label}{" "} + {versionIsTag ? ( + + {version} + + ) : ( + version + )} + {shaShort ? ( + <> + {" ("} + + {shaShort} + + {")"} + + ) : null} + {buildDate ? <> built {buildDate} : null} + + ); +}; + +export const Footer = () => { + const { data } = useHealthQuery( + {}, + { + staleTime: 5 * 60 * 1000, + refetchOnWindowFocus: false, + retry: false, + }, + ); + + return ( + + ); +}; diff --git a/apps/client/src/features/app/queries/app.graphql b/apps/client/src/features/app/queries/app.graphql index 6443569..ef86570 100644 --- a/apps/client/src/features/app/queries/app.graphql +++ b/apps/client/src/features/app/queries/app.graphql @@ -4,5 +4,8 @@ query Health { timestamp timezone uptime + version + gitSha + buildDate } } diff --git a/apps/client/src/layouts/AuthenticatedLayout.tsx b/apps/client/src/layouts/AuthenticatedLayout.tsx index 000ddc9..24d16c8 100644 --- a/apps/client/src/layouts/AuthenticatedLayout.tsx +++ b/apps/client/src/layouts/AuthenticatedLayout.tsx @@ -1,5 +1,6 @@ import { Button } from "@cv/ui"; import { Navigate, Outlet } from "react-router-dom"; +import { Footer } from "@/components/Footer"; import { Navbar } from "@/components/Navbar"; import { defaultNavLinks } from "@/components/navLinks"; import { ProfileProvider } from "@/contexts/ProfileProvider"; @@ -80,15 +81,16 @@ export const AuthenticatedLayout = () => { return ( -
+
!l.adminOnly || isAdmin)} /> -
+
+
); diff --git a/ci/deploy-client.sh b/ci/deploy-client.sh index c22267f..50c99c3 100755 --- a/ci/deploy-client.sh +++ b/ci/deploy-client.sh @@ -4,20 +4,26 @@ set -euo pipefail # Builds the client SPA and deploys to Cloudflare Pages. # # Required env vars: -# CLOUDFLARE_API_TOKEN — Cloudflare API token with Pages permissions -# CLOUDFLARE_ACCOUNT_ID — Cloudflare account ID +# CLOUDFLARE_API_TOKEN - Cloudflare API token with Pages permissions +# CLOUDFLARE_ACCOUNT_ID - Cloudflare account ID # # Optional env vars: -# VITE_SERVER_URL — API server URL (default: https://cv-generator-api.riotbyte.com) -# PAGES_PROJECT — Cloudflare Pages project name (default: cv-generator) +# VITE_SERVER_URL - API server URL (default: https://cv-generator-api.riotbyte.com) +# VITE_APP_VERSION - SPA version label baked into the build (default: dev) +# VITE_GIT_SHA - git sha baked into the build (default: unknown) +# VITE_BUILD_DATE - ISO build timestamp baked into the build (default: current UTC time) +# PAGES_PROJECT - Cloudflare Pages project name (default: cv-generator) export VITE_SERVER_URL="${VITE_SERVER_URL:-https://cv-generator-api.riotbyte.com}" +export VITE_APP_VERSION="${VITE_APP_VERSION:-dev}" +export VITE_GIT_SHA="${VITE_GIT_SHA:-unknown}" +export VITE_BUILD_DATE="${VITE_BUILD_DATE:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}" PAGES_PROJECT="${PAGES_PROJECT:-cv-generator}" : "${CLOUDFLARE_API_TOKEN:?CLOUDFLARE_API_TOKEN is required}" : "${CLOUDFLARE_ACCOUNT_ID:?CLOUDFLARE_ACCOUNT_ID is required}" -echo "Building client with VITE_SERVER_URL=${VITE_SERVER_URL}..." +echo "Building client (VITE_SERVER_URL=${VITE_SERVER_URL}, VITE_APP_VERSION=${VITE_APP_VERSION})..." pnpm --filter @cv/client build echo "Deploying to Cloudflare Pages project '${PAGES_PROJECT}'..."