diff --git a/login/CONTRIBUTING.md b/login/CONTRIBUTING.md index a0eb24c4a2..1a84158a0d 100644 --- a/login/CONTRIBUTING.md +++ b/login/CONTRIBUTING.md @@ -46,17 +46,23 @@ The application is now available at `http://localhost:3000` Configure apps/login/.env.local to target the Zitadel instance of your choice. The login app live-reloads on changes, so you can start developing right away. - + +Log in at https://127.0.0.1.sslip.io/ui/v2/login/loginname and use the following credentials: +**Loginname**: *zitadel-admin@zitadel.127.0.0.1.sslip.io* +**Password**: *Password1!*. ### Quality Assurance diff --git a/login/Makefile b/login/Makefile index 836a9d12d6..a2c3fa5c28 100644 --- a/login/Makefile +++ b/login/Makefile @@ -26,7 +26,7 @@ export LOGIN_TEST_ACCEPTANCE_SAMLSP_TAG := login-test-acceptance-samlsp:${DOCKER export LOGIN_TEST_ACCEPTANCE_SAMLIDP_TAG := login-test-acceptance-samlidp:${DOCKER_METADATA_OUTPUT_VERSION} export POSTGRES_TAG := postgres:17.0-alpine3.19 export GOLANG_TAG := golang:1.24-alpine -export ZITADEL_TAG ?= ghcr.io/zitadel/zitadel:v3.3.0 +export ZITADEL_TAG ?= ghcr.io/zitadel/zitadel:02617cf17fdde849378c1a6b5254bbfb2745b164 export CORE_MOCK_TAG := login-core-mock:${DOCKER_METADATA_OUTPUT_VERSION} .PHONY: login-help @@ -52,10 +52,10 @@ login-test-unit: $(LOGIN_BAKE_CLI_WITH_COMMON_ARGS) login-test-unit login-test-integration-build: - $(LOGIN_BAKE_CLI_WITH_COMMON_ARGS) login-core-mock login-test-integration login-standalone + $(LOGIN_BAKE_CLI_WITH_COMMON_ARGS) core-mock login-test-integration login-standalone login-test-integration-dev: login-test-integration-cleanup - $(LOGIN_BAKE_CLI_WITH_COMMON_ARGS) login-core-mock && docker compose --file $(LOGIN_DIR)apps/login-test-integration/docker-compose.yaml run --service-ports --rm login-core-mock + $(LOGIN_BAKE_CLI_WITH_COMMON_ARGS) core-mock && docker compose --file $(LOGIN_DIR)apps/login-test-integration/docker-compose.yaml run --service-ports --rm core-mock login-test-integration-run: login-test-integration-cleanup docker compose --file $(LOGIN_DIR)apps/login-test-integration/docker-compose.yaml run --rm integration @@ -79,8 +79,11 @@ login-test-acceptance-build-compose: login-test-acceptance-build: login-test-acceptance-build-compose login-test-acceptance-build-bake @: -login-test-acceptance-dev: login-test-acceptance-build-compose login-test-acceptance-cleanup - docker compose --file $(LOGIN_DIR)apps/login-test-acceptance/docker-compose.yaml up zitadel setup traefik setup sink +login-test-acceptance-env: login-test-acceptance-build-compose login-test-acceptance-cleanup + docker compose --file $(LOGIN_DIR)apps/login-test-acceptance/docker-compose.yaml run setup + +login-test-acceptance-dev: + docker compose --file $(LOGIN_DIR)apps/login-test-acceptance/docker-compose.yaml up zitadel traefik sink login-test-acceptance-run: login-test-acceptance-cleanup docker compose --file $(LOGIN_DIR)apps/login-test-acceptance/docker-compose.yaml --file $(LOGIN_DIR)apps/login-test-acceptance/docker-compose-ci.yaml run --rm --service-ports acceptance diff --git a/login/apps/login-test-acceptance/docker-compose-ci.yaml b/login/apps/login-test-acceptance/docker-compose-ci.yaml index d52aa172d9..f3b022746c 100644 --- a/login/apps/login-test-acceptance/docker-compose-ci.yaml +++ b/login/apps/login-test-acceptance/docker-compose-ci.yaml @@ -9,7 +9,6 @@ services: setup: environment: - WRITE_ENVIRONMENT_FILE: /login-env/.env ZITADEL_API_DOMAIN: traefik ZITADEL_API_URL: https://traefik LOGIN_BASE_URL: https://traefik/ui/v2/login/ @@ -38,6 +37,8 @@ services: - CI - LOGIN_BASE_URL=https://traefik/ui/v2/login/ - NODE_TLS_REJECT_UNAUTHORIZED=0 + volumes: + - ../login/.env.test.local:/builder/apps/login/.env.test.local ports: - 9323:9323 ipc: "host" diff --git a/login/apps/login-test-acceptance/docker-compose.yaml b/login/apps/login-test-acceptance/docker-compose.yaml index d71711338f..c0fcad01f1 100644 --- a/login/apps/login-test-acceptance/docker-compose.yaml +++ b/login/apps/login-test-acceptance/docker-compose.yaml @@ -5,7 +5,7 @@ services: image: "${ZITADEL_IMAGE:-ghcr.io/zitadel/zitadel:v3.3.0}" container_name: acceptance-zitadel pull_policy: always - command: 'start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled --config /zitadel.yaml --steps /zitadel.yaml' + command: 'start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --config /zitadel.yaml --steps /zitadel.yaml' labels: - "traefik.enable=true" - "traefik.http.routers.zitadel.rule=!PathPrefix(`/ui/v2/login`)" @@ -55,7 +55,7 @@ services: - "traefik.http.routers.login.rule=PathPrefix(`/ui/v2/login`)" - "traefik.http.services.login-service.loadbalancer.server.url=http://host.docker.internal:3000" command: - - "--log.level=DEBUG" +# - "--log.level=DEBUG" - "--ping" - "--api.insecure=true" - "--providers.docker=true" @@ -70,6 +70,7 @@ services: start_period: "20s" ports: - "443:443" + - "8090:8080" volumes: - "/var/run/docker.sock:/var/run/docker.sock:ro" extra_hosts: @@ -87,18 +88,16 @@ services: environment: PAT_FILE: /pat/zitadel-admin-sa.pat ZITADEL_API_INTERNAL_URL: http://zitadel:8080 - WRITE_ENVIRONMENT_FILE: /login-env/.env.local - WRITE_TEST_ENVIRONMENT_FILE: /acceptance-env/.env + WRITE_ENVIRONMENT_FILE: /login-env/.env.test.local SINK_EMAIL_INTERNAL_URL: http://sink:3333/email SINK_SMS_INTERNAL_URL: http://sink:3333/sms SINK_NOTIFICATION_URL: http://localhost:3333/notification - LOGIN_BASE_URL: https://localhost/ui/v2/login/ - ZITADEL_API_URL: https://localhost - ZITADEL_API_DOMAIN: localhost - ZITADEL_ADMIN_USER: zitadel-admin@zitadel.localhost + LOGIN_BASE_URL: https://127.0.0.1.sslip.io/ui/v2/login/ + ZITADEL_API_URL: https://127.0.0.1.sslip.io + ZITADEL_API_DOMAIN: 127.0.0.1.sslip.io + ZITADEL_ADMIN_USER: zitadel-admin@zitadel.127.0.0.1.sslip.io volumes: - ./pat:/pat # Read the PAT file from zitadels setup - - ./env:/acceptance-env # Write the environment variables file for the tests - ../login:/login-env # Write the environment variables file for the login depends_on: traefik: diff --git a/login/apps/login-test-acceptance/env/.gitignore b/login/apps/login-test-acceptance/env/.gitignore deleted file mode 100644 index 377ccd3fdf..0000000000 --- a/login/apps/login-test-acceptance/env/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitkeep diff --git a/login/apps/login-test-acceptance/env/.gitkeep b/login/apps/login-test-acceptance/env/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/login/apps/login-test-acceptance/package.json b/login/apps/login-test-acceptance/package.json index 28f4901b8c..e2f6c8ca04 100644 --- a/login/apps/login-test-acceptance/package.json +++ b/login/apps/login-test-acceptance/package.json @@ -3,6 +3,7 @@ "private": true, "scripts": { "test:acceptance": "pnpm exec playwright", + "test:acceptance:env": "cd ../.. && make login-test-acceptance-env", "test:acceptance:setup": "cd ../.. && make login-test-acceptance-dev" }, "devDependencies": { diff --git a/login/apps/login-test-acceptance/playwright.config.ts b/login/apps/login-test-acceptance/playwright.config.ts index 0e77aa70b0..8025db3238 100644 --- a/login/apps/login-test-acceptance/playwright.config.ts +++ b/login/apps/login-test-acceptance/playwright.config.ts @@ -2,7 +2,7 @@ import { defineConfig, devices } from "@playwright/test"; import dotenv from "dotenv"; import path from "path"; -dotenv.config({ path: path.resolve(__dirname, "./env/.env") }); +dotenv.config({ path: path.resolve(__dirname, "../login/.env.test.local") }); /** * See https://playwright.dev/docs/test-configuration. diff --git a/login/apps/login-test-acceptance/setup/setup.sh b/login/apps/login-test-acceptance/setup/setup.sh index c34f1d0b38..e5fdb25b86 100755 --- a/login/apps/login-test-acceptance/setup/setup.sh +++ b/login/apps/login-test-acceptance/setup/setup.sh @@ -12,8 +12,7 @@ ZITADEL_API_INTERNAL_URL="${ZITADEL_API_INTERNAL_URL:-${ZITADEL_API_URL}}" SINK_EMAIL_INTERNAL_URL="${SINK_EMAIL_INTERNAL_URL:-"http://sink:3333/email"}" SINK_SMS_INTERNAL_URL="${SINK_SMS_INTERNAL_URL:-"http://sink:3333/sms"}" SINK_NOTIFICATION_URL="${SINK_NOTIFICATION_URL:-"http://localhost:3333/notification"}" -WRITE_ENVIRONMENT_FILE=${WRITE_ENVIRONMENT_FILE:-$(dirname "$0")/../apps/login/.env.local} -WRITE_TEST_ENVIRONMENT_FILE=${WRITE_TEST_ENVIRONMENT_FILE:-$(dirname "$0")/../apps/login-test-acceptance/tests/.env.local} +WRITE_ENVIRONMENT_FILE=${WRITE_ENVIRONMENT_FILE:-$(dirname "$0")/../apps/login/.env.test.local} if [ -z "${PAT}" ]; then echo "Reading PAT from file ${PAT_FILE}" @@ -59,7 +58,6 @@ echo "Received ServiceAccount Token: ${SA_PAT}" ################################################################# echo "Writing environment file ${WRITE_ENVIRONMENT_FILE}." -echo "Writing environment file ${WRITE_TEST_ENVIRONMENT_FILE}." echo "ZITADEL_API_URL=${ZITADEL_API_URL} ZITADEL_SERVICE_USER_TOKEN=${SA_PAT} @@ -71,14 +69,11 @@ LOGIN_BASE_URL=${LOGIN_BASE_URL} NODE_TLS_REJECT_UNAUTHORIZED=0 ZITADEL_ADMIN_USER=${ZITADEL_ADMIN_USER:-"zitadel-admin@zitadel.localhost"} NEXT_PUBLIC_BASE_PATH=/ui/v2/login -" | tee "${WRITE_ENVIRONMENT_FILE}" "${WRITE_TEST_ENVIRONMENT_FILE}" > /dev/null +" > ${WRITE_ENVIRONMENT_FILE} echo "Wrote environment file ${WRITE_ENVIRONMENT_FILE}" cat ${WRITE_ENVIRONMENT_FILE} -echo "Wrote environment file ${WRITE_TEST_ENVIRONMENT_FILE}" -cat ${WRITE_TEST_ENVIRONMENT_FILE} - ################################################################# # SMS provider with HTTP ################################################################# diff --git a/login/apps/login-test-acceptance/tests/email-verify.spec.ts b/login/apps/login-test-acceptance/tests/email-verify.spec.ts index 9a672e4767..2c546b8eee 100644 --- a/login/apps/login-test-acceptance/tests/email-verify.spec.ts +++ b/login/apps/login-test-acceptance/tests/email-verify.spec.ts @@ -9,7 +9,7 @@ import { getCodeFromSink } from "./sink"; import { PasswordUser } from "./user"; // Read from ".env" file. -dotenv.config({ path: path.resolve(__dirname, "../env/.env") }); +dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") }); const test = base.extend<{ user: PasswordUser }>({ user: async ({ page }, use) => { diff --git a/login/apps/login-test-acceptance/tests/register.spec.ts b/login/apps/login-test-acceptance/tests/register.spec.ts index 0479d00045..4ad7e9e349 100644 --- a/login/apps/login-test-acceptance/tests/register.spec.ts +++ b/login/apps/login-test-acceptance/tests/register.spec.ts @@ -7,7 +7,7 @@ import { registerWithPasskey, registerWithPassword } from "./register"; import { removeUserByUsername } from "./zitadel"; // Read from ".env" file. -dotenv.config({ path: path.resolve(__dirname, "../env/.env") }); +dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") }); test("register with password", async ({ page }) => { const username = faker.internet.email(); diff --git a/login/apps/login-test-acceptance/tests/username-passkey.spec.ts b/login/apps/login-test-acceptance/tests/username-passkey.spec.ts index 418b338cbd..dff1c65f5a 100644 --- a/login/apps/login-test-acceptance/tests/username-passkey.spec.ts +++ b/login/apps/login-test-acceptance/tests/username-passkey.spec.ts @@ -6,7 +6,7 @@ import { loginScreenExpect, loginWithPasskey } from "./login"; import { PasskeyUser } from "./user"; // Read from ".env" file. -dotenv.config({ path: path.resolve(__dirname, "../env/.env") }); +dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") }); const test = base.extend<{ user: PasskeyUser }>({ user: async ({ page }, use) => { diff --git a/login/apps/login-test-acceptance/tests/username-password-change-required.spec.ts b/login/apps/login-test-acceptance/tests/username-password-change-required.spec.ts index ab883dca34..50605e5ff0 100644 --- a/login/apps/login-test-acceptance/tests/username-password-change-required.spec.ts +++ b/login/apps/login-test-acceptance/tests/username-password-change-required.spec.ts @@ -7,7 +7,7 @@ import { changePassword } from "./password"; import { PasswordUser } from "./user"; // Read from ".env" file. -dotenv.config({ path: path.resolve(__dirname, "../env/.env") }); +dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") }); const test = base.extend<{ user: PasswordUser }>({ user: async ({ page }, use) => { diff --git a/login/apps/login-test-acceptance/tests/username-password-changed.spec.ts b/login/apps/login-test-acceptance/tests/username-password-changed.spec.ts index f424549d67..dc29dc2286 100644 --- a/login/apps/login-test-acceptance/tests/username-password-changed.spec.ts +++ b/login/apps/login-test-acceptance/tests/username-password-changed.spec.ts @@ -8,7 +8,7 @@ import { changePasswordScreen, changePasswordScreenExpect } from "./password-scr import { PasswordUser } from "./user"; // Read from ".env" file. -dotenv.config({ path: path.resolve(__dirname, "../env/.env") }); +dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") }); const test = base.extend<{ user: PasswordUser }>({ user: async ({ page }, use) => { diff --git a/login/apps/login-test-acceptance/tests/username-password-otp_email.spec.ts b/login/apps/login-test-acceptance/tests/username-password-otp_email.spec.ts index 6df5a1a201..e4a77751c1 100644 --- a/login/apps/login-test-acceptance/tests/username-password-otp_email.spec.ts +++ b/login/apps/login-test-acceptance/tests/username-password-otp_email.spec.ts @@ -8,7 +8,7 @@ import { loginScreenExpect, loginWithPassword, loginWithPasswordAndEmailOTP } fr import { OtpType, PasswordUserWithOTP } from "./user"; // Read from ".env" file. -dotenv.config({ path: path.resolve(__dirname, "../env/.env") }); +dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") }); const test = base.extend<{ user: PasswordUserWithOTP; sink: any }>({ user: async ({ page }, use) => { diff --git a/login/apps/login-test-acceptance/tests/username-password-otp_sms.spec.ts b/login/apps/login-test-acceptance/tests/username-password-otp_sms.spec.ts index de05f65f8d..10901cd243 100644 --- a/login/apps/login-test-acceptance/tests/username-password-otp_sms.spec.ts +++ b/login/apps/login-test-acceptance/tests/username-password-otp_sms.spec.ts @@ -8,7 +8,7 @@ import { loginScreenExpect, loginWithPassword, loginWithPasswordAndPhoneOTP } fr import { OtpType, PasswordUserWithOTP } from "./user"; // Read from ".env" file. -dotenv.config({ path: path.resolve(__dirname, "../env/.env") }); +dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") }); const test = base.extend<{ user: PasswordUserWithOTP; sink: any }>({ user: async ({ page }, use) => { diff --git a/login/apps/login-test-acceptance/tests/username-password-set.spec.ts b/login/apps/login-test-acceptance/tests/username-password-set.spec.ts index 4ad8c3b84e..06ce42f1a7 100644 --- a/login/apps/login-test-acceptance/tests/username-password-set.spec.ts +++ b/login/apps/login-test-acceptance/tests/username-password-set.spec.ts @@ -9,7 +9,7 @@ import { resetPasswordScreen, resetPasswordScreenExpect } from "./password-scree import { PasswordUser } from "./user"; // Read from ".env" file. -dotenv.config({ path: path.resolve(__dirname, "../env/.env") }); +dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") }); const test = base.extend<{ user: PasswordUser }>({ user: async ({ page }, use) => { diff --git a/login/apps/login-test-acceptance/tests/username-password-totp.spec.ts b/login/apps/login-test-acceptance/tests/username-password-totp.spec.ts index b76480dd7a..e495b16681 100644 --- a/login/apps/login-test-acceptance/tests/username-password-totp.spec.ts +++ b/login/apps/login-test-acceptance/tests/username-password-totp.spec.ts @@ -8,7 +8,7 @@ import { loginScreenExpect, loginWithPassword, loginWithPasswordAndTOTP } from " import { PasswordUserWithTOTP } from "./user"; // Read from ".env" file. -dotenv.config({ path: path.resolve(__dirname, "../env/.env") }); +dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") }); const test = base.extend<{ user: PasswordUserWithTOTP; sink: any }>({ user: async ({ page }, use) => { diff --git a/login/apps/login-test-acceptance/tests/username-password.spec.ts b/login/apps/login-test-acceptance/tests/username-password.spec.ts index 11385ab014..ceb340f8da 100644 --- a/login/apps/login-test-acceptance/tests/username-password.spec.ts +++ b/login/apps/login-test-acceptance/tests/username-password.spec.ts @@ -10,7 +10,7 @@ import { passwordScreenExpect } from "./password-screen"; import { PasswordUser } from "./user"; // Read from ".env" file. -dotenv.config({ path: path.resolve(__dirname, "../env/.env") }); +dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") }); const test = base.extend<{ user: PasswordUser }>({ user: async ({ page }, use) => { diff --git a/login/apps/login-test-acceptance/tests/zitadel.ts b/login/apps/login-test-acceptance/tests/zitadel.ts index 3838eb7fe2..b252654f86 100644 --- a/login/apps/login-test-acceptance/tests/zitadel.ts +++ b/login/apps/login-test-acceptance/tests/zitadel.ts @@ -7,7 +7,7 @@ import { request } from "gaxios"; import path from "path"; import { OtpType, userProps } from "./user"; -dotenv.config({ path: path.resolve(__dirname, "../env/.env") }); +dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") }); export async function addUser(props: userProps) { const body = { diff --git a/login/apps/login-test-acceptance/zitadel.yaml b/login/apps/login-test-acceptance/zitadel.yaml index ecef8d8334..3ddeaf67f0 100644 --- a/login/apps/login-test-acceptance/zitadel.yaml +++ b/login/apps/login-test-acceptance/zitadel.yaml @@ -1,6 +1,7 @@ -ExternalDomain: localhost +ExternalDomain: 127.0.0.1.sslip.io ExternalSecure: true ExternalPort: 443 +TLS.Enabled: false FirstInstance: PatPath: /pat/zitadel-admin-sa.pat diff --git a/login/apps/login-test-integration/.env.integration b/login/apps/login/.env.test similarity index 81% rename from login/apps/login-test-integration/.env.integration rename to login/apps/login/.env.test index a72ccdecd2..ee70003348 100644 --- a/login/apps/login-test-integration/.env.integration +++ b/login/apps/login/.env.test @@ -1,5 +1,5 @@ +NEXT_PUBLIC_BASE_PATH="" ZITADEL_API_URL=http://localhost:22222 ZITADEL_SERVICE_USER_TOKEN="yolo" EMAIL_VERIFICATION=true DEBUG=true -NEXT_PUBLIC_BASE_PATH="" \ No newline at end of file diff --git a/login/apps/login/.gitignore b/login/apps/login/.gitignore index 05b505239f..deb54dfcc6 100644 --- a/login/apps/login/.gitignore +++ b/login/apps/login/.gitignore @@ -1,3 +1,2 @@ custom-config.js -.env.local -.env.acceptance +.env*.local diff --git a/login/apps/login/locales/de.json b/login/apps/login/locales/de.json index 36dc145120..f7b0d064ba 100644 --- a/login/apps/login/locales/de.json +++ b/login/apps/login/locales/de.json @@ -22,7 +22,8 @@ "loginname": { "title": "Willkommen zurück!", "description": "Geben Sie Ihre Anmeldedaten ein.", - "register": "Neuen Benutzer registrieren" + "register": "Neuen Benutzer registrieren", + "submit": "Weiter" }, "password": { "verify": { diff --git a/login/apps/login/locales/en.json b/login/apps/login/locales/en.json index 0b1cbeb472..6ce32d9833 100644 --- a/login/apps/login/locales/en.json +++ b/login/apps/login/locales/en.json @@ -22,7 +22,8 @@ "loginname": { "title": "Welcome back!", "description": "Enter your login data.", - "register": "Register new user" + "register": "Register new user", + "submit": "Continue" }, "password": { "verify": { diff --git a/login/apps/login/locales/es.json b/login/apps/login/locales/es.json index 5cd40f764a..b9a4140bce 100644 --- a/login/apps/login/locales/es.json +++ b/login/apps/login/locales/es.json @@ -22,7 +22,8 @@ "loginname": { "title": "¡Bienvenido de nuevo!", "description": "Introduce tus datos de acceso.", - "register": "Registrar nuevo usuario" + "register": "Registrar nuevo usuario", + "submit": "Continuar" }, "password": { "verify": { diff --git a/login/apps/login/locales/it.json b/login/apps/login/locales/it.json index a19aa91cfb..109ab15b52 100644 --- a/login/apps/login/locales/it.json +++ b/login/apps/login/locales/it.json @@ -22,7 +22,8 @@ "loginname": { "title": "Bentornato!", "description": "Inserisci i tuoi dati di accesso.", - "register": "Registrati come nuovo utente" + "register": "Registrati come nuovo utente", + "submit": "Continua" }, "password": { "verify": { diff --git a/login/apps/login/locales/pl.json b/login/apps/login/locales/pl.json index b97e7e4b47..0c664101ae 100644 --- a/login/apps/login/locales/pl.json +++ b/login/apps/login/locales/pl.json @@ -22,7 +22,8 @@ "loginname": { "title": "Witamy ponownie!", "description": "Wprowadź dane logowania.", - "register": "Zarejestruj nowego użytkownika" + "register": "Zarejestruj nowego użytkownika", + "submit": "Kontynuuj" }, "password": { "verify": { diff --git a/login/apps/login/locales/ru.json b/login/apps/login/locales/ru.json index 77ea8ba79e..c9fee46295 100644 --- a/login/apps/login/locales/ru.json +++ b/login/apps/login/locales/ru.json @@ -22,7 +22,8 @@ "loginname": { "title": "С возвращением!", "description": "Введите свои данные для входа.", - "register": "Зарегистрировать нового пользователя" + "register": "Зарегистрировать нового пользователя", + "submit": "Продолжить" }, "password": { "verify": { diff --git a/login/apps/login/locales/zh.json b/login/apps/login/locales/zh.json index 0ad9c7e056..1601d7b7bd 100644 --- a/login/apps/login/locales/zh.json +++ b/login/apps/login/locales/zh.json @@ -22,7 +22,8 @@ "loginname": { "title": "欢迎回来!", "description": "请输入您的登录信息。", - "register": "注册新用户" + "register": "注册新用户", + "submit": "继续" }, "password": { "verify": { diff --git a/login/apps/login/next.config.mjs b/login/apps/login/next.config.mjs index 01f22173d0..b84f11a230 100755 --- a/login/apps/login/next.config.mjs +++ b/login/apps/login/next.config.mjs @@ -26,8 +26,6 @@ const secureHeaders = [ key: "X-XSS-Protection", value: "1; mode=block", }, - // img-src vercel.com needed for deploy button, - // script-src va.vercel-scripts.com for analytics/vercel scripts { key: "Content-Security-Policy", value: `${DEFAULT_CSP} frame-ancestors 'none'`, diff --git a/login/apps/login/package.json b/login/apps/login/package.json index 393c8b54ef..49c06d8c3a 100644 --- a/login/apps/login/package.json +++ b/login/apps/login/package.json @@ -3,8 +3,8 @@ "private": true, "type": "module", "scripts": { - "dev": "pnpm exec next dev --turbopack", - "test:unit": "pnpm exec vitest", + "dev": "pnpm next dev --turbopack", + "test:unit": "pnpm vitest", "test:unit:watch": "pnpm test:unit --watch", "lint": "pnpm exec next lint && pnpm exec prettier --check .", "lint:fix": "pnpm exec prettier --write .", @@ -31,10 +31,9 @@ "clsx": "1.2.1", "copy-to-clipboard": "^3.3.3", "deepmerge": "^4.3.1", - "jose": "^5.3.0", "lucide-react": "0.469.0", "moment": "^2.29.4", - "next": "15.4.0-canary.3", + "next": "15.4.0-canary.86", "next-intl": "^3.25.1", "next-themes": "^0.2.1", "nice-grpc": "2.0.1", @@ -42,7 +41,6 @@ "react": "19.1.0", "react-dom": "19.1.0", "react-hook-form": "7.39.5", - "swr": "^2.2.0", "tinycolor2": "1.4.2", "uuid": "^11.1.0" }, diff --git a/login/apps/login/src/app/(login)/accounts/page.tsx b/login/apps/login/src/app/(login)/accounts/page.tsx index 32c88d17bf..a1e99401e2 100644 --- a/login/apps/login/src/app/(login)/accounts/page.tsx +++ b/login/apps/login/src/app/(login)/accounts/page.tsx @@ -1,5 +1,6 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { SessionsList } from "@/components/sessions-list"; +import { Translated } from "@/components/translated"; import { getAllSessionCookieIds } from "@/lib/cookies"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { @@ -9,7 +10,7 @@ import { } from "@/lib/zitadel"; import { UserPlusIcon } from "@heroicons/react/24/outline"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; -import { getLocale, getTranslations } from "next-intl/server"; +import { getLocale } from "next-intl/server"; import { headers } from "next/headers"; import Link from "next/link"; @@ -33,7 +34,6 @@ export default async function Page(props: { }) { const searchParams = await props.searchParams; const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "accounts" }); const requestId = searchParams?.requestId; const organization = searchParams?.organization; @@ -71,8 +71,12 @@ export default async function Page(props: { return (
-

