mirror of
https://github.com/temetro/temetro.git
synced 2026-07-26 11:58:14 +00:00
release workflow: publish multi-arch images; settings: confirm language switch
- release.yml: add QEMU + platforms: linux/amd64,linux/arm64 to both build-push steps so the published images work on Apple Silicon (fixes 'no matching manifest for linux/arm64/v8' on docker compose pull). - Settings -> Profile language picker is now a select that opens a confirmation dialog before applying, instead of switching instantly. - CLAUDE.md: require a dated docs changelog entry for every release. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,11 @@ jobs:
|
||||
id: meta
|
||||
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# QEMU lets the amd64 runner emulate arm64 so the images below build for
|
||||
# both platforms (Intel + Apple Silicon self-hosters).
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
@@ -49,6 +54,7 @@ jobs:
|
||||
with:
|
||||
context: ./backend
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
${{ env.REGISTRY_NAMESPACE }}/temetro-backend:${{ steps.meta.outputs.version }}
|
||||
${{ env.REGISTRY_NAMESPACE }}/temetro-backend:latest
|
||||
@@ -58,6 +64,7 @@ jobs:
|
||||
with:
|
||||
context: ./frontend
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
${{ env.REGISTRY_NAMESPACE }}/temetro-frontend:${{ steps.meta.outputs.version }}
|
||||
${{ env.REGISTRY_NAMESPACE }}/temetro-frontend:latest
|
||||
|
||||
@@ -80,6 +80,11 @@ accurate (e.g. a new backend route needs an `content/docs/api/*.mdx` entry; a UI
|
||||
in the matching guide; status changes belong in the roadmap). Commit docs changes inside that
|
||||
repo, separately from this one.
|
||||
|
||||
**Every release must also get a dated entry in the docs changelog**
|
||||
(`content/docs/changelog.mdx`, newest first) — not just the monorepo `CHANGELOG.md`. When you cut a
|
||||
version (see "Always release after pushing"), add a matching, user-facing section to that page in the
|
||||
same session so `../temetro/docs` never falls behind the shipped version.
|
||||
|
||||
## Running the stack
|
||||
|
||||
From `backend/`: ensure a `.env` exists (`cp .env.example .env`, then set `BETTER_AUTH_SECRET` via
|
||||
|
||||
@@ -6,7 +6,15 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Select,
|
||||
SelectItem,
|
||||
SelectPopup,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { DeleteAccountDialog } from "@/components/settings/delete-account-dialog";
|
||||
import {
|
||||
CopyField,
|
||||
@@ -68,6 +76,10 @@ export function ProfilePanel() {
|
||||
const [baselineName, setBaselineName] = useState("");
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||
// A language picked in the Select but not yet applied — its presence opens the
|
||||
// confirmation dialog. The Select stays bound to `activeLang`, so cancelling
|
||||
// (clearing this) automatically reverts the shown selection.
|
||||
const [pendingLang, setPendingLang] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
@@ -222,21 +234,23 @@ export function ProfilePanel() {
|
||||
>
|
||||
<SettingsCard className="space-y-1.5 p-5">
|
||||
<FieldLabel>{t("settings.profile.language.label")}</FieldLabel>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{supportedLanguages.map((lng) => (
|
||||
<Button
|
||||
aria-pressed={activeLang === lng}
|
||||
className="rounded-3xl"
|
||||
key={lng}
|
||||
onClick={() => void i18n.changeLanguage(lng)}
|
||||
size="sm"
|
||||
type="button"
|
||||
variant={activeLang === lng ? "default" : "outline"}
|
||||
>
|
||||
{t(`settings.profile.language.${lng}`)}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
if (value !== activeLang) setPendingLang(value);
|
||||
}}
|
||||
value={activeLang}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectPopup>
|
||||
{supportedLanguages.map((lng) => (
|
||||
<SelectItem key={lng} value={lng}>
|
||||
{t(`settings.profile.language.${lng}`)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectPopup>
|
||||
</Select>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
@@ -316,6 +330,27 @@ export function ProfilePanel() {
|
||||
|
||||
<DeleteAccountDialog onOpenChange={setDeleteOpen} open={deleteOpen} />
|
||||
|
||||
<ConfirmDialog
|
||||
cancelLabel={t("settings.profile.language.cancel")}
|
||||
confirmLabel={t("settings.profile.language.confirmCta")}
|
||||
description={
|
||||
pendingLang
|
||||
? t("settings.profile.language.confirmBody", {
|
||||
language: t(`settings.profile.language.${pendingLang}`),
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
onConfirm={() => {
|
||||
if (pendingLang) void i18n.changeLanguage(pendingLang);
|
||||
}}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setPendingLang(null);
|
||||
}}
|
||||
open={pendingLang !== null}
|
||||
title={t("settings.profile.language.confirmTitle")}
|
||||
variant="default"
|
||||
/>
|
||||
|
||||
{dirty ? (
|
||||
<div className="sticky bottom-4 z-10">
|
||||
<div className="flex items-center justify-between gap-4 rounded-2xl border border-border bg-background/95 px-4 py-3 shadow-lg backdrop-blur">
|
||||
|
||||
@@ -1689,7 +1689,11 @@
|
||||
"description": "The language temetro's interface is shown in on this device.",
|
||||
"label": "Display language",
|
||||
"en": "English",
|
||||
"fr": "Français"
|
||||
"fr": "Français",
|
||||
"confirmTitle": "Change display language?",
|
||||
"confirmBody": "Switch the interface to {{language}}? The app will reload its text in the new language.",
|
||||
"confirmCta": "Change language",
|
||||
"cancel": "Cancel"
|
||||
},
|
||||
"patientNotifications": "Patient notifications",
|
||||
"patientNotificationsDescription": "Emails sent to patients about their records, results, and pending approvals",
|
||||
|
||||
@@ -1689,7 +1689,11 @@
|
||||
"description": "La langue dans laquelle l'interface de temetro s'affiche sur cet appareil.",
|
||||
"label": "Langue d'affichage",
|
||||
"en": "English",
|
||||
"fr": "Français"
|
||||
"fr": "Français",
|
||||
"confirmTitle": "Changer la langue d'affichage ?",
|
||||
"confirmBody": "Basculer l'interface en {{language}} ? L'application rechargera ses textes dans la nouvelle langue.",
|
||||
"confirmCta": "Changer la langue",
|
||||
"cancel": "Annuler"
|
||||
},
|
||||
"patientNotifications": "Notifications patients",
|
||||
"patientNotificationsDescription": "E-mails envoyés aux patients à propos de leurs dossiers, résultats et approbations en attente",
|
||||
|
||||
Reference in New Issue
Block a user