{t("title")}

-

{t("description")}

+

+ +

+

+ +

@@ -81,7 +85,9 @@ export default async function Page(props: {
- {t("addAnother")} + + +
diff --git a/login/apps/login/src/app/(login)/authenticator/set/page.tsx b/login/apps/login/src/app/(login)/authenticator/set/page.tsx index 95b89af92d..a339426c89 100644 --- a/login/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/login/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -3,6 +3,7 @@ import { BackButton } from "@/components/back-button"; import { ChooseAuthenticatorToSetup } from "@/components/choose-authenticator-to-setup"; import { DynamicTheme } from "@/components/dynamic-theme"; import { SignInWithIdp } from "@/components/sign-in-with-idp"; +import { Translated } from "@/components/translated"; import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; @@ -17,7 +18,7 @@ import { listAuthenticationMethodTypes, } from "@/lib/zitadel"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; -import { getLocale, getTranslations } from "next-intl/server"; +import { getLocale } from "next-intl/server"; import { headers } from "next/headers"; import { redirect } from "next/navigation"; @@ -26,8 +27,6 @@ export default async function Page(props: { }) { const searchParams = await props.searchParams; const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "authenticator" }); - const tError = await getTranslations({ locale, namespace: "error" }); const { loginName, requestId, organization, sessionId } = searchParams; @@ -99,7 +98,11 @@ export default async function Page(props: { !sessionWithData.factors || !sessionWithData.factors.user ) { - return {tError("unknownContext")}; + return ( + + + + ); } const branding = await getBrandingSettings({ @@ -165,9 +168,13 @@ export default async function Page(props: { return (
-

{t("title")}

+

+ +

-

{t("description")}

+

+ +

-

{t("linkWithIDP")}

+

+ +

>; }) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale }); const userCode = searchParams?.user_code; const requestId = searchParams?.requestId; const organization = searchParams?.organization; if (!userCode || !requestId) { - return
{t("error.noUserCode")}
; + return ( +
+ +
+ ); } const _headers = await headers(); @@ -34,7 +36,11 @@ export default async function Page(props: { }); if (!deviceAuthorizationRequest) { - return
{t("error.noDeviceRequest")}
; + return ( +
+ +
+ ); } let defaultOrganization; @@ -66,15 +72,19 @@ export default async function Page(props: {

- {t("device.request.title", { - appName: deviceAuthorizationRequest?.appName, - })} +

- {t("device.request.description", { - appName: deviceAuthorizationRequest?.appName, - })} +

>; }) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "device" }); const userCode = searchParams?.user_code; const organization = searchParams?.organization; @@ -37,8 +35,12 @@ export default async function Page(props: { return (
-

{t("usercode.title")}

-

{t("usercode.description")}

+

+ +

+

+ +

diff --git a/login/apps/login/src/app/(login)/error.tsx b/login/apps/login/src/app/(login)/error.tsx index bee6516a59..d14150a4b4 100644 --- a/login/apps/login/src/app/(login)/error.tsx +++ b/login/apps/login/src/app/(login)/error.tsx @@ -2,7 +2,7 @@ import { Boundary } from "@/components/boundary"; import { Button } from "@/components/button"; -import { useTranslations } from "next-intl"; +import { Translated } from "@/components/translated"; import { useEffect } from "react"; export default function Error({ error, reset }: any) { @@ -10,8 +10,6 @@ export default function Error({ error, reset }: any) { console.log("logging error:", error); }, [error]); - const t = useTranslations("error"); - return (
@@ -19,7 +17,9 @@ export default function Error({ error, reset }: any) { Error: {error?.message}
- +
diff --git a/login/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx b/login/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx index de6ad858d9..f2b7a19b91 100644 --- a/login/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx +++ b/login/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx @@ -1,6 +1,7 @@ import { Alert, AlertType } from "@/components/alert"; import { ChooseAuthenticatorToLogin } from "@/components/choose-authenticator-to-login"; import { DynamicTheme } from "@/components/dynamic-theme"; +import { Translated } from "@/components/translated"; import { UserAvatar } from "@/components/user-avatar"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { @@ -11,7 +12,6 @@ import { } from "@/lib/zitadel"; import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; export default async function Page(props: { @@ -19,8 +19,6 @@ export default async function Page(props: { params: Promise<{ provider: string }>; }) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "idp" }); const { organization, userId } = searchParams; @@ -77,8 +75,12 @@ export default async function Page(props: { return (
-

{t("loginError.title")}

- {t("loginError.description")} +

+ +

+ + + {userId && authMethods.length && ( <> diff --git a/login/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/login/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index bfbde8b252..ae9feff6b7 100644 --- a/login/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/login/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -5,6 +5,7 @@ import { linkingFailed } from "@/components/idps/pages/linking-failed"; import { linkingSuccess } from "@/components/idps/pages/linking-success"; import { loginFailed } from "@/components/idps/pages/login-failed"; import { loginSuccess } from "@/components/idps/pages/login-success"; +import { Translated } from "@/components/translated"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { addHuman, @@ -27,7 +28,6 @@ import { AddHumanUserRequestSchema, UpdateHumanUserRequestSchema, } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; const ORG_SUFFIX_REGEX = /(?<=@)(.+)/; @@ -73,8 +73,6 @@ export default async function Page(props: { }) { const params = await props.params; const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "idp" }); let { id, token, requestId, organization, link } = searchParams; const { provider } = params; @@ -321,8 +319,12 @@ export default async function Page(props: { return (
-

{t("registerSuccess.title")}

-

{t("registerSuccess.description")}

+

+ +

+

+ +

>; }) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "idp" }); const requestId = searchParams?.requestId; const organization = searchParams?.organization; @@ -33,8 +31,12 @@ export default async function Page(props: { return (
-

{t("title")}

-

{t("description")}

+

+ +

+

+ +

{identityProviders && ( >; }) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "loginname" }); const loginName = searchParams?.loginName; const requestId = searchParams?.requestId; @@ -63,8 +61,12 @@ export default async function Page(props: { return (
-

{t("title")}

-

{t("description")}

+

+ +

+

+ +

>; }) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "logout" }); const organization = searchParams?.organization; const postLogoutRedirectUri = searchParams?.post_logout_redirect_uri; @@ -67,8 +65,12 @@ export default async function Page(props: { return (
-

{t("title")}

-

{t("description")}

+

+ +

+

+ +

}) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "logout" }); const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); @@ -33,8 +31,12 @@ export default async function Page(props: { searchParams: Promise }) { return (
-

{t("success.title")}

-

{t("success.description")}

+

+ +

+

+ +

); diff --git a/login/apps/login/src/app/(login)/mfa/page.tsx b/login/apps/login/src/app/(login)/mfa/page.tsx index c65d6d3058..5543cdf66f 100644 --- a/login/apps/login/src/app/(login)/mfa/page.tsx +++ b/login/apps/login/src/app/(login)/mfa/page.tsx @@ -2,6 +2,7 @@ import { Alert } from "@/components/alert"; import { BackButton } from "@/components/back-button"; import { ChooseSecondFactor } from "@/components/choose-second-factor"; import { DynamicTheme } from "@/components/dynamic-theme"; +import { Translated } from "@/components/translated"; import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; @@ -11,16 +12,12 @@ import { getSession, listAuthenticationMethodTypes, } from "@/lib/zitadel"; -import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise>; }) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "mfa" }); - const tError = await getTranslations({ locale, namespace: "error" }); const { loginName, requestId, organization, sessionId } = searchParams; @@ -90,9 +87,13 @@ export default async function Page(props: { return (
-

{t("verify.title")}

+

+ +

-

{t("verify.description")}

+

+ +

{sessionFactors && ( )} - {!(loginName || sessionId) && {tError("unknownContext")}} + {!(loginName || sessionId) && ( + + + + )} {sessionFactors ? ( ) : ( - {t("verify.noResults")} + + + )}
diff --git a/login/apps/login/src/app/(login)/mfa/set/page.tsx b/login/apps/login/src/app/(login)/mfa/set/page.tsx index c7f2fa6599..ebfa358d6d 100644 --- a/login/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/login/apps/login/src/app/(login)/mfa/set/page.tsx @@ -2,6 +2,7 @@ import { Alert } from "@/components/alert"; import { BackButton } from "@/components/back-button"; import { ChooseSecondFactorToSetup } from "@/components/choose-second-factor-to-setup"; import { DynamicTheme } from "@/components/dynamic-theme"; +import { Translated } from "@/components/translated"; import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; @@ -15,7 +16,6 @@ import { } from "@/lib/zitadel"; import { Timestamp, timestampDate } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; -import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; function isSessionValid(session: Partial): { @@ -38,9 +38,6 @@ export default async function Page(props: { searchParams: Promise>; }) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "mfa" }); - const tError = await getTranslations({ locale, namespace: "error" }); const { loginName, checkAfter, force, requestId, organization, sessionId } = searchParams; @@ -119,9 +116,13 @@ export default async function Page(props: { return (
-

{t("set.title")}

+

+ +

-

{t("set.description")}

+

+ +

{sessionWithData && ( )} - {!(loginName || sessionId) && {tError("unknownContext")}} + {!(loginName || sessionId) && ( + + + + )} - {!valid && {tError("sessionExpired")}} + {!valid && ( + + + + )} {isSessionValid(sessionWithData).valid && loginSettings && diff --git a/login/apps/login/src/app/(login)/otp/[method]/page.tsx b/login/apps/login/src/app/(login)/otp/[method]/page.tsx index ee58420c42..2d9daac64f 100644 --- a/login/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/login/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -1,6 +1,7 @@ import { Alert } from "@/components/alert"; import { DynamicTheme } from "@/components/dynamic-theme"; import { LoginOTP } from "@/components/login-otp"; +import { Translated } from "@/components/translated"; import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; @@ -10,7 +11,7 @@ import { getLoginSettings, getSession, } from "@/lib/zitadel"; -import { getLocale, getTranslations } from "next-intl/server"; +import { getLocale } from "next-intl/server"; import { headers } from "next/headers"; export default async function Page(props: { @@ -20,8 +21,6 @@ export default async function Page(props: { const params = await props.params; const searchParams = await props.searchParams; const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "otp" }); - const tError = await getTranslations({ locale, namespace: "error" }); const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); @@ -81,20 +80,30 @@ export default async function Page(props: { return (
-

{t("verify.title")}

+

+ +

{method === "time-based" && ( -

{t("verify.totpDescription")}

+

+ +

)} {method === "sms" && ( -

{t("verify.smsDescription")}

+

+ +

)} {method === "email" && ( -

{t("verify.emailDescription")}

+

+ +

)} {!session && (
- {tError("unknownContext")} + + +
)} diff --git a/login/apps/login/src/app/(login)/otp/[method]/set/page.tsx b/login/apps/login/src/app/(login)/otp/[method]/set/page.tsx index d3fc4c89f7..f74093ce8e 100644 --- a/login/apps/login/src/app/(login)/otp/[method]/set/page.tsx +++ b/login/apps/login/src/app/(login)/otp/[method]/set/page.tsx @@ -3,6 +3,7 @@ import { BackButton } from "@/components/back-button"; import { Button, ButtonVariants } from "@/components/button"; import { DynamicTheme } from "@/components/dynamic-theme"; import { TotpRegister } from "@/components/totp-register"; +import { Translated } from "@/components/translated"; import { UserAvatar } from "@/components/user-avatar"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; @@ -14,7 +15,6 @@ import { registerTOTP, } from "@/lib/zitadel"; import { RegisterTOTPResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; import Link from "next/link"; import { redirect } from "next/navigation"; @@ -25,9 +25,6 @@ export default async function Page(props: { }) { const params = await props.params; const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "otp" }); - const tError = await getTranslations({ locale, namespace: "error" }); const { loginName, organization, sessionId, requestId, checkAfter } = searchParams; @@ -128,10 +125,14 @@ export default async function Page(props: { return (
-

{t("set.title")}

+

+ +

{!session && (
- {tError("unknownContext")} + + +
)} @@ -152,7 +153,12 @@ export default async function Page(props: { {totpResponse && "uri" in totpResponse && "secret" in totpResponse ? ( <> -

{t("set.totpRegisterDescription")}

+

+ +

- {t("set.submit")} +
diff --git a/login/apps/login/src/app/(login)/passkey/page.tsx b/login/apps/login/src/app/(login)/passkey/page.tsx index e24585e7e0..bef71986f3 100644 --- a/login/apps/login/src/app/(login)/passkey/page.tsx +++ b/login/apps/login/src/app/(login)/passkey/page.tsx @@ -1,21 +1,18 @@ import { Alert } from "@/components/alert"; import { DynamicTheme } from "@/components/dynamic-theme"; import { LoginPasskey } from "@/components/login-passkey"; +import { Translated } from "@/components/translated"; import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getSession } from "@/lib/zitadel"; -import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise>; }) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "passkey" }); - const tError = await getTranslations({ locale, namespace: "error" }); const { loginName, altPassword, requestId, organization, sessionId } = searchParams; @@ -55,7 +52,9 @@ export default async function Page(props: { return (
-

{t("verify.title")}

+

+ +

{sessionFactors && ( )} -

{t("verify.description")}

+

+ +

- {!(loginName || sessionId) && {tError("unknownContext")}} + {!(loginName || sessionId) && ( + + + + )} {(loginName || sessionId) && ( >; }) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "passkey" }); - const tError = await getTranslations({ locale, namespace: "error" }); const { loginName, prompt, organization, requestId, userId } = searchParams; @@ -37,7 +34,9 @@ export default async function Page(props: { return (
-

{t("set.title")}

+

+ +

{session && ( )} -

{t("set.description")}

+

+ +

- {t("set.info.description")} + - {t("set.info.link")} + {!session && (
- {tError("unknownContext")} + + +
)} diff --git a/login/apps/login/src/app/(login)/password/change/page.tsx b/login/apps/login/src/app/(login)/password/change/page.tsx index 05f8cd6a10..78ba88d282 100644 --- a/login/apps/login/src/app/(login)/password/change/page.tsx +++ b/login/apps/login/src/app/(login)/password/change/page.tsx @@ -1,6 +1,7 @@ import { Alert } from "@/components/alert"; import { ChangePasswordForm } from "@/components/change-password-form"; import { DynamicTheme } from "@/components/dynamic-theme"; +import { Translated } from "@/components/translated"; import { UserAvatar } from "@/components/user-avatar"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; @@ -9,7 +10,6 @@ import { getLoginSettings, getPasswordComplexitySettings, } from "@/lib/zitadel"; -import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; export default async function Page(props: { @@ -19,9 +19,6 @@ export default async function Page(props: { const { serviceUrl } = getServiceUrlFromHeaders(_headers); const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "password" }); - const tError = await getTranslations({ locale, namespace: "error" }); const { loginName, organization, requestId } = searchParams; @@ -53,15 +50,21 @@ export default async function Page(props: {

- {sessionFactors?.factors?.user?.displayName ?? t("change.title")} + {sessionFactors?.factors?.user?.displayName ?? ( + + )}

-

{t("change.description")}

+

+ +

{/* show error only if usernames should be shown to be unknown */} {(!sessionFactors || !loginName) && !loginSettings?.ignoreUnknownUsernames && (
- {tError("unknownContext")} + + +
)} @@ -86,7 +89,9 @@ export default async function Page(props: { /> ) : (
- {tError("failedLoading")} + + +
)}
diff --git a/login/apps/login/src/app/(login)/password/page.tsx b/login/apps/login/src/app/(login)/password/page.tsx index 506454a275..56b0ee4473 100644 --- a/login/apps/login/src/app/(login)/password/page.tsx +++ b/login/apps/login/src/app/(login)/password/page.tsx @@ -1,6 +1,7 @@ import { Alert } from "@/components/alert"; import { DynamicTheme } from "@/components/dynamic-theme"; import { PasswordForm } from "@/components/password-form"; +import { Translated } from "@/components/translated"; import { UserAvatar } from "@/components/user-avatar"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; @@ -11,17 +12,12 @@ import { } from "@/lib/zitadel"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; -import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise>; }) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "password" }); - const tError = await getTranslations({ locale, namespace: "error" }); - let { loginName, organization, requestId, alt } = searchParams; const _headers = await headers(); @@ -66,15 +62,21 @@ export default async function Page(props: {

- {sessionFactors?.factors?.user?.displayName ?? t("verify.title")} + {sessionFactors?.factors?.user?.displayName ?? ( + + )}

-

{t("verify.description")}

+

+ +

{/* show error only if usernames should be shown to be unknown */} {(!sessionFactors || !loginName) && !loginSettings?.ignoreUnknownUsernames && (
- {tError("unknownContext")} + + +
)} diff --git a/login/apps/login/src/app/(login)/password/set/page.tsx b/login/apps/login/src/app/(login)/password/set/page.tsx index 26e065438c..b717fd5d96 100644 --- a/login/apps/login/src/app/(login)/password/set/page.tsx +++ b/login/apps/login/src/app/(login)/password/set/page.tsx @@ -1,6 +1,7 @@ import { Alert, AlertType } from "@/components/alert"; import { DynamicTheme } from "@/components/dynamic-theme"; import { SetPasswordForm } from "@/components/set-password-form"; +import { Translated } from "@/components/translated"; import { UserAvatar } from "@/components/user-avatar"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; @@ -12,7 +13,7 @@ import { } from "@/lib/zitadel"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; -import { getLocale, getTranslations } from "next-intl/server"; +import { getLocale } from "next-intl/server"; import { headers } from "next/headers"; export default async function Page(props: { @@ -20,8 +21,6 @@ export default async function Page(props: { }) { const searchParams = await props.searchParams; const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "password" }); - const tError = await getTranslations({ locale, namespace: "error" }); const { userId, loginName, organization, requestId, code, initial } = searchParams; @@ -73,13 +72,21 @@ export default async function Page(props: { return (
-

{session?.factors?.user?.displayName ?? t("set.title")}

-

{t("set.description")}

+

+ {session?.factors?.user?.displayName ?? ( + + )} +

+

+ +

{/* show error only if usernames should be shown to be unknown */} {loginName && !session && !loginSettings?.ignoreUnknownUsernames && (
- {tError("unknownContext")} + + +
)} @@ -99,7 +106,11 @@ export default async function Page(props: { > ) : null} - {!initial && {t("set.codeSent")}} + {!initial && ( + + + + )} {passwordComplexity && (loginName ?? user?.preferredLoginName) && @@ -115,7 +126,9 @@ export default async function Page(props: { /> ) : (
- {tError("failedLoading")} + + +
)}
diff --git a/login/apps/login/src/app/(login)/register/page.tsx b/login/apps/login/src/app/(login)/register/page.tsx index be872b4dc7..aa83ad1ead 100644 --- a/login/apps/login/src/app/(login)/register/page.tsx +++ b/login/apps/login/src/app/(login)/register/page.tsx @@ -2,6 +2,7 @@ import { Alert } from "@/components/alert"; import { DynamicTheme } from "@/components/dynamic-theme"; import { RegisterForm } from "@/components/register-form"; import { SignInWithIdp } from "@/components/sign-in-with-idp"; +import { Translated } from "@/components/translated"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { getActiveIdentityProviders, @@ -13,7 +14,7 @@ import { } from "@/lib/zitadel"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; -import { getLocale, getTranslations } from "next-intl/server"; +import { getLocale } from "next-intl/server"; import { headers } from "next/headers"; export default async function Page(props: { @@ -21,8 +22,6 @@ export default async function Page(props: { }) { const searchParams = await props.searchParams; const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "register" }); - const tError = await getTranslations({ locale, namespace: "error" }); let { firstname, lastname, email, organization, requestId } = searchParams; @@ -70,8 +69,12 @@ export default async function Page(props: { return (
-

{t("disabled.title")}

-

{t("disabled.description")}

+

+ +

+

+ +

); @@ -80,10 +83,18 @@ export default async function Page(props: { return (
-

{t("title")}

-

{t("description")}

+

+ +

+

+ +

- {!organization && {tError("unknownContext")}} + {!organization && ( + + + + )} {legal && passwordComplexitySettings && @@ -107,7 +118,9 @@ export default async function Page(props: { {loginSettings?.allowExternalIdp && !!identityProviders.length && ( <>
-

{t("orUseIDP")}

+

+ +

>; }) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "register" }); let { firstname, lastname, email, organization, requestId } = searchParams; @@ -57,15 +55,23 @@ export default async function Page(props: { return missingData ? (
-

{t("missingdata.title")}

-

{t("missingdata.description")}

+

+ +

+

+ +

) : loginSettings?.allowRegister && loginSettings.allowUsernamePassword ? (
-

{t("password.title")}

-

{t("description")}

+

+ +

+

+ +

{legal && passwordComplexitySettings && (
-

{t("disabled.title")}

-

{t("disabled.description")}

+

+ +

+

+ +

); diff --git a/login/apps/login/src/app/(login)/signedin/page.tsx b/login/apps/login/src/app/(login)/signedin/page.tsx index dd19096244..5b2ed5fbf4 100644 --- a/login/apps/login/src/app/(login)/signedin/page.tsx +++ b/login/apps/login/src/app/(login)/signedin/page.tsx @@ -1,6 +1,7 @@ import { Alert, AlertType } from "@/components/alert"; import { Button, ButtonVariants } from "@/components/button"; import { DynamicTheme } from "@/components/dynamic-theme"; +import { Translated } from "@/components/translated"; import { UserAvatar } from "@/components/user-avatar"; import { getMostRecentCookieWithLoginname, @@ -14,7 +15,6 @@ import { getLoginSettings, getSession, } from "@/lib/zitadel"; -import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; import Link from "next/link"; @@ -37,8 +37,6 @@ async function loadSessionById( export default async function Page(props: { searchParams: Promise }) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "signedin" }); const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); @@ -66,8 +64,12 @@ export default async function Page(props: { searchParams: Promise }) { return (
-

{t("error.title")}

-

{t("error.description")}

+

+ +

+

+ +

{err.message}
@@ -94,9 +96,15 @@ export default async function Page(props: { searchParams: Promise }) {

- {t("title", { user: sessionFactors?.factors?.user?.displayName })} +

-

{t("description")}

+

+ +

}) { className="self-end" variant={ButtonVariants.Primary} > - {t("continue")} +
diff --git a/login/apps/login/src/app/(login)/u2f/page.tsx b/login/apps/login/src/app/(login)/u2f/page.tsx index b16dc88f4b..7fba7be1be 100644 --- a/login/apps/login/src/app/(login)/u2f/page.tsx +++ b/login/apps/login/src/app/(login)/u2f/page.tsx @@ -1,12 +1,13 @@ import { Alert } from "@/components/alert"; import { DynamicTheme } from "@/components/dynamic-theme"; import { LoginPasskey } from "@/components/login-passkey"; +import { Translated } from "@/components/translated"; import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getSession } from "@/lib/zitadel"; -import { getLocale, getTranslations } from "next-intl/server"; +import { getLocale } from "next-intl/server"; import { headers } from "next/headers"; export default async function Page(props: { @@ -14,8 +15,6 @@ export default async function Page(props: { }) { const searchParams = await props.searchParams; const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "u2f" }); - const tError = await getTranslations({ locale, namespace: "error" }); const { loginName, requestId, sessionId, organization } = searchParams; @@ -59,7 +58,9 @@ export default async function Page(props: { return (
-

{t("verify.title")}

+

+ +

{sessionFactors && ( )} -

{t("verify.description")}

+

+ +

- {!(loginName || sessionId) && {tError("unknownContext")}} + {!(loginName || sessionId) && ( + + + + )} {(loginName || sessionId) && (
-

{t("set.title")}

+

+ +

{sessionFactors && ( )} -

{t("set.description")}

+

+ {" "} + +

{!sessionFactors && (
- {tError("unknownContext")} + + +
)} diff --git a/login/apps/login/src/app/(login)/verify/page.tsx b/login/apps/login/src/app/(login)/verify/page.tsx index 7634ff063a..a61d4e608c 100644 --- a/login/apps/login/src/app/(login)/verify/page.tsx +++ b/login/apps/login/src/app/(login)/verify/page.tsx @@ -1,5 +1,6 @@ import { Alert, AlertType } from "@/components/alert"; import { DynamicTheme } from "@/components/dynamic-theme"; +import { Translated } from "@/components/translated"; import { UserAvatar } from "@/components/user-avatar"; import { VerifyForm } from "@/components/verify-form"; import { sendEmailCode, sendInviteEmailCode } from "@/lib/server/verify"; @@ -7,14 +8,12 @@ import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getUserByID } from "@/lib/zitadel"; import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; -import { getLocale, getTranslations } from "next-intl/server"; +import { getLocale } from "next-intl/server"; import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise }) { const searchParams = await props.searchParams; const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "verify" }); - const tError = await getTranslations({ locale, namespace: "error" }); const { userId, loginName, code, organization, requestId, invite, send } = searchParams; @@ -121,23 +120,26 @@ export default async function Page(props: { searchParams: Promise }) { return (
-

{t("verify.title")}

-

{t("verify.description")}

+

+ +

+

+ +

{!id && ( - <> -

{t("verify.title")}

-

{t("verify.description")}

- -
- {tError("unknownContext")} -
- +
+ + + +
)} {id && send && (
- {t("verify.codeSent")} + + +
)} diff --git a/login/apps/login/src/app/(login)/verify/success/page.tsx b/login/apps/login/src/app/(login)/verify/success/page.tsx index 1668e2e3fd..a0df0327c4 100644 --- a/login/apps/login/src/app/(login)/verify/success/page.tsx +++ b/login/apps/login/src/app/(login)/verify/success/page.tsx @@ -1,4 +1,5 @@ import { DynamicTheme } from "@/components/dynamic-theme"; +import { Translated } from "@/components/translated"; import { UserAvatar } from "@/components/user-avatar"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; @@ -8,13 +9,10 @@ import { getUserByID, } from "@/lib/zitadel"; import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; -import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise }) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "verify" }); const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); @@ -65,8 +63,12 @@ export default async function Page(props: { searchParams: Promise }) { return (
-

{t("successTitle")}

-

{t("successDescription")}

+

+ +

+

+ +

{sessionFactors ? ( void; }) { - const t = useTranslations("error"); - return ( // global-error must include html and body tags @@ -25,7 +23,9 @@ export default function GlobalError({ Error: {error?.message}
- +
diff --git a/login/apps/login/src/app/healthy/route.ts b/login/apps/login/src/app/healthy/route.ts index da41c2cca8..004b096d43 100644 --- a/login/apps/login/src/app/healthy/route.ts +++ b/login/apps/login/src/app/healthy/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; export async function GET() { - return NextResponse.json({}, { status: 200 }); + return NextResponse.json({token: process.env.ZITADEL_SERVICE_USER_TOKEN}, { status: 200 }); } diff --git a/login/apps/login/src/components/authentication-method-radio.tsx b/login/apps/login/src/components/authentication-method-radio.tsx index 1b2af2d167..c3b273ab46 100644 --- a/login/apps/login/src/components/authentication-method-radio.tsx +++ b/login/apps/login/src/components/authentication-method-radio.tsx @@ -1,7 +1,7 @@ "use client"; import { RadioGroup } from "@headlessui/react"; -import { useTranslations } from "next-intl"; +import { Translated } from "./translated"; export enum AuthenticationMethod { Passkey = "passkey", @@ -20,8 +20,6 @@ export function AuthenticationMethodRadio({ selected: any; selectionChanged: (value: any) => void; }) { - const t = useTranslations("register"); - return (
@@ -80,7 +78,18 @@ export function AuthenticationMethodRadio({ as="p" className={`font-medium ${checked ? "" : ""}`} > - {t(`methods.${method}`)} + {method === AuthenticationMethod.Passkey && ( + + )} + {method === AuthenticationMethod.Password && ( + + )}
diff --git a/login/apps/login/src/components/back-button.tsx b/login/apps/login/src/components/back-button.tsx index fe348af9c4..31d4a880ad 100644 --- a/login/apps/login/src/components/back-button.tsx +++ b/login/apps/login/src/components/back-button.tsx @@ -1,11 +1,10 @@ "use client"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { Button, ButtonVariants } from "./button"; +import { Translated } from "./translated"; export function BackButton() { - const t = useTranslations("common"); const router = useRouter(); return ( ); } diff --git a/login/apps/login/src/components/change-password-form.tsx b/login/apps/login/src/components/change-password-form.tsx index 54aab7b3ca..00513d8dda 100644 --- a/login/apps/login/src/components/change-password-form.tsx +++ b/login/apps/login/src/components/change-password-form.tsx @@ -13,7 +13,6 @@ import { import { create } from "@zitadel/client"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; @@ -23,6 +22,7 @@ import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { PasswordComplexity } from "./password-complexity"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Inputs = | { @@ -46,7 +46,6 @@ export function ChangePasswordForm({ requestId, organization, }: Props) { - const t = useTranslations("password"); const router = useRouter(); const { register, handleSubmit, watch, formState } = useForm({ @@ -203,8 +202,8 @@ export function ChangePasswordForm({ onClick={handleSubmit(submitChange)} data-testid="submit-button" > - {loading && } - {t("change.submit")} + {loading && }{" "} +
diff --git a/login/apps/login/src/components/choose-authenticator-to-login.tsx b/login/apps/login/src/components/choose-authenticator-to-login.tsx index f7e3cdf8f8..0f5dd79134 100644 --- a/login/apps/login/src/components/choose-authenticator-to-login.tsx +++ b/login/apps/login/src/components/choose-authenticator-to-login.tsx @@ -3,8 +3,8 @@ import { PasskeysType, } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { useTranslations } from "next-intl"; import { PASSKEYS, PASSWORD } from "./auth-methods"; +import { Translated } from "./translated"; type Props = { authMethods: AuthenticationMethodType[]; @@ -17,13 +17,13 @@ export function ChooseAuthenticatorToLogin({ params, loginSettings, }: Props) { - const t = useTranslations("idp"); - return ( <> {authMethods.includes(AuthenticationMethodType.PASSWORD) && loginSettings?.allowUsernamePassword && ( -
Choose an alternative method to login
+
+ +
)}
{authMethods.includes(AuthenticationMethodType.PASSWORD) && diff --git a/login/apps/login/src/components/choose-authenticator-to-setup.tsx b/login/apps/login/src/components/choose-authenticator-to-setup.tsx index 9075e3286e..4aa4de720a 100644 --- a/login/apps/login/src/components/choose-authenticator-to-setup.tsx +++ b/login/apps/login/src/components/choose-authenticator-to-setup.tsx @@ -3,9 +3,9 @@ import { PasskeysType, } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { useTranslations } from "next-intl"; import { Alert, AlertType } from "./alert"; import { PASSKEYS, PASSWORD } from "./auth-methods"; +import { Translated } from "./translated"; type Props = { authMethods: AuthenticationMethodType[]; @@ -18,16 +18,23 @@ export function ChooseAuthenticatorToSetup({ params, loginSettings, }: Props) { - const t = useTranslations("authenticator"); - if (authMethods.length !== 0) { - return {t("allSetup")}; + return ( + + + + ); } else { return ( <> {loginSettings.passkeysType == PasskeysType.NOT_ALLOWED && !loginSettings.allowUsernamePassword && ( - {t("noMethodsAvailable")} + + + )}
diff --git a/login/apps/login/src/components/choose-second-factor-to-setup.tsx b/login/apps/login/src/components/choose-second-factor-to-setup.tsx index e56379e147..edd0ae2b61 100644 --- a/login/apps/login/src/components/choose-second-factor-to-setup.tsx +++ b/login/apps/login/src/components/choose-second-factor-to-setup.tsx @@ -6,9 +6,9 @@ import { SecondFactorType, } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { EMAIL, SMS, TOTP, U2F } from "./auth-methods"; +import { Translated } from "./translated"; type Props = { userId: string; @@ -37,7 +37,6 @@ export function ChooseSecondFactorToSetup({ emailVerified, force, }: Props) { - const t = useTranslations("mfa"); const router = useRouter(); const params = new URLSearchParams({}); @@ -112,7 +111,7 @@ export function ChooseSecondFactorToSetup({ type="button" data-testid="reset-button" > - {t("set.skip")} + )} diff --git a/login/apps/login/src/components/consent.tsx b/login/apps/login/src/components/consent.tsx index d3d30b3113..e60ed2901b 100644 --- a/login/apps/login/src/components/consent.tsx +++ b/login/apps/login/src/components/consent.tsx @@ -8,6 +8,7 @@ import { useState } from "react"; import { Alert } from "./alert"; import { Button, ButtonVariants } from "./button"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; export function ConsentScreen({ scope, @@ -50,7 +51,7 @@ export function ConsentScreen({
    {scopes?.length === 0 && ( - {t("device.scope.openid")} + )} {scopes?.map((s) => { @@ -73,7 +74,11 @@ export function ConsentScreen({

- {t("device.request.disclaimer", { appName: appName })} +

{error && ( @@ -91,7 +96,7 @@ export function ConsentScreen({ data-testid="deny-button" > {loading && } - {t("device.request.deny")} + @@ -102,7 +107,7 @@ export function ConsentScreen({ className="self-end" variant={ButtonVariants.Primary} > - {t("device.request.submit")} +
diff --git a/login/apps/login/src/components/device-code-form.tsx b/login/apps/login/src/components/device-code-form.tsx index e09adb1147..a1efc07207 100644 --- a/login/apps/login/src/components/device-code-form.tsx +++ b/login/apps/login/src/components/device-code-form.tsx @@ -2,7 +2,6 @@ import { Alert } from "@/components/alert"; import { getDeviceAuthorizationRequest } from "@/lib/server/oidc"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { useForm } from "react-hook-form"; @@ -10,14 +9,13 @@ import { BackButton } from "./back-button"; import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Inputs = { userCode: string; }; export function DeviceCodeForm({ userCode }: { userCode?: string }) { - const t = useTranslations("verify"); - const router = useRouter(); const { register, handleSubmit, formState } = useForm({ @@ -87,8 +85,8 @@ export function DeviceCodeForm({ userCode }: { userCode?: string }) { onClick={handleSubmit(submitCodeAndContinue)} data-testid="submit-button" > - {loading && } - {t("verify.submit")} + {loading && }{" "} +
diff --git a/login/apps/login/src/components/idps/pages/complete-idp.tsx b/login/apps/login/src/components/idps/pages/complete-idp.tsx index e1ed5e7401..2061a28e3e 100644 --- a/login/apps/login/src/components/idps/pages/complete-idp.tsx +++ b/login/apps/login/src/components/idps/pages/complete-idp.tsx @@ -1,8 +1,8 @@ import { RegisterFormIDPIncomplete } from "@/components/register-form-idp-incomplete"; import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; import { AddHumanUserRequest } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { getLocale, getTranslations } from "next-intl/server"; import { DynamicTheme } from "../../dynamic-theme"; +import { Translated } from "../../translated"; export async function completeIDP({ idpUserId, @@ -26,14 +26,15 @@ export async function completeIDP({ idpIntentToken: string; }; }) { - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "idp" }); - return (
-

{t("completeRegister.title")}

-

{t("completeRegister.description")}

+

+ +

+

+ +

-

{t("linkingError.title")}

-

{t("linkingError.description")}

+

+ +

+

+ +

{error && (
{{error}} diff --git a/login/apps/login/src/components/idps/pages/linking-success.tsx b/login/apps/login/src/components/idps/pages/linking-success.tsx index f4faa8e1bf..8d41cd8c32 100644 --- a/login/apps/login/src/components/idps/pages/linking-success.tsx +++ b/login/apps/login/src/components/idps/pages/linking-success.tsx @@ -1,7 +1,7 @@ import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; -import { getLocale, getTranslations } from "next-intl/server"; import { DynamicTheme } from "../../dynamic-theme"; import { IdpSignin } from "../../idp-signin"; +import { Translated } from "../../translated"; export async function linkingSuccess( userId: string, @@ -9,14 +9,15 @@ export async function linkingSuccess( requestId?: string, branding?: BrandingSettings, ) { - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "idp" }); - return (
-

{t("linkingSuccess.title")}

-

{t("linkingSuccess.description")}

+

+ +

+

+ +

-

{t("loginError.title")}

-

{t("loginError.description")}

+

+ +

+

+ +

{error && (
{{error}} diff --git a/login/apps/login/src/components/idps/pages/login-success.tsx b/login/apps/login/src/components/idps/pages/login-success.tsx index 6c884873f1..6beec160a9 100644 --- a/login/apps/login/src/components/idps/pages/login-success.tsx +++ b/login/apps/login/src/components/idps/pages/login-success.tsx @@ -1,7 +1,7 @@ import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; -import { getLocale, getTranslations } from "next-intl/server"; import { DynamicTheme } from "../../dynamic-theme"; import { IdpSignin } from "../../idp-signin"; +import { Translated } from "../../translated"; export async function loginSuccess( userId: string, @@ -9,14 +9,15 @@ export async function loginSuccess( requestId?: string, branding?: BrandingSettings, ) { - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "idp" }); - return (
-

{t("loginSuccess.title")}

-

{t("loginSuccess.description")}

+

+ +

+

+ +

(function SignInWithApple(props, ref) { const { children, name, ...restProps } = props; - const t = useTranslations("idp"); return ( @@ -24,7 +23,13 @@ export const SignInWithApple = forwardRef< {children ? ( children ) : ( - {name ? name : t("signInWithApple")} + + {name ? ( + name + ) : ( + + )} + )} ); diff --git a/login/apps/login/src/components/idps/sign-in-with-azure-ad.tsx b/login/apps/login/src/components/idps/sign-in-with-azure-ad.tsx index a3a4c82272..3cd33708b6 100644 --- a/login/apps/login/src/components/idps/sign-in-with-azure-ad.tsx +++ b/login/apps/login/src/components/idps/sign-in-with-azure-ad.tsx @@ -1,7 +1,7 @@ "use client"; -import { useTranslations } from "next-intl"; import { forwardRef } from "react"; +import { Translated } from "../translated"; import { BaseButton, SignInWithIdentityProviderProps } from "./base-button"; export const SignInWithAzureAd = forwardRef< @@ -9,7 +9,6 @@ export const SignInWithAzureAd = forwardRef< SignInWithIdentityProviderProps >(function SignInWithAzureAd(props, ref) { const { children, name, ...restProps } = props; - const t = useTranslations("idp"); return ( @@ -30,7 +29,13 @@ export const SignInWithAzureAd = forwardRef< {children ? ( children ) : ( - {name ? name : t("signInWithAzureAD")} + + {name ? ( + name + ) : ( + + )} + )} ); diff --git a/login/apps/login/src/components/idps/sign-in-with-github.tsx b/login/apps/login/src/components/idps/sign-in-with-github.tsx index 45108d17f7..8800e66c3d 100644 --- a/login/apps/login/src/components/idps/sign-in-with-github.tsx +++ b/login/apps/login/src/components/idps/sign-in-with-github.tsx @@ -1,7 +1,7 @@ "use client"; -import { useTranslations } from "next-intl"; import { forwardRef } from "react"; +import { Translated } from "../translated"; import { BaseButton, SignInWithIdentityProviderProps } from "./base-button"; function GitHubLogo() { @@ -42,7 +42,6 @@ export const SignInWithGithub = forwardRef< SignInWithIdentityProviderProps >(function SignInWithGithub(props, ref) { const { children, name, ...restProps } = props; - const t = useTranslations("idp"); return ( @@ -52,7 +51,13 @@ export const SignInWithGithub = forwardRef< {children ? ( children ) : ( - {name ? name : t("signInWithGithub")} + + {name ? ( + name + ) : ( + + )} + )} ); diff --git a/login/apps/login/src/components/idps/sign-in-with-gitlab.tsx b/login/apps/login/src/components/idps/sign-in-with-gitlab.tsx index 8a1ed7d349..00f3712a90 100644 --- a/login/apps/login/src/components/idps/sign-in-with-gitlab.tsx +++ b/login/apps/login/src/components/idps/sign-in-with-gitlab.tsx @@ -1,7 +1,7 @@ "use client"; -import { useTranslations } from "next-intl"; import { forwardRef } from "react"; +import { Translated } from "../translated"; import { BaseButton, SignInWithIdentityProviderProps } from "./base-button"; export const SignInWithGitlab = forwardRef< @@ -9,7 +9,6 @@ export const SignInWithGitlab = forwardRef< SignInWithIdentityProviderProps >(function SignInWithGitlab(props, ref) { const { children, name, ...restProps } = props; - const t = useTranslations("idp"); return ( @@ -41,7 +40,13 @@ export const SignInWithGitlab = forwardRef< {children ? ( children ) : ( - {name ? name : t("signInWithGitlab")} + + {name ? ( + name + ) : ( + + )} + )} ); diff --git a/login/apps/login/src/components/idps/sign-in-with-google.tsx b/login/apps/login/src/components/idps/sign-in-with-google.tsx index 6162ef4a96..4759ad69c9 100644 --- a/login/apps/login/src/components/idps/sign-in-with-google.tsx +++ b/login/apps/login/src/components/idps/sign-in-with-google.tsx @@ -1,7 +1,7 @@ "use client"; -import { useTranslations } from "next-intl"; import { forwardRef } from "react"; +import { Translated } from "../translated"; import { BaseButton, SignInWithIdentityProviderProps } from "./base-button"; export const SignInWithGoogle = forwardRef< @@ -9,7 +9,6 @@ export const SignInWithGoogle = forwardRef< SignInWithIdentityProviderProps >(function SignInWithGoogle(props, ref) { const { children, name, ...restProps } = props; - const t = useTranslations("idp"); return ( @@ -54,7 +53,13 @@ export const SignInWithGoogle = forwardRef< {children ? ( children ) : ( - {name ? name : t("signInWithGoogle")} + + {name ? ( + name + ) : ( + + )} + )} ); diff --git a/login/apps/login/src/components/login-otp.tsx b/login/apps/login/src/components/login-otp.tsx index f8500f6909..4ad6cced6a 100644 --- a/login/apps/login/src/components/login-otp.tsx +++ b/login/apps/login/src/components/login-otp.tsx @@ -6,7 +6,6 @@ import { create } from "@zitadel/client"; import { RequestChallengesSchema } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import { useForm } from "react-hook-form"; @@ -15,6 +14,7 @@ import { BackButton } from "./back-button"; import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; // either loginName or sessionId must be provided type Props = { @@ -42,8 +42,6 @@ export function LoginOTP({ code, loginSettings, }: Props) { - const t = useTranslations("otp"); - const [error, setError] = useState(""); const [loading, setLoading] = useState(false); @@ -223,7 +221,7 @@ export function LoginOTP({
- {t("verify.noCodeReceived")} +
@@ -277,8 +275,8 @@ export function LoginOTP({ })} data-testid="submit-button" > - {loading && } - {t("verify.submit")} + {loading && }{" "} +
diff --git a/login/apps/login/src/components/login-passkey.tsx b/login/apps/login/src/components/login-passkey.tsx index b3f0b1212f..0b7ecd5a9e 100644 --- a/login/apps/login/src/components/login-passkey.tsx +++ b/login/apps/login/src/components/login-passkey.tsx @@ -9,13 +9,13 @@ import { UserVerificationRequirement, } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import { Alert } from "./alert"; import { BackButton } from "./back-button"; import { Button, ButtonVariants } from "./button"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; // either loginName or sessionId must be provided type Props = { @@ -35,8 +35,6 @@ export function LoginPasskey({ organization, login = true, }: Props) { - const t = useTranslations("passkey"); - const [error, setError] = useState(""); const [loading, setLoading] = useState(false); @@ -234,7 +232,7 @@ export function LoginPasskey({ }} data-testid="password-button" > - {t("verify.usePassword")} + ) : ( @@ -273,8 +271,8 @@ export function LoginPasskey({ }} data-testid="submit-button" > - {loading && } - {t("verify.submit")} + {loading && }{" "} +
diff --git a/login/apps/login/src/components/password-form.tsx b/login/apps/login/src/components/password-form.tsx index 17461644d8..c65a4049f8 100644 --- a/login/apps/login/src/components/password-form.tsx +++ b/login/apps/login/src/components/password-form.tsx @@ -4,7 +4,6 @@ import { resetPassword, sendPassword } from "@/lib/server/password"; import { create } from "@zitadel/client"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { useForm } from "react-hook-form"; @@ -13,6 +12,7 @@ import { BackButton } from "./back-button"; import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Inputs = { password: string; @@ -35,8 +35,6 @@ export function PasswordForm({ promptPasswordless, isAlternative, }: Props) { - const t = useTranslations("password"); - const { register, handleSubmit, formState } = useForm({ mode: "onBlur", }); @@ -136,7 +134,7 @@ export function PasswordForm({ disabled={loading} data-testid="reset-button" > - {t("verify.resetPassword")} + )} @@ -173,8 +171,8 @@ export function PasswordForm({ onClick={handleSubmit(submitPassword)} data-testid="submit-button" > - {loading && } - {t("verify.submit")} + {loading && }{" "} +
diff --git a/login/apps/login/src/components/privacy-policy-checkboxes.tsx b/login/apps/login/src/components/privacy-policy-checkboxes.tsx index 5ac0340bcb..4ab0e33222 100644 --- a/login/apps/login/src/components/privacy-policy-checkboxes.tsx +++ b/login/apps/login/src/components/privacy-policy-checkboxes.tsx @@ -1,9 +1,9 @@ "use client"; import { LegalAndSupportSettings } from "@zitadel/proto/zitadel/settings/v2/legal_settings_pb"; -import { useTranslations } from "next-intl"; import Link from "next/link"; import { useState } from "react"; import { Checkbox } from "./checkbox"; +import { Translated } from "./translated"; type Props = { legal: LegalAndSupportSettings; @@ -16,7 +16,6 @@ type AcceptanceState = { }; export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) { - const t = useTranslations("register"); const [acceptanceState, setAcceptanceState] = useState({ tosAccepted: false, privacyPolicyAccepted: false, @@ -25,7 +24,7 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) { return ( <>

- {t("agreeTo")} + {legal?.helpLink && ( @@ -66,7 +65,7 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {

- {t("termsOfService")} +

@@ -95,7 +94,7 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) { className="underline" target="_blank" > - {t("privacyPolicy")} +

diff --git a/login/apps/login/src/components/register-form-idp-incomplete.tsx b/login/apps/login/src/components/register-form-idp-incomplete.tsx index 6194b34052..b8a7765c9c 100644 --- a/login/apps/login/src/components/register-form-idp-incomplete.tsx +++ b/login/apps/login/src/components/register-form-idp-incomplete.tsx @@ -1,7 +1,6 @@ "use client"; import { registerUserAndLinkToIDP } from "@/lib/server/register"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; @@ -10,6 +9,7 @@ import { BackButton } from "./back-button"; import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Inputs = | { @@ -45,8 +45,6 @@ export function RegisterFormIDPIncomplete({ idpId, idpUserName, }: Props) { - const t = useTranslations("register"); - const { register, handleSubmit, formState } = useForm({ mode: "onBlur", defaultValues: { @@ -149,8 +147,8 @@ export function RegisterFormIDPIncomplete({ onClick={handleSubmit(submitAndRegister)} data-testid="submit-button" > - {loading && } - {t("submit")} + {loading && }{" "} +
diff --git a/login/apps/login/src/components/register-form.tsx b/login/apps/login/src/components/register-form.tsx index c581131c8c..50acdc0004 100644 --- a/login/apps/login/src/components/register-form.tsx +++ b/login/apps/login/src/components/register-form.tsx @@ -6,7 +6,6 @@ import { LoginSettings, PasskeysType, } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; @@ -21,6 +20,7 @@ import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { PrivacyPolicyCheckboxes } from "./privacy-policy-checkboxes"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Inputs = | { @@ -51,8 +51,6 @@ export function RegisterForm({ loginSettings, idpCount = 0, }: Props) { - const t = useTranslations("register"); - const { register, handleSubmit, formState } = useForm({ mode: "onBlur", defaultValues: { @@ -173,7 +171,7 @@ export function RegisterForm({ loginSettings.passkeysType == PasskeysType.ALLOWED && ( <>

- {t("selectMethod")} +

@@ -186,10 +184,15 @@ export function RegisterForm({ )} {!loginSettings?.allowUsernamePassword && - loginSettings?.passkeysType != PasskeysType.ALLOWED && + loginSettings?.passkeysType !== PasskeysType.ALLOWED && (!loginSettings?.allowExternalIdp || !idpCount) && (
- {t("noMethodAvailableWarning")} + + +
)} @@ -217,7 +220,7 @@ export function RegisterForm({ data-testid="submit-button" > {loading && } - {t("submit")} +
diff --git a/login/apps/login/src/components/register-passkey.tsx b/login/apps/login/src/components/register-passkey.tsx index 8687312bbc..e21e1acdbb 100644 --- a/login/apps/login/src/components/register-passkey.tsx +++ b/login/apps/login/src/components/register-passkey.tsx @@ -5,7 +5,6 @@ import { registerPasskeyLink, verifyPasskeyRegistration, } from "@/lib/server/passkeys"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { useForm } from "react-hook-form"; @@ -13,6 +12,7 @@ import { Alert } from "./alert"; import { BackButton } from "./back-button"; import { Button, ButtonVariants } from "./button"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Inputs = {}; @@ -29,8 +29,6 @@ export function RegisterPasskey({ organization, requestId, }: Props) { - const t = useTranslations("passkey"); - const { handleSubmit, formState } = useForm({ mode: "onBlur", }); @@ -198,7 +196,7 @@ export function RegisterPasskey({ continueAndLogin(); }} > - {t("set.skip")} + ) : ( @@ -213,8 +211,8 @@ export function RegisterPasskey({ onClick={handleSubmit(submitRegisterAndContinue)} data-testid="submit-button" > - {loading && } - {t("set.submit")} + {loading && }{" "} +
diff --git a/login/apps/login/src/components/register-u2f.tsx b/login/apps/login/src/components/register-u2f.tsx index 753eae017d..e72bf1fc69 100644 --- a/login/apps/login/src/components/register-u2f.tsx +++ b/login/apps/login/src/components/register-u2f.tsx @@ -5,13 +5,13 @@ import { getNextUrl } from "@/lib/client"; import { addU2F, verifyU2F } from "@/lib/server/u2f"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { RegisterU2FResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { Alert } from "./alert"; import { BackButton } from "./back-button"; import { Button, ButtonVariants } from "./button"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Props = { loginName?: string; @@ -30,8 +30,6 @@ export function RegisterU2f({ checkAfter, loginSettings, }: Props) { - const t = useTranslations("u2f"); - const [error, setError] = useState(""); const [loading, setLoading] = useState(false); @@ -218,8 +216,8 @@ export function RegisterU2f({ onClick={submitRegisterAndContinue} data-testid="submit-button" > - {loading && } - {t("set.submit")} + {loading && }{" "} +
diff --git a/login/apps/login/src/components/session-clear-item.tsx b/login/apps/login/src/components/session-clear-item.tsx index 8bff33fee9..81930b11b3 100644 --- a/login/apps/login/src/components/session-clear-item.tsx +++ b/login/apps/login/src/components/session-clear-item.tsx @@ -4,11 +4,12 @@ import { clearSession } from "@/lib/server/session"; import { timestampDate } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import moment from "moment"; -import { useLocale, useTranslations } from "next-intl"; +import { useLocale } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { Avatar } from "./avatar"; import { isSessionValid } from "./session-item"; +import { Translated } from "./translated"; export function SessionClearItem({ session, @@ -17,8 +18,6 @@ export function SessionClearItem({ session: Session; reload: () => void; }) { - const t = useTranslations("logout"); - const currentLocale = useLocale(); moment.locale(currentLocale === "zh" ? "zh-cn" : currentLocale); @@ -70,10 +69,13 @@ export function SessionClearItem({ {valid ? ( - {verifiedAt && - t("verfiedAt", { - time: moment(timestampDate(verifiedAt)).fromNow(), - })} + {verifiedAt && ( + + )} ) : ( verifiedAt && ( @@ -89,7 +91,7 @@ export function SessionClearItem({
- {t("clear")} +
{valid ? ( diff --git a/login/apps/login/src/components/sessions-clear-list.tsx b/login/apps/login/src/components/sessions-clear-list.tsx index fe7a67f746..5989948725 100644 --- a/login/apps/login/src/components/sessions-clear-list.tsx +++ b/login/apps/login/src/components/sessions-clear-list.tsx @@ -3,11 +3,11 @@ import { clearSession } from "@/lib/server/session"; import { timestampDate } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; -import { useTranslations } from "next-intl"; import { redirect, useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { Alert, AlertType } from "./alert"; import { SessionClearItem } from "./session-clear-item"; +import { Translated } from "./translated"; type Props = { sessions: Session[]; @@ -22,7 +22,6 @@ export function SessionsClearList({ postLogoutRedirectUri, organization, }: Props) { - const t = useTranslations("logout"); const [list, setList] = useState(sessions); const router = useRouter(); @@ -97,10 +96,14 @@ export function SessionsClearList({ ); })} {list.length === 0 && ( - {t("noResults")} + + + )}
) : ( - {t("noResults")} + + + ); } diff --git a/login/apps/login/src/components/sessions-list.tsx b/login/apps/login/src/components/sessions-list.tsx index 50f621a62d..a3a1f8ed94 100644 --- a/login/apps/login/src/components/sessions-list.tsx +++ b/login/apps/login/src/components/sessions-list.tsx @@ -2,10 +2,10 @@ import { timestampDate } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; -import { useTranslations } from "next-intl"; import { useState } from "react"; import { Alert } from "./alert"; import { SessionItem } from "./session-item"; +import { Translated } from "./translated"; type Props = { sessions: Session[]; @@ -13,7 +13,6 @@ type Props = { }; export function SessionsList({ sessions, requestId }: Props) { - const t = useTranslations("accounts"); const [list, setList] = useState(sessions); return sessions ? (
@@ -44,6 +43,8 @@ export function SessionsList({ sessions, requestId }: Props) { })}
) : ( - {t("noResults")} + + + ); } diff --git a/login/apps/login/src/components/set-password-form.tsx b/login/apps/login/src/components/set-password-form.tsx index 08f5c7c4ef..2c3db8dbf2 100644 --- a/login/apps/login/src/components/set-password-form.tsx +++ b/login/apps/login/src/components/set-password-form.tsx @@ -14,7 +14,6 @@ import { import { create } from "@zitadel/client"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; @@ -24,6 +23,7 @@ import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { PasswordComplexity } from "./password-complexity"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Inputs = | { @@ -52,8 +52,6 @@ export function SetPasswordForm({ code, codeRequired, }: Props) { - const t = useTranslations("password"); - const { register, handleSubmit, watch, formState } = useForm({ mode: "onBlur", defaultValues: { @@ -195,7 +193,7 @@ export function SetPasswordForm({
- {t("set.noCodeReceived")} +
@@ -279,8 +277,8 @@ export function SetPasswordForm({ onClick={handleSubmit(submitPassword)} data-testid="submit-button" > - {loading && } - {t("set.submit")} + {loading && }{" "} +
diff --git a/login/apps/login/src/components/set-register-password-form.tsx b/login/apps/login/src/components/set-register-password-form.tsx index 3e48c649c1..7660e60753 100644 --- a/login/apps/login/src/components/set-register-password-form.tsx +++ b/login/apps/login/src/components/set-register-password-form.tsx @@ -8,7 +8,6 @@ import { } from "@/helpers/validators"; import { registerUser } from "@/lib/server/register"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; @@ -18,6 +17,7 @@ import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { PasswordComplexity } from "./password-complexity"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Inputs = | { @@ -43,8 +43,6 @@ export function SetRegisterPasswordForm({ organization, requestId, }: Props) { - const t = useTranslations("register"); - const { register, handleSubmit, watch, formState } = useForm({ mode: "onBlur", defaultValues: { @@ -163,8 +161,8 @@ export function SetRegisterPasswordForm({ onClick={handleSubmit(submitRegister)} data-testid="submit-button" > - {loading && } - {t("password.submit")} + {loading && }{" "} +
diff --git a/login/apps/login/src/components/totp-register.tsx b/login/apps/login/src/components/totp-register.tsx index b5c81d8645..ea40fffbf0 100644 --- a/login/apps/login/src/components/totp-register.tsx +++ b/login/apps/login/src/components/totp-register.tsx @@ -3,7 +3,6 @@ import { getNextUrl } from "@/lib/client"; import { verifyTOTP } from "@/lib/server/verify"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; -import { useTranslations } from "next-intl"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { QRCodeSVG } from "qrcode.react"; @@ -14,6 +13,7 @@ import { Button, ButtonVariants } from "./button"; import { CopyToClipboard } from "./copy-to-clipboard"; import { TextInput } from "./input"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Inputs = { code: string; @@ -39,8 +39,6 @@ export function TotpRegister({ checkAfter, loginSettings, }: Props) { - const t = useTranslations("otp"); - const [error, setError] = useState(""); const [loading, setLoading] = useState(false); const router = useRouter(); @@ -148,7 +146,7 @@ export function TotpRegister({ data-testid="submit-button" > {loading && } - {t("set.submit")} +
diff --git a/login/apps/login/src/components/translated.tsx b/login/apps/login/src/components/translated.tsx new file mode 100644 index 0000000000..807ea18e8f --- /dev/null +++ b/login/apps/login/src/components/translated.tsx @@ -0,0 +1,23 @@ +import { useTranslations } from "next-intl"; + +export function Translated({ + i18nKey, + children, + namespace, + data, + ...props +}: { + i18nKey: string; + children?: React.ReactNode; + namespace?: string; + data?: any; +} & React.HTMLAttributes) { + const t = useTranslations(namespace); + const helperKey = `${namespace ? `${namespace}.` : ""}${i18nKey}`; + + return ( + + {t(i18nKey, data)} + + ); +} diff --git a/login/apps/login/src/components/username-form.tsx b/login/apps/login/src/components/username-form.tsx index 6801f6b274..b16092bd9e 100644 --- a/login/apps/login/src/components/username-form.tsx +++ b/login/apps/login/src/components/username-form.tsx @@ -2,7 +2,6 @@ import { sendLoginname } from "@/lib/server/loginname"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { ReactNode, useEffect, useState } from "react"; import { useForm } from "react-hook-form"; @@ -11,6 +10,7 @@ import { BackButton } from "./back-button"; import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Inputs = { loginName: string; @@ -37,7 +37,6 @@ export function UsernameForm({ allowRegister, children, }: Props) { - const t = useTranslations("loginname"); const { register, handleSubmit, formState } = useForm({ mode: "onBlur", defaultValues: { @@ -127,7 +126,7 @@ export function UsernameForm({ disabled={loading} data-testid="register-button" > - {t("register")} + )}
@@ -152,7 +151,7 @@ export function UsernameForm({ onClick={handleSubmit((e) => submitLoginName(e, organization))} > {loading && } - continue +
diff --git a/login/apps/login/src/components/verify-form.tsx b/login/apps/login/src/components/verify-form.tsx index 0933f598dd..dac4c91314 100644 --- a/login/apps/login/src/components/verify-form.tsx +++ b/login/apps/login/src/components/verify-form.tsx @@ -2,7 +2,6 @@ import { Alert, AlertType } from "@/components/alert"; import { resendVerification, sendVerification } from "@/lib/server/verify"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useState } from "react"; import { useForm } from "react-hook-form"; @@ -10,6 +9,7 @@ import { BackButton } from "./back-button"; import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Inputs = { code: string; @@ -32,8 +32,6 @@ export function VerifyForm({ code, isInvite, }: Props) { - const t = useTranslations("verify"); - const router = useRouter(); const { register, handleSubmit, formState } = useForm({ @@ -117,7 +115,7 @@ export function VerifyForm({
- {t("verify.noCodeReceived")} +
@@ -161,7 +159,7 @@ export function VerifyForm({ data-testid="submit-button" > {loading && } - {t("verify.submit")} +
diff --git a/login/apps/login/src/i18n/request.ts b/login/apps/login/src/i18n/request.ts index 59c9da42cc..271d370f7c 100644 --- a/login/apps/login/src/i18n/request.ts +++ b/login/apps/login/src/i18n/request.ts @@ -1,4 +1,7 @@ import { LANGS, LANGUAGE_COOKIE_NAME, LANGUAGE_HEADER_NAME } from "@/lib/i18n"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; +import { getHostedLoginTranslation } from "@/lib/zitadel"; +import { JsonObject } from "@zitadel/client"; import deepmerge from "deepmerge"; import { getRequestConfig } from "next-intl/server"; import { cookies, headers } from "next/headers"; @@ -9,6 +12,26 @@ export default getRequestConfig(async () => { let locale: string = fallback; + const _headers = await headers(); + const { serviceUrl } = getServiceUrlFromHeaders(_headers); + + const i18nOrganization = _headers.get("x-zitadel-i18n-organization") || ""; // You may need to set this header in middleware + console.log("i18nOrganization:", i18nOrganization); + let translations: JsonObject | {} = {}; + try { + const i18nJSON = await getHostedLoginTranslation({ + serviceUrl, + locale, + organization: i18nOrganization, + }); + + if (i18nJSON) { + translations = i18nJSON; + } + } catch (error) { + console.warn("Error fetching custom translations:", error); + } + const languageHeader = await (await headers()).get(LANGUAGE_HEADER_NAME); if (languageHeader) { const headerLocale = languageHeader.split(",")[0].split("-")[0]; // Extract the language code @@ -24,12 +47,13 @@ export default getRequestConfig(async () => { } } - const userMessages = (await import(`../../locales/${locale}.json`)).default; + const customMessages = translations; + const localeMessages = (await import(`../../locales/${locale}.json`)).default; const fallbackMessages = (await import(`../../locales/${fallback}.json`)) .default; return { locale, - messages: deepmerge(fallbackMessages, userMessages), + messages: deepmerge.all([fallbackMessages, localeMessages, customMessages]), }; }); diff --git a/login/apps/login/src/lib/zitadel.ts b/login/apps/login/src/lib/zitadel.ts index 8cc4efe0bc..e148e7c0be 100644 --- a/login/apps/login/src/lib/zitadel.ts +++ b/login/apps/login/src/lib/zitadel.ts @@ -59,6 +59,42 @@ async function cacheWrapper(callback: Promise) { return callback; } +export async function getHostedLoginTranslation({ + serviceUrl, + organization, + locale, +}: { + serviceUrl: string; + organization?: string; + locale?: string; +}) { + const settingsService: Client = + await createServiceForHost(SettingsService, serviceUrl); + + const callback = settingsService + .getHostedLoginTranslation( + { + level: organization + ? { + case: "organizationId", + value: organization, + } + : { + case: "instance", + value: true, + }, + locale: locale, + }, + {}, + ) + .then((resp) => { + console.log(resp); + return resp.translations ? resp.translations : undefined; + }); + + return useCache ? cacheWrapper(callback) : callback; +} + export async function getBrandingSettings({ serviceUrl, organization, diff --git a/login/apps/login/src/middleware.ts b/login/apps/login/src/middleware.ts index 4d66d0ab39..fa287e2bdd 100644 --- a/login/apps/login/src/middleware.ts +++ b/login/apps/login/src/middleware.ts @@ -10,21 +10,51 @@ export const config = { "/oidc/:path*", "/idps/callback/:path*", "/saml/:path*", + "/:path*", ], }; export async function middleware(request: NextRequest) { + // Add the original URL as a header to all requests + const requestHeaders = new Headers(request.headers); + + // Extract "organization" search param from the URL and set it as a header if available + const organization = request.nextUrl.searchParams.get("organization"); + if (organization) { + requestHeaders.set("x-zitadel-i18n-organization", organization); + } + + // Only run the rest of the logic for the original matcher paths + const matchedPaths = [ + "/.well-known/", + "/oauth/", + "/oidc/", + "/idps/callback/", + "/saml/", + ]; + + const isMatched = matchedPaths.some((prefix) => + request.nextUrl.pathname.startsWith(prefix), + ); + + if (!isMatched) { + // For all other routes, just add the header and continue + return NextResponse.next({ + request: { headers: requestHeaders }, + }); + } + // escape proxy if the environment is setup for multitenancy if (!process.env.ZITADEL_API_URL || !process.env.ZITADEL_SERVICE_USER_TOKEN) { - return NextResponse.next(); + return NextResponse.next({ + request: { headers: requestHeaders }, + }); } const _headers = await headers(); - const { serviceUrl } = getServiceUrlFromHeaders(_headers); // Call the /security route handler - // TODO check this on cloud run deployment const securityResponse = await fetch(`${request.nextUrl.origin}/security`); if (!securityResponse.ok) { @@ -32,7 +62,9 @@ export async function middleware(request: NextRequest) { "Failed to fetch security settings:", securityResponse.statusText, ); - return NextResponse.next(); // Fallback if the request fails + return NextResponse.next({ + request: { headers: requestHeaders }, + }); } const { settings: securitySettings } = await securityResponse.json(); @@ -41,13 +73,8 @@ export async function middleware(request: NextRequest) { .replace("https://", "") .replace("http://", ""); - const requestHeaders = new Headers(request.headers); - - // this is a workaround for the next.js server not forwarding the host header - // requestHeaders.set("x-zitadel-forwarded", `host="${request.nextUrl.host}"`); + // Add additional headers as before requestHeaders.set("x-zitadel-public-host", `${request.nextUrl.host}`); - - // this is a workaround for the next.js server not forwarding the host header requestHeaders.set("x-zitadel-instance-host", instanceHost); const responseHeaders = new Headers(); @@ -55,7 +82,6 @@ export async function middleware(request: NextRequest) { responseHeaders.set("Access-Control-Allow-Headers", "*"); if (securitySettings?.embeddedIframe?.enabled) { - securitySettings.embeddedIframe.allowedOrigins; responseHeaders.set( "Content-Security-Policy", `${DEFAULT_CSP} frame-ancestors ${securitySettings.embeddedIframe.allowedOrigins.join(" ")};`, diff --git a/login/docker-bake.hcl b/login/docker-bake.hcl index 6a9714fd27..830585bf07 100644 --- a/login/docker-bake.hcl +++ b/login/docker-bake.hcl @@ -28,7 +28,7 @@ target "typescript-proto-client-out" { ] } -# proto-files is only used to build login-core-mock against which the integration tests run. +# proto-files is only used to build core-mock against which the integration tests run. # To build the proto-client, we use buf to generate and download the client code directly. # It is not login-prefixed, so it is easily extendable. # To extend this bake-file.hcl, set the context of all login-prefixed targets to a different directory. @@ -75,10 +75,11 @@ target "login-client" { } variable "LOGIN_CORE_MOCK_TAG" { - default = "login-core-mock:local" + default = "core-mock:local" } -target "login-core-mock" { +# the core-mock context must not be overwritten, so we don't prefix it with login-. +target "core-mock" { context = "${LOGIN_DIR}apps/login-test-integration/core-mock" contexts = { protos = "target:proto-files" diff --git a/login/package.json b/login/package.json index 0d2b1530f8..abe9f22450 100644 --- a/login/package.json +++ b/login/package.json @@ -13,9 +13,9 @@ "start": "pnpm exec turbo run start", "start:built": "pnpm exec turbo run start:built", "test:unit": "pnpm exec turbo run test:unit -- --passWithNoTests", - "test:integration:setup": "dotenv -e ./apps/login-test-integration/.env pnpm exec turbo run test:integration:setup", + "test:integration:setup": "NODE_ENV=test dotenv -e ./apps/login-test-integration/.env pnpm exec turbo run test:integration:setup", "test:integration": "cd apps/login-test-integration && dotenv -e ./.env pnpm test:integration", - "test:acceptance:setup": "pnpm exec turbo run test:acceptance:setup", + "test:acceptance:setup": "pnpm exec turbo run test:acceptance:env && NODE_ENV=test pnpm exec turbo run test:acceptance:setup", "test:acceptance": "cd apps/login-test-acceptance && dotenv -e ./env/.env pnpm test:acceptance", "test:watch": "pnpm exec turbo run test:watch", "dev": "pnpm exec turbo run dev --no-cache --continue", diff --git a/login/pnpm-lock.yaml b/login/pnpm-lock.yaml index 8f0dedd261..8c6424ccf6 100644 --- a/login/pnpm-lock.yaml +++ b/login/pnpm-lock.yaml @@ -70,7 +70,7 @@ importers: version: 0.5.7(tailwindcss@3.4.14) '@vercel/analytics': specifier: ^1.2.2 - version: 1.3.1(next@15.4.0-canary.3(@babel/core@7.26.10)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react@19.1.0) + version: 1.3.1(next@15.4.0-canary.86(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react@19.1.0) '@zitadel/client': specifier: workspace:* version: link:../../packages/zitadel-client @@ -86,9 +86,6 @@ importers: deepmerge: specifier: ^4.3.1 version: 4.3.1 - jose: - specifier: ^5.3.0 - version: 5.8.0 lucide-react: specifier: 0.469.0 version: 0.469.0(react@19.1.0) @@ -96,14 +93,14 @@ importers: specifier: ^2.29.4 version: 2.30.1 next: - specifier: 15.4.0-canary.3 - version: 15.4.0-canary.3(@babel/core@7.26.10)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0) + specifier: 15.4.0-canary.86 + version: 15.4.0-canary.86(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0) next-intl: specifier: ^3.25.1 - version: 3.25.1(next@15.4.0-canary.3(@babel/core@7.26.10)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react@19.1.0) + version: 3.26.5(next@15.4.0-canary.86(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react@19.1.0) next-themes: specifier: ^0.2.1 - version: 0.2.1(next@15.4.0-canary.3(@babel/core@7.26.10)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 0.2.1(next@15.4.0-canary.86(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) nice-grpc: specifier: 2.0.1 version: 2.0.1 @@ -119,9 +116,6 @@ importers: react-hook-form: specifier: 7.39.5 version: 7.39.5(react@19.1.0) - swr: - specifier: ^2.2.0 - version: 2.2.5(react@19.1.0) tinycolor2: specifier: 1.4.2 version: 1.4.2 @@ -825,9 +819,6 @@ packages: '@formatjs/icu-skeleton-parser@1.8.8': resolution: {integrity: sha512-vHwK3piXwamFcx5YQdCdJxUQ1WdTl6ANclt5xba5zLGDv5Bsur7qz8AD7BevaKxITwpgDeU0u8My3AIibW9ywA==} - '@formatjs/intl-localematcher@0.5.4': - resolution: {integrity: sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==} - '@formatjs/intl-localematcher@0.5.8': resolution: {integrity: sha512-I+WDNWWJFZie+jkfkiK5Mp4hEDyRSEvmyfYadflOno/mmKJKcB17fEpEH0oJu/OWhhCJ8kJBDz2YMd/6cDl7Mg==} @@ -1016,56 +1007,56 @@ packages: resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true - '@next/env@15.4.0-canary.3': - resolution: {integrity: sha512-lu4pB2e3Z/d+B0rxEm9YuQMb57Hd96iJUBZgVlcRNemlIryr0GByu17kvN6nBk3JjbWL8h+MW90stpGzGdhbqg==} + '@next/env@15.4.0-canary.86': + resolution: {integrity: sha512-WPrEvwqHnjeLx05ncJvqizbBJJFlQGRbxzOnL/pZWKzo19auM9x5Se87P27+E/D/d6jJS801l+thF85lfobAZQ==} '@next/eslint-plugin-next@14.2.18': resolution: {integrity: sha512-KyYTbZ3GQwWOjX3Vi1YcQbekyGP0gdammb7pbmmi25HBUCINzDReyrzCMOJIeZisK1Q3U6DT5Rlc4nm2/pQeXA==} - '@next/swc-darwin-arm64@15.4.0-canary.3': - resolution: {integrity: sha512-w9u8IpwLb/JS7HzHLt24smP4FxIYMgciOtYNUCognO1xh1XZfqqjDIrRAXDuuYDPKrc1i2EvI24R5eDTz7EYMQ==} + '@next/swc-darwin-arm64@15.4.0-canary.86': + resolution: {integrity: sha512-1ofBmzjPkmoMdM+dXvybZ/Roq8HRo0sFzcwXk7/FJNOufuwyK+QKdSpLE7pHlPR7ZREqfEMj61ONO+gAK+zOJw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.4.0-canary.3': - resolution: {integrity: sha512-5pL1hBRw8h1XeArzWYjCDERtRFIfrMAz1Nq9m1np8FrTuHclE7xitKKfOJqqmBbO9dWtnZIfA8lZl9bdlNEUZg==} + '@next/swc-darwin-x64@15.4.0-canary.86': + resolution: {integrity: sha512-WCKSrllvwzYi4TgrSdgxKSOF2nhieeaWWOeGucn0OXy50uOAamr0HwP5OaIBCx3oRar4w66gvs4IrdTdMedeJA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.4.0-canary.3': - resolution: {integrity: sha512-vx6cU4jKoecF2QZw3CQqJrzb+D0WhNzHHoWUN8O+YKPnX0oG4wEtAQWSWisxKjNrU1U4TiraOql0nOQBUOKwaQ==} + '@next/swc-linux-arm64-gnu@15.4.0-canary.86': + resolution: {integrity: sha512-8qn7DJVNFjhEIDo2ts0YCsO7g+vJjPWh8Ur8lBK3XspeX0BPsF4s+YmgidrpzRXeIfoo2uYLkkXcy/57CVDblw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.4.0-canary.3': - resolution: {integrity: sha512-7ig1sQHRRgTrj4QHt5l8OT1z2SJnEAHbnEY9SDP2HilwQIfgOAOxveFDBR+f/8AMdAKhCTSeMyrZsivpC0xTUA==} + '@next/swc-linux-arm64-musl@15.4.0-canary.86': + resolution: {integrity: sha512-8MTn6N4Ja25neMLu2Bra1lqW9AWPqsYg0BVs5M/cxL0QkcN3mak/8LLX1vbzz7GigMGSA+NLwg+ol8lglfgIGA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@15.4.0-canary.3': - resolution: {integrity: sha512-fML6pzNX9i3DlrCOdE6A1TbVL0aIQkIDDCjrbn/f37hOn88god1OrVd/d4J4w1YqLKQWpmJPnUn6Bkn8qXqbRw==} + '@next/swc-linux-x64-gnu@15.4.0-canary.86': + resolution: {integrity: sha512-hIhzDwWDQHnH0M0Pzaqs1c5fa4+LHiLLEBuPJQvhBxQfH+Eh86DWiWHDCaoNiURvdRPg6uCuF2MjwptrMplEkg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.4.0-canary.3': - resolution: {integrity: sha512-87/JPkbr3fgvASdWW2qBVuaXwcjSxgy+CTllj2DgYB7e7BEzT7QJEdj0HJZljBjVbN5oT1FOKwhaVRgRWuwYLQ==} + '@next/swc-linux-x64-musl@15.4.0-canary.86': + resolution: {integrity: sha512-FG6SBuSeRWYMNu6tsfaZ4iDzv3BLxlpRncO2xvKKQPeUdDSQ0cehuHYnx8fRte8IOAJ3rlbRd6NXvrDarqu92Q==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@15.4.0-canary.3': - resolution: {integrity: sha512-cTZh72h3ZX8z0lhdVs5m38uyy83mW5r0jz6hKagysPT06uTdOAypK6CRqG5CJSN7RM0n7CkfcO6ExjDqhkDhRA==} + '@next/swc-win32-arm64-msvc@15.4.0-canary.86': + resolution: {integrity: sha512-3HvZo4VuyINrNYplRhvC8ILdKwi/vFDHOcTN/I4ru039TFpu2eO6VtXsLBdOdJjGslSSSBYkX+6yRrghihAZDA==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.4.0-canary.3': - resolution: {integrity: sha512-8oZKOKRGad4EVZ94L5Sz2EP59khHIeKGKg+/z8r5mCbBtupLPTXmWjrXoi1R55hHRXJjbW2D5NwcPfJn/ltZ3Q==} + '@next/swc-win32-x64-msvc@15.4.0-canary.86': + resolution: {integrity: sha512-UO9JzGGj7GhtSJFdI0Bl0dkIIBfgbhXLsgNVmq9Z/CsUsQB6J9RS/BMhsxfVwhO+RETk13nFpNutMAhAwcuD8w==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1838,10 +1829,6 @@ packages: peerDependencies: esbuild: '>=0.18' - busboy@1.6.0: - resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} - engines: {node: '>=10.16.0'} - cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -1870,9 +1857,6 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - caniuse-lite@1.0.30001680: - resolution: {integrity: sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==} - caniuse-lite@1.0.30001715: resolution: {integrity: sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==} @@ -2164,10 +2148,6 @@ packages: engines: {node: '>=0.10'} hasBin: true - detect-libc@2.0.3: - resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} - engines: {node: '>=8'} - detect-libc@2.0.4: resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} @@ -3402,11 +3382,11 @@ packages: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} - next-intl@3.25.1: - resolution: {integrity: sha512-Z2dJWn5f/b1sb8EmuJcuDhbQTIp4RG1KBFAILgRt/y27W0ifU7Ll/os3liphUY4InyRH89uShTAk7ItAlpr0uA==} + next-intl@3.26.5: + resolution: {integrity: sha512-EQlCIfY0jOhRldiFxwSXG+ImwkQtDEfQeSOEQp6ieAGSLWGlgjdb/Ck/O7wMfC430ZHGeUKVKax8KGusTPKCgg==} peerDependencies: next: ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0 next-themes@0.2.1: resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==} @@ -3415,13 +3395,13 @@ packages: react: '*' react-dom: '*' - next@15.4.0-canary.3: - resolution: {integrity: sha512-OkwxAFNQeuE0vNL7tTwU+jm3nf3x3D5DHSmjRlFktsedGtxZiILZTq6UNExNaFBjttR+2Y6oGqRsFWXC4ob1Wg==} + next@15.4.0-canary.86: + resolution: {integrity: sha512-lGeO0sOvPZ7oFIklqRA863YzRL1bW+kT/OqU3N6RBquHldiucZwnZKQceZdn6WcHEFmWIHzZV+SMG1JEK7hZLg==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 - '@playwright/test': ^1.41.2 + '@playwright/test': ^1.51.1 babel-plugin-react-compiler: '*' react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 @@ -4180,10 +4160,6 @@ packages: stream-combiner@0.0.4: resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} - streamsearch@1.1.0: - resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} - engines: {node: '>=10.0.0'} - string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -4286,11 +4262,6 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - swr@2.2.5: - resolution: {integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==} - peerDependencies: - react: ^16.11.0 || ^17.0.0 || ^18.0.0 - symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -4566,15 +4537,10 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - use-intl@3.25.1: - resolution: {integrity: sha512-Xeyl0+BjlBf6fJr2h5W/CESZ2IQAH7jzXYK4c/ao+qR26jNPW3FXBLjg7eLRxdeI6QaLcYGLtH3WYhC9I0+6Yg==} + use-intl@3.26.5: + resolution: {integrity: sha512-OdsJnC/znPvHCHLQH/duvQNXnP1w0hPfS+tkSi3mAbfjYBGh4JnyfdwkQBfIVf7t8gs9eSX/CntxUMvtKdG2MQ==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 - - use-sync-external-store@1.2.2: - resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0 util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -5360,10 +5326,6 @@ snapshots: '@formatjs/ecma402-abstract': 2.2.4 tslib: 2.8.1 - '@formatjs/intl-localematcher@0.5.4': - dependencies: - tslib: 2.8.1 - '@formatjs/intl-localematcher@0.5.8': dependencies: tslib: 2.8.1 @@ -5548,34 +5510,34 @@ snapshots: - encoding - supports-color - '@next/env@15.4.0-canary.3': {} + '@next/env@15.4.0-canary.86': {} '@next/eslint-plugin-next@14.2.18': dependencies: glob: 10.3.10 - '@next/swc-darwin-arm64@15.4.0-canary.3': + '@next/swc-darwin-arm64@15.4.0-canary.86': optional: true - '@next/swc-darwin-x64@15.4.0-canary.3': + '@next/swc-darwin-x64@15.4.0-canary.86': optional: true - '@next/swc-linux-arm64-gnu@15.4.0-canary.3': + '@next/swc-linux-arm64-gnu@15.4.0-canary.86': optional: true - '@next/swc-linux-arm64-musl@15.4.0-canary.3': + '@next/swc-linux-arm64-musl@15.4.0-canary.86': optional: true - '@next/swc-linux-x64-gnu@15.4.0-canary.3': + '@next/swc-linux-x64-gnu@15.4.0-canary.86': optional: true - '@next/swc-linux-x64-musl@15.4.0-canary.3': + '@next/swc-linux-x64-musl@15.4.0-canary.86': optional: true - '@next/swc-win32-arm64-msvc@15.4.0-canary.3': + '@next/swc-win32-arm64-msvc@15.4.0-canary.86': optional: true - '@next/swc-win32-x64-msvc@15.4.0-canary.3': + '@next/swc-win32-x64-msvc@15.4.0-canary.86': optional: true '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': @@ -6035,11 +5997,11 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vercel/analytics@1.3.1(next@15.4.0-canary.3(@babel/core@7.26.10)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react@19.1.0)': + '@vercel/analytics@1.3.1(next@15.4.0-canary.86(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react@19.1.0)': dependencies: server-only: 0.0.1 optionalDependencies: - next: 15.4.0-canary.3(@babel/core@7.26.10)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0) + next: 15.4.0-canary.86(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0) react: 19.1.0 '@vercel/git-hooks@1.0.0': {} @@ -6107,7 +6069,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -6346,10 +6308,6 @@ snapshots: esbuild: 0.25.2 load-tsconfig: 0.2.5 - busboy@1.6.0: - dependencies: - streamsearch: 1.1.0 - cac@6.7.14: {} cachedir@2.4.0: {} @@ -6376,8 +6334,6 @@ snapshots: camelcase-css@2.0.1: {} - caniuse-lite@1.0.30001680: {} - caniuse-lite@1.0.30001715: {} case-anything@2.1.13: {} @@ -6640,6 +6596,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.0: + dependencies: + ms: 2.1.3 + debug@4.4.0(supports-color@5.5.0): dependencies: ms: 2.1.3 @@ -6703,9 +6663,6 @@ snapshots: detect-libc@1.0.3: {} - detect-libc@2.0.3: - optional: true - detect-libc@2.0.4: {} didyoumean@1.2.2: {} @@ -7325,7 +7282,7 @@ snapshots: follow-redirects@1.15.9(debug@4.4.0): optionalDependencies: - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0 for-each@0.3.3: dependencies: @@ -7588,7 +7545,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -7601,14 +7558,14 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0 transitivePeerDependencies: - supports-color https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -7953,7 +7910,7 @@ snapshots: dependencies: chalk: 5.4.1 commander: 13.1.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0 execa: 8.0.1 lilconfig: 3.1.3 listr2: 8.3.2 @@ -8137,40 +8094,38 @@ snapshots: negotiator@1.0.0: {} - next-intl@3.25.1(next@15.4.0-canary.3(@babel/core@7.26.10)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react@19.1.0): + next-intl@3.26.5(next@15.4.0-canary.86(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react@19.1.0): dependencies: - '@formatjs/intl-localematcher': 0.5.4 + '@formatjs/intl-localematcher': 0.5.8 negotiator: 1.0.0 - next: 15.4.0-canary.3(@babel/core@7.26.10)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0) + next: 15.4.0-canary.86(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0) react: 19.1.0 - use-intl: 3.25.1(react@19.1.0) + use-intl: 3.26.5(react@19.1.0) - next-themes@0.2.1(next@15.4.0-canary.3(@babel/core@7.26.10)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + next-themes@0.2.1(next@15.4.0-canary.86(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - next: 15.4.0-canary.3(@babel/core@7.26.10)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0) + next: 15.4.0-canary.86(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - next@15.4.0-canary.3(@babel/core@7.26.10)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0): + next@15.4.0-canary.86(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0): dependencies: - '@next/env': 15.4.0-canary.3 - '@swc/counter': 0.1.3 + '@next/env': 15.4.0-canary.86 '@swc/helpers': 0.5.15 - busboy: 1.6.0 - caniuse-lite: 1.0.30001680 + caniuse-lite: 1.0.30001715 postcss: 8.4.31 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - styled-jsx: 5.1.6(@babel/core@7.26.10)(react@19.1.0) + styled-jsx: 5.1.6(react@19.1.0) optionalDependencies: - '@next/swc-darwin-arm64': 15.4.0-canary.3 - '@next/swc-darwin-x64': 15.4.0-canary.3 - '@next/swc-linux-arm64-gnu': 15.4.0-canary.3 - '@next/swc-linux-arm64-musl': 15.4.0-canary.3 - '@next/swc-linux-x64-gnu': 15.4.0-canary.3 - '@next/swc-linux-x64-musl': 15.4.0-canary.3 - '@next/swc-win32-arm64-msvc': 15.4.0-canary.3 - '@next/swc-win32-x64-msvc': 15.4.0-canary.3 + '@next/swc-darwin-arm64': 15.4.0-canary.86 + '@next/swc-darwin-x64': 15.4.0-canary.86 + '@next/swc-linux-arm64-gnu': 15.4.0-canary.86 + '@next/swc-linux-arm64-musl': 15.4.0-canary.86 + '@next/swc-linux-x64-gnu': 15.4.0-canary.86 + '@next/swc-linux-x64-musl': 15.4.0-canary.86 + '@next/swc-win32-arm64-msvc': 15.4.0-canary.86 + '@next/swc-win32-x64-msvc': 15.4.0-canary.86 '@playwright/test': 1.52.0 sass: 1.87.0 sharp: 0.34.1 @@ -8744,7 +8699,7 @@ snapshots: sharp@0.34.1: dependencies: color: 4.2.3 - detect-libc: 2.0.3 + detect-libc: 2.0.4 semver: 7.7.1 optionalDependencies: '@img/sharp-darwin-arm64': 0.34.1 @@ -8887,7 +8842,7 @@ snapshots: arg: 5.0.2 bluebird: 3.7.2 check-more-types: 2.24.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0 execa: 5.1.1 lazy-ass: 1.6.0 ps-tree: 1.2.0 @@ -8905,8 +8860,6 @@ snapshots: dependencies: duplexer: 0.1.2 - streamsearch@1.1.0: {} - string-argv@0.3.2: {} string-width@4.2.3: @@ -8995,12 +8948,10 @@ snapshots: strip-json-comments@3.1.1: {} - styled-jsx@5.1.6(@babel/core@7.26.10)(react@19.1.0): + styled-jsx@5.1.6(react@19.1.0): dependencies: client-only: 0.0.1 react: 19.1.0 - optionalDependencies: - '@babel/core': 7.26.10 sucrase@3.35.0: dependencies: @@ -9026,12 +8977,6 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - swr@2.2.5(react@19.1.0): - dependencies: - client-only: 0.0.1 - react: 19.1.0 - use-sync-external-store: 1.2.2(react@19.1.0) - symbol-tree@3.2.4: {} tabbable@6.2.0: {} @@ -9312,16 +9257,12 @@ snapshots: dependencies: punycode: 2.3.1 - use-intl@3.25.1(react@19.1.0): + use-intl@3.26.5(react@19.1.0): dependencies: '@formatjs/fast-memoize': 2.2.3 intl-messageformat: 10.7.7 react: 19.1.0 - use-sync-external-store@1.2.2(react@19.1.0): - dependencies: - react: 19.1.0 - util-deprecate@1.0.2: {} uuid@11.1.0: {} diff --git a/login/turbo.json b/login/turbo.json index 944b0ed6d0..597ed891be 100644 --- a/login/turbo.json +++ b/login/turbo.json @@ -28,6 +28,7 @@ "test:unit": {}, "test:unit:standalone": {}, "test:integration:setup": {}, + "test:acceptance:env": {}, "test:acceptance:setup": {}, "test:watch": { "persistent": true