- {/* Error from URL (OAuth failure) */}
+ {/* Error from URL (provider failure) */}
{urlError && (
{urlError}
@@ -302,7 +242,7 @@ export function LoginPage({ redirectUrl = "/_emdash/admin" }: LoginPageProps) {
{/* Login Card */}
- {method === "passkey" && (
+ {method === "passkey" && !activeProvider && (
- {/* OAuth Providers */}
-
- {OAUTH_PROVIDERS.map((provider) => (
- handleOAuthClick(provider.id)}
- className="w-full justify-center"
- >
- {provider.icon}
- {provider.name}
-
- ))}
-
+ {/* Auth provider buttons */}
+ {buttonProviders.length > 0 && (
+
+ {buttonProviders.map((provider) => {
+ const Btn = provider.LoginButton!;
+ const hasForm = !!provider.LoginForm;
+ const selectProvider = () => setActiveProvider(provider.id);
+ return (
+
+
+
+ );
+ })}
+
+ )}
{/* Magic Link Option */}
)}
+ {/* Provider form (full card replacement, like magic link) */}
+ {method === "passkey" &&
+ activeProvider &&
+ (() => {
+ const provider = authProviderList.find((p) => p.id === activeProvider);
+ if (!provider?.LoginForm) return null;
+ const Form = provider.LoginForm;
+ return (
+
+
+ setActiveProvider(null)}
+ >
+ {t`Back to login`}
+
+
+ );
+ })()}
+
{method === "magic-link" && setMethod("passkey")} />}
{/* Help text */}
- {method === "passkey"
- ? t`Use your registered passkey to sign in securely.`
- : t`We'll send you a link to sign in without a password.`}
+ {method === "magic-link"
+ ? t`We'll send you a link to sign in without a password.`
+ : activeProvider
+ ? t`Enter your handle to sign in.`
+ : t`Use your registered passkey to sign in securely.`}
{/* Signup link — only shown when self-signup is enabled */}
- {manifest?.signupEnabled && (
+ {authInfo?.signupEnabled && (
Don't have an account?{" "}
diff --git a/packages/admin/src/components/SetupWizard.tsx b/packages/admin/src/components/SetupWizard.tsx
index c8a877add..88d763427 100644
--- a/packages/admin/src/components/SetupWizard.tsx
+++ b/packages/admin/src/components/SetupWizard.tsx
@@ -6,8 +6,9 @@
*
* Steps:
* 1. Site Configuration (title, tagline, sample content)
- * 2. Admin Account (email, name)
- * 3. Passkey Registration
+ * 2. Create admin account — user picks any available auth method:
+ * - Passkey (always available)
+ * - Any configured auth provider (AT Protocol, GitHub, Google, etc.)
*/
import { Button, Checkbox, Input, Loader } from "@cloudflare/kumo";
@@ -17,6 +18,7 @@ import { useMutation, useQuery } from "@tanstack/react-query";
import * as React from "react";
import { apiFetch, fetchManifest, parseApiResponse } from "../lib/api/client";
+import { useAuthProviderList, type AuthProviderModule } from "../lib/auth-provider-context";
import { PasskeyRegistration } from "./auth/PasskeyRegistration";
import { BrandLogo } from "./Logo.js";
@@ -262,50 +264,97 @@ function AdminStep({ onNext, onBack, isLoading, error }: AdminStepProps) {
);
}
-interface PasskeyStepProps {
- adminData: SetupAdminRequest;
- onBack: () => void;
+function handleSetupSuccess() {
+ window.location.href = "/_emdash/admin";
}
-function handlePasskeySuccess() {
- // Redirect to admin dashboard after successful registration
- window.location.href = "/_emdash/admin";
+interface AuthMethodStepProps {
+ adminData: SetupAdminRequest;
+ providers: AuthProviderModule[];
+ onBack: () => void;
}
-function PasskeyStep({ adminData, onBack }: PasskeyStepProps) {
+function AuthMethodStep({ adminData, providers, onBack }: AuthMethodStepProps) {
const { t } = useLingui();
+ const [activeProvider, setActiveProvider] = React.useState(null);
+
+ const buttonProviders = providers.filter((p) => p.LoginButton);
+ const hasProviders = buttonProviders.length > 0;
+
+ // Show provider form (full card replacement)
+ if (activeProvider) {
+ const provider = providers.find((p) => p.id === activeProvider);
+ if (provider && (provider.SetupStep || provider.LoginForm)) {
+ return (
+
+
+
{t`Sign in with ${provider.label}`}
+
+ {provider.SetupStep ? (
+
+ ) : provider.LoginForm ? (
+
+ ) : null}
+
setActiveProvider(null)}
+ >
+ {t`← Back`}
+
+
+ );
+ }
+ }
+
return (
+ {/* Passkey option */}
-
-
{t`Set up your passkey`}
+
{t`Choose how to sign in`}
- {t`Passkeys are more secure than passwords. You'll use your device's biometrics, PIN, or security key to sign in.`}
+ {t`Pick any method to create your admin account.`}
+ {/* Auth provider options */}
+ {hasProviders && (
+ <>
+
+
+
+
+
+ Or continue with
+
+
+
+
+ {buttonProviders.map((provider) => {
+ const Btn = provider.LoginButton!;
+ const hasForm = !!provider.LoginForm || !!provider.SetupStep;
+ const selectProvider = () => setActiveProvider(provider.id);
+ return (
+
+
+
+ );
+ })}
+
+ >
+ )}
+
{t`← Back`}
@@ -330,7 +379,7 @@ function StepIndicator({ currentStep, useAccessAuth }: StepIndicatorProps) {
: ([
{ key: "site", label: t`Site` },
{ key: "admin", label: t`Account` },
- { key: "passkey", label: t`Passkey` },
+ { key: "passkey", label: t`Sign In` },
] as const);
const currentIndex = steps.findIndex((s) => s.key === currentStep);
@@ -393,6 +442,23 @@ export function SetupWizard() {
const [_siteData, setSiteData] = React.useState
(null);
const [adminData, setAdminData] = React.useState(null);
const [error, setError] = React.useState();
+ const [urlError, setUrlError] = React.useState(null);
+
+ // Auth provider components from virtual module (via context)
+ const authProviderList = useAuthProviderList();
+
+ // Check for error in URL (from OAuth/provider redirect)
+ React.useEffect(() => {
+ const params = new URLSearchParams(window.location.search);
+ const errorParam = params.get("error");
+ const message = params.get("message");
+
+ if (errorParam) {
+ setUrlError(message || `Authentication error: ${errorParam}`);
+ // Clean up URL
+ window.history.replaceState({}, "", window.location.pathname);
+ }
+ }, []);
// Check setup status
const {
@@ -424,7 +490,7 @@ export function SetupWizard() {
window.location.href = "/_emdash/admin";
return;
}
- // Otherwise continue to admin account creation
+ // Continue to admin account creation
setCurrentStep("admin");
},
onError: (err: Error) => {
@@ -510,6 +576,13 @@ export function SetupWizard() {
)}
+ {/* Error from URL (provider failure) */}
+ {urlError && (
+
+ {urlError}
+
+ )}
+
{/* Progress */}
@@ -537,8 +610,9 @@ export function SetupWizard() {
)}
{currentStep === "passkey" && adminData && (
- {
setError(undefined);
setCurrentStep("admin");
diff --git a/packages/admin/src/index.ts b/packages/admin/src/index.ts
index aadc83fde..75e0bba03 100644
--- a/packages/admin/src/index.ts
+++ b/packages/admin/src/index.ts
@@ -26,6 +26,15 @@ export {
type PluginAdmins,
} from "./lib/plugin-context";
+// Auth provider context (for accessing pluggable auth provider components)
+export {
+ AuthProviderProvider,
+ useAuthProviders,
+ useAuthProviderList,
+ type AuthProviderModule,
+ type AuthProviders,
+} from "./lib/auth-provider-context";
+
// Locales
export {
useLocale,
diff --git a/packages/admin/src/lib/api/client.ts b/packages/admin/src/lib/api/client.ts
index a9730f4e4..0b9887188 100644
--- a/packages/admin/src/lib/api/client.ts
+++ b/packages/admin/src/lib/api/client.ts
@@ -179,3 +179,20 @@ export async function fetchManifest(): Promise {
const response = await apiFetch(`${API_BASE}/manifest`);
return parseApiResponse(response, "Failed to fetch manifest");
}
+
+/**
+ * Fetch auth mode (public endpoint — works without authentication).
+ * Used by the login page to determine which login UI to render.
+ */
+export async function fetchAuthMode(): Promise<{
+ authMode: string;
+ signupEnabled?: boolean;
+ providers?: Array<{ id: string; label: string }>;
+}> {
+ const response = await apiFetch(`${API_BASE}/auth/mode`);
+ return parseApiResponse<{
+ authMode: string;
+ signupEnabled?: boolean;
+ providers?: Array<{ id: string; label: string }>;
+ }>(response, "Failed to fetch auth mode");
+}
diff --git a/packages/admin/src/lib/api/index.ts b/packages/admin/src/lib/api/index.ts
index 7be62fbff..ae3a98e05 100644
--- a/packages/admin/src/lib/api/index.ts
+++ b/packages/admin/src/lib/api/index.ts
@@ -13,6 +13,7 @@ export {
type FindManyResult,
type AdminManifest,
fetchManifest,
+ fetchAuthMode,
} from "./client.js";
// Content CRUD and revisions
diff --git a/packages/admin/src/lib/auth-provider-context.tsx b/packages/admin/src/lib/auth-provider-context.tsx
new file mode 100644
index 000000000..539be0231
--- /dev/null
+++ b/packages/admin/src/lib/auth-provider-context.tsx
@@ -0,0 +1,62 @@
+/**
+ * Auth Provider Context
+ *
+ * Provides pluggable auth provider UI components (LoginButton, LoginForm, SetupStep)
+ * to the admin UI via React context. Auth providers are registered in astro.config.ts
+ * and their admin components are bundled via the virtual:emdash/auth-providers module.
+ */
+
+import * as React from "react";
+import { createContext, useContext } from "react";
+
+/** Shape of a single auth provider's admin exports */
+export interface AuthProviderModule {
+ id: string;
+ label: string;
+ /** Compact button for the login page (icon + label) */
+ LoginButton?: React.ComponentType;
+ /** Full form if the provider needs custom input (e.g., handle field) */
+ LoginForm?: React.ComponentType;
+ /** Component for the setup wizard admin creation step */
+ SetupStep?: React.ComponentType<{ onComplete: () => void }>;
+}
+
+/** All auth provider modules keyed by provider ID */
+export type AuthProviders = Record;
+
+const AuthProviderContext = createContext({});
+
+export interface AuthProviderContextProps {
+ children: React.ReactNode;
+ authProviders: AuthProviders;
+}
+
+/**
+ * Provider that makes auth provider components available to all descendants
+ */
+export function AuthProviderProvider({ children, authProviders }: AuthProviderContextProps) {
+ return (
+ {children}
+ );
+}
+
+/**
+ * Get all auth provider modules
+ */
+export function useAuthProviders(): AuthProviders {
+ return useContext(AuthProviderContext);
+}
+
+/**
+ * Get auth providers as an ordered array (buttons first, then forms)
+ */
+export function useAuthProviderList(): AuthProviderModule[] {
+ const providers = useContext(AuthProviderContext);
+ const list = Object.values(providers);
+ // Sort: providers with only LoginButton first (compact), then those with LoginForm
+ return list.toSorted((a, b) => {
+ const aHasForm = a.LoginForm ? 1 : 0;
+ const bHasForm = b.LoginForm ? 1 : 0;
+ return aHasForm - bHasForm;
+ });
+}
diff --git a/packages/admin/src/locales/ar/messages.po b/packages/admin/src/locales/ar/messages.po
index 1b1a3ce82..13a4a6579 100644
--- a/packages/admin/src/locales/ar/messages.po
+++ b/packages/admin/src/locales/ar/messages.po
@@ -63,7 +63,7 @@ msgid "{0, plural, one {(# item)} other {(# items)}}"
msgstr "{0, plural, few {(# عناصر)} many {(# عنصرًا)}} other {(# عنصر)}}"
#. placeholder {0}: seedInfo.collections
-#: packages/admin/src/components/SetupWizard.tsx:181
+#: packages/admin/src/components/SetupWizard.tsx:183
msgid "{0, plural, one {# collection} other {# collections}}"
msgstr "{0, plural, few {# مجموعات} many {# مجموعةً} other {# مجموعة}}"
@@ -319,8 +319,9 @@ msgstr "• يتم رفعها إلى مساحة تخزين وسائط EmDash ا
msgid "• URLs in your content are updated automatically"
msgstr "• يتم تحديث الروابط في محتواك تلقائيًا"
-#: packages/admin/src/components/SetupWizard.tsx:250
-#: packages/admin/src/components/SetupWizard.tsx:310
+#: packages/admin/src/components/SetupWizard.tsx:252
+#: packages/admin/src/components/SetupWizard.tsx:304
+#: packages/admin/src/components/SetupWizard.tsx:359
#: packages/admin/src/components/WordPressImport.tsx:1431
msgid "← Back"
msgstr "← رجوع"
@@ -413,7 +414,7 @@ msgstr "قبول وتثبيت"
msgid "Accept & Update"
msgstr "قبول وتحديث"
-#: packages/admin/src/components/SetupWizard.tsx:332
+#: packages/admin/src/components/SetupWizard.tsx:381
msgid "Account"
msgstr "الحساب"
@@ -698,7 +699,7 @@ msgstr "قم بتعيين مؤلفي WordPress إلى مستخدمي EmDash. س
msgid "Authentication error: {0}"
msgstr "خطأ في المصادقة: {0}"
-#: packages/admin/src/components/LoginPage.tsx:253
+#: packages/admin/src/components/LoginPage.tsx:195
msgid "Authentication error: {error}"
msgstr "خطأ في المصادقة: {error}"
@@ -768,8 +769,9 @@ msgstr "رجوع"
msgid "Back to {collectionLabel} list"
msgstr "العودة إلى قائمة {collectionLabel}"
-#: packages/admin/src/components/LoginPage.tsx:174
-#: packages/admin/src/components/LoginPage.tsx:210
+#: packages/admin/src/components/LoginPage.tsx:117
+#: packages/admin/src/components/LoginPage.tsx:153
+#: packages/admin/src/components/LoginPage.tsx:319
#: packages/admin/src/components/SignupPage.tsx:280
msgid "Back to login"
msgstr "العودة إلى تسجيل الدخول"
@@ -961,7 +963,7 @@ msgstr "التحقق من التحديثات"
msgid "Check Site"
msgstr "فحص الموقع"
-#: packages/admin/src/components/LoginPage.tsx:158
+#: packages/admin/src/components/LoginPage.tsx:101
#: packages/admin/src/components/SignupPage.tsx:129
#: packages/admin/src/components/SignupPage.tsx:401
msgid "Check your email"
@@ -975,6 +977,10 @@ msgstr "جاري التحقق من {urlInput}..."
msgid "Checking authentication..."
msgstr "جاري التحقق من المصادقة..."
+#: packages/admin/src/components/SetupWizard.tsx:315
+msgid "Choose how to sign in"
+msgstr ""
+
#: packages/admin/src/components/Settings.tsx:130
msgid "Choose your preferred admin language"
msgstr "إختر لغة التحكم المفضلة لديك"
@@ -983,7 +989,7 @@ msgstr "إختر لغة التحكم المفضلة لديك"
msgid "Click the link in the email to continue setting up your account."
msgstr "اضغط على الرابط في البريد الإلكتروني لمتابعة إعداد حسابك."
-#: packages/admin/src/components/LoginPage.tsx:169
+#: packages/admin/src/components/LoginPage.tsx:112
msgid "Click the link in the email to sign in."
msgstr "إضغط على الرابط في البريد الإلكتروني لتسجيل الدخول."
@@ -1189,8 +1195,8 @@ msgstr "كتابة المحتوى"
msgid "Continue"
msgstr "متابعة"
-#: packages/admin/src/components/SetupWizard.tsx:175
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:177
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Continue →"
msgstr "متابعة >"
@@ -1295,7 +1301,7 @@ msgstr "إنشاء رمز جديد"
msgid "Create one in WordPress: Users → Profile → Application Passwords"
msgstr "أنشئ واحدة في WordPress: المستخدمون → الملف الشخصي → كلمات مرور التطبيق"
-#: packages/admin/src/components/SetupWizard.tsx:305
+#: packages/admin/src/components/SetupWizard.tsx:325
msgid "Create Passkey"
msgstr "إنشاء مفتاح مرور"
@@ -1340,7 +1346,7 @@ msgstr "إنشاء تصنيف"
msgid "Create Token"
msgstr "إنشاء رمز"
-#: packages/admin/src/components/SetupWizard.tsx:505
+#: packages/admin/src/components/SetupWizard.tsx:571
msgid "Create your account"
msgstr "قم بإنشاء حسابك"
@@ -1728,7 +1734,7 @@ msgstr "تم إزالة النطاق"
msgid "Domain updated"
msgstr "تم تحديث النطاق"
-#: packages/admin/src/components/LoginPage.tsx:366
+#: packages/admin/src/components/LoginPage.tsx:340
msgid "Don't have an account? <0>Sign up0>"
msgstr "أليس لديك حساب؟ قم ب<0>التسجيل0>"
@@ -1857,13 +1863,13 @@ msgstr "البريد الإلكتروني"
msgid "Email (optional)"
msgstr "البريد الإلكتروني (اختياري)"
-#: packages/admin/src/components/LoginPage.tsx:183
+#: packages/admin/src/components/LoginPage.tsx:126
#: packages/admin/src/components/SignupPage.tsx:65
#: packages/admin/src/components/users/InviteUserModal.tsx:154
msgid "Email address"
msgstr "عنوان البريد الإلكتروني"
-#: packages/admin/src/components/SetupWizard.tsx:204
+#: packages/admin/src/components/SetupWizard.tsx:206
#: packages/admin/src/components/SignupPage.tsx:48
msgid "Email is required"
msgstr "البريد الإلكتروني مطلوب"
@@ -1969,6 +1975,10 @@ msgstr "أدخل الاسم"
msgid "Enter the code from your terminal"
msgstr "أدخل الرمز من الطرفية الخاصة بك"
+#: packages/admin/src/components/LoginPage.tsx:333
+msgid "Enter your handle to sign in."
+msgstr ""
+
#: packages/admin/src/components/WordPressImport.tsx:1289
msgid "Enter your WordPress credentials to import content directly."
msgstr "أدخل بيانات اعتماد WordPress لاستيراد المحتوى مباشرةً."
@@ -1979,7 +1989,7 @@ msgstr "أدخل رابط موقع WordPress الخاص بك"
#: packages/admin/src/components/MenuEditor.tsx:83
#: packages/admin/src/components/MenuEditor.tsx:122
-#: packages/admin/src/components/SetupWizard.tsx:484
+#: packages/admin/src/components/SetupWizard.tsx:550
msgid "Error"
msgstr "خطأ"
@@ -2093,7 +2103,7 @@ msgstr "فشل في تحميل الإضافات: {0}"
msgid "Failed to load revisions"
msgstr "فشل في تحميل المراجعات"
-#: packages/admin/src/components/SetupWizard.tsx:486
+#: packages/admin/src/components/SetupWizard.tsx:552
msgid "Failed to load setup"
msgstr "فشل في تحميل الإعداد"
@@ -2124,8 +2134,8 @@ msgstr ""
msgid "Failed to save settings"
msgstr "فشل في حفظ الإعدادات"
-#: packages/admin/src/components/LoginPage.tsx:127
-#: packages/admin/src/components/LoginPage.tsx:132
+#: packages/admin/src/components/LoginPage.tsx:70
+#: packages/admin/src/components/LoginPage.tsx:75
msgid "Failed to send magic link"
msgstr "فشل في إرسال الرابط السحري"
@@ -2332,7 +2342,7 @@ msgstr "تم تعتيم الأيقونة بسبب التدقيق على الصو
msgid "ID"
msgstr "المعرف"
-#: packages/admin/src/components/LoginPage.tsx:160
+#: packages/admin/src/components/LoginPage.tsx:103
msgid "If an account exists for <0>{email}0>, we've sent a sign-in link."
msgstr "إذا كان يوجد حساب لـ <0>{email}0>، قمنا بإرسال رابط لتسجيل دخول."
@@ -2452,7 +2462,7 @@ msgstr "جاري استيراد المحتوى..."
msgid "Importing Media"
msgstr "جاري استيراد الوسائط"
-#: packages/admin/src/components/SetupWizard.tsx:163
+#: packages/admin/src/components/SetupWizard.tsx:165
msgid "Include sample content (recommended for new sites)"
msgstr "تضمين محتوى نموذجي (مُوصى به للمواقع الجديدة)"
@@ -2565,7 +2575,7 @@ msgstr "تم حذف العنصر"
msgid "Item updated"
msgstr "تم تحديث العنصر"
-#: packages/admin/src/components/SetupWizard.tsx:238
+#: packages/admin/src/components/SetupWizard.tsx:240
#: packages/admin/src/components/SignupPage.tsx:204
msgid "Jane Doe"
msgstr "جين دو"
@@ -2589,7 +2599,7 @@ msgstr "الكلمات المفتاحية"
msgid "Label"
msgstr "التسمية"
-#: packages/admin/src/components/LoginPage.tsx:379
+#: packages/admin/src/components/LoginPage.tsx:353
#: packages/admin/src/components/Settings.tsx:129
#: packages/admin/src/components/Settings.tsx:134
msgid "Language"
@@ -2729,7 +2739,7 @@ msgstr "جاري تحميل الأقسام..."
msgid "Loading settings..."
msgstr "جاري تحميل الإعدادات..."
-#: packages/admin/src/components/SetupWizard.tsx:473
+#: packages/admin/src/components/SetupWizard.tsx:539
msgid "Loading setup..."
msgstr "جاري تحميل الإعداد..."
@@ -3025,7 +3035,7 @@ msgstr "نص عادي متعدد الأسطر"
msgid "Multiple choices from options"
msgstr "اختيارات متعددة من الخيارات المتاحة"
-#: packages/admin/src/components/SetupWizard.tsx:145
+#: packages/admin/src/components/SetupWizard.tsx:147
msgid "My Awesome Blog"
msgstr "مدونتي الرائعة"
@@ -3407,7 +3417,7 @@ msgstr "أو اختر من المكتبة"
msgid "Or click to browse. Accepts .xml files exported from WordPress."
msgstr "أو اضغط للتصفح. يقبل ملفات .xml المُصدَّرة من WordPress."
-#: packages/admin/src/components/LoginPage.tsx:321
+#: packages/admin/src/components/LoginPage.tsx:269
msgid "Or continue with"
msgstr "أو المواصلة مع"
@@ -3466,10 +3476,6 @@ msgstr "جزء من حلقة إعادة توجيه لا منتهية"
msgid "Pass"
msgstr "نجح"
-#: packages/admin/src/components/SetupWizard.tsx:333
-msgid "Passkey"
-msgstr "مفتاح المرور"
-
#: packages/admin/src/components/settings/SecuritySettings.tsx:98
msgid "Passkey added successfully"
msgstr "تم إضافة مفتاح المرور بنجاح"
@@ -3511,10 +3517,6 @@ msgstr "مفاتيح المرور هي طريقة آمنة وبدون كلمة
msgid "Passkeys are a secure, passwordless way to sign in using your device's biometrics, PIN, or security key."
msgstr "مفاتيح المرور هي طريقة آمنة خالية من كلمات المرور لتسجيل الدخول باستخدام القياسات الحيوية لجهازك أو رمز PIN أو مفتاح الأمان."
-#: packages/admin/src/components/SetupWizard.tsx:297
-msgid "Passkeys are more secure than passwords. You'll use your device's biometrics, PIN, or security key to sign in."
-msgstr "مفاتيح المرور أكثر أمانًا من كلمات المرور. ستستخدم القياسات الحيوية لجهازك أو رمز PIN أو مفتاح الأمان لتسجيل الدخول."
-
#: packages/admin/src/components/auth/PasskeyLogin.tsx:303
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:300
msgid "Passkeys Not Available Here"
@@ -3562,12 +3564,16 @@ msgstr "حذف {title} نهائيًا"
msgid "Permissions"
msgstr "الصلاحيات"
+#: packages/admin/src/components/SetupWizard.tsx:317
+msgid "Pick any method to create your admin account."
+msgstr ""
+
#: packages/admin/src/components/auth/PasskeyLogin.tsx:313
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:310
msgid "Plain"
msgstr "عادي"
-#: packages/admin/src/components/SetupWizard.tsx:206
+#: packages/admin/src/components/SetupWizard.tsx:208
msgid "Please enter a valid email"
msgstr "يرجى إدخال بريد إلكتروني صحيح"
@@ -3641,7 +3647,7 @@ msgid "Preparing to download files from WordPress..."
msgstr "جاري التحضير لتنزيل الملفات من WordPress..."
#: packages/admin/src/components/auth/PasskeyLogin.tsx:166
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Preparing..."
msgstr "جاري التحضير..."
@@ -4268,7 +4274,7 @@ msgstr "أقسام"
msgid "secure context"
msgstr "سياق آمن"
-#: packages/admin/src/components/SetupWizard.tsx:506
+#: packages/admin/src/components/SetupWizard.tsx:572
msgid "Secure your account"
msgstr "تأمين حسابك"
@@ -4392,7 +4398,7 @@ msgstr "أرسل بريدًا إلكترونيًا دعوةً لعضو فريق
msgid "Send Invite"
msgstr "إرسال الدعوة"
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
msgid "Send magic link"
msgstr "إرسال رابط سحري"
@@ -4408,7 +4414,7 @@ msgstr "إرسال تجريبي"
msgid "Send Test Email"
msgstr "إرسال بريد إلكتروني تجريبي"
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
#: packages/admin/src/components/settings/EmailSettings.tsx:162
#: packages/admin/src/components/SignupPage.tsx:87
#: packages/admin/src/components/SignupPage.tsx:150
@@ -4444,15 +4450,11 @@ msgstr "عنوان تحسين محركات البحث"
msgid "Set a custom display size for this image instance."
msgstr "عيّن حجم عرض مخصص لنسخة هذه الصورة."
-#: packages/admin/src/components/SetupWizard.tsx:295
-msgid "Set up your passkey"
-msgstr "إعداد مفتاح مرورك"
-
-#: packages/admin/src/components/SetupWizard.tsx:504
+#: packages/admin/src/components/SetupWizard.tsx:570
msgid "Set up your site"
msgstr "إعداد موقعك"
-#: packages/admin/src/components/SetupWizard.tsx:175
+#: packages/admin/src/components/SetupWizard.tsx:177
msgid "Setting up..."
msgstr "جاري الإعداد..."
@@ -4500,24 +4502,35 @@ msgstr "يُعرض عند التحويم فوق الصورة."
msgid "Sign in"
msgstr "تسجيل الدخول"
+#: packages/admin/src/components/SetupWizard.tsx:382
+msgid "Sign In"
+msgstr ""
+
#: packages/admin/src/components/SignupPage.tsx:269
msgid "Sign in instead"
msgstr "تسجيل الدخول بدلًا من ذلك"
-#: packages/admin/src/components/LoginPage.tsx:291
+#: packages/admin/src/components/LoginPage.tsx:240
msgid "Sign in to your site"
msgstr "قم بتسجيل الدخول إلى موقعك"
-#: packages/admin/src/components/LoginPage.tsx:292
+#. placeholder {0}: authProviderList.find((p) => p.id === activeProvider)?.label ?? activeProvider
+#. placeholder {0}: provider.label
+#: packages/admin/src/components/LoginPage.tsx:239
+#: packages/admin/src/components/SetupWizard.tsx:291
+msgid "Sign in with {0}"
+msgstr ""
+
+#: packages/admin/src/components/LoginPage.tsx:237
msgid "Sign in with email"
msgstr "تسجيل الدخول بالبريد الإلكتروني"
-#: packages/admin/src/components/LoginPage.tsx:348
+#: packages/admin/src/components/LoginPage.tsx:298
msgid "Sign in with email link"
msgstr "تسجيل الدخول برابط البريد الإلكتروني"
#: packages/admin/src/components/auth/PasskeyLogin.tsx:136
-#: packages/admin/src/components/LoginPage.tsx:312
+#: packages/admin/src/components/LoginPage.tsx:260
msgid "Sign in with Passkey"
msgstr "تسجيل الدخول بمفتاح مرور"
@@ -4534,7 +4547,7 @@ msgstr "اختيار واحد من الخيارات المتاحة"
msgid "Single line text input"
msgstr "إدخال نص في سطر واحد"
-#: packages/admin/src/components/SetupWizard.tsx:331
+#: packages/admin/src/components/SetupWizard.tsx:380
msgid "Site"
msgstr "الموقع"
@@ -4546,12 +4559,12 @@ msgstr "هوية الموقع"
msgid "Site identity, logo, favicon, and reading preferences"
msgstr "هوية الموقع، الشعار، أيقونة المتصفح، وإعدادات القراءة"
-#: packages/admin/src/components/SetupWizard.tsx:329
+#: packages/admin/src/components/SetupWizard.tsx:378
msgid "Site Settings"
msgstr "إعدادات الموقع"
#: packages/admin/src/components/settings/GeneralSettings.tsx:156
-#: packages/admin/src/components/SetupWizard.tsx:141
+#: packages/admin/src/components/SetupWizard.tsx:143
msgid "Site Title"
msgstr "عنوان الموقع"
@@ -4559,7 +4572,7 @@ msgstr "عنوان الموقع"
msgid "Site title & tagline"
msgstr "عنوان الموقع والشعار الكتابي"
-#: packages/admin/src/components/SetupWizard.tsx:125
+#: packages/admin/src/components/SetupWizard.tsx:127
msgid "Site title is required"
msgstr "عنوان الموقع مطلوب"
@@ -4703,7 +4716,7 @@ msgid "System ({resolvedLabel})"
msgstr "النظام ({resolvedLabel})"
#: packages/admin/src/components/settings/GeneralSettings.tsx:162
-#: packages/admin/src/components/SetupWizard.tsx:152
+#: packages/admin/src/components/SetupWizard.tsx:154
msgid "Tagline"
msgstr "الشعار"
@@ -4741,7 +4754,7 @@ msgstr "تم إنشاء التصنيف"
msgid "Taxonomy not found:"
msgstr "لم يتم العثور على التصنيف:"
-#: packages/admin/src/components/SetupWizard.tsx:180
+#: packages/admin/src/components/SetupWizard.tsx:182
msgid "Template:"
msgstr "القالب:"
@@ -4774,7 +4787,7 @@ msgstr "الجداول التالية تحتوي على محتوى لكنها غ
msgid "The invited user will have this role once they complete registration."
msgstr "سيكون للمستخدم المدعو هذا الدور بمجرد إكمال التسجيل."
-#: packages/admin/src/components/LoginPage.tsx:170
+#: packages/admin/src/components/LoginPage.tsx:113
#: packages/admin/src/components/SignupPage.tsx:138
msgid "The link will expire in 15 minutes."
msgstr "ستنتهي صلاحية الرابط بعد 15 دقيقة."
@@ -4899,7 +4912,7 @@ msgstr "سيؤدي هذا إلى إزالة الإضافة وحزمتها من
msgid "This will revert to the published version. Your draft changes will be lost."
msgstr "سيؤدي هذا إلى الرجوع إلى النسخة المنشورة. ستُفقد تغييرات المسودة."
-#: packages/admin/src/components/SetupWizard.tsx:156
+#: packages/admin/src/components/SetupWizard.tsx:158
msgid "Thoughts, tutorials, and more"
msgstr "أفكار، شروحات، والمزيد"
@@ -5265,7 +5278,7 @@ msgstr "استخدم [param] أو [...rest] في الروابط النسبية
msgid "Use your device's biometric authentication, security key, or PIN to sign in."
msgstr "استخدم المصادقة الحيوية لجهازك أو مفتاح الأمان أو رمز PIN لتسجيل الدخول."
-#: packages/admin/src/components/LoginPage.tsx:359
+#: packages/admin/src/components/LoginPage.tsx:334
msgid "Use your registered passkey to sign in securely."
msgstr "قم باستخدام مفتاح المرور المسجل الخاص بك لتسجيل الدخول بشكل آمن."
@@ -5383,7 +5396,7 @@ msgstr "تعذّر الاتصال بموقع WordPress على {0}. قد يعني
msgid "We'll check what import options are available for your site."
msgstr "سنقوم بالتحقق من خيارات الاستيراد المتاحة لموقعك."
-#: packages/admin/src/components/LoginPage.tsx:360
+#: packages/admin/src/components/LoginPage.tsx:331
msgid "We'll send you a link to sign in without a password."
msgstr "سنقوم بإرسال رابط إليك لتسجيل الدخول بدون كلمة مرور."
@@ -5520,7 +5533,7 @@ msgstr "سيتم توجيهك إلى WordPress لتفويض الاتصال."
msgid "You'll be signing up as"
msgstr "ستقوم بالتسجيل بوصفك"
-#: packages/admin/src/components/SetupWizard.tsx:509
+#: packages/admin/src/components/SetupWizard.tsx:575
msgid "You're signed in via Cloudflare Access"
msgstr "تم تسجيل دخولك عبر Cloudflare Access"
@@ -5529,7 +5542,7 @@ msgid "you@company.com"
msgstr "you@company.com"
#: packages/admin/src/components/auth/PasskeyLogin.tsx:336
-#: packages/admin/src/components/SetupWizard.tsx:226
+#: packages/admin/src/components/SetupWizard.tsx:228
msgid "you@example.com"
msgstr "you@example.com"
@@ -5547,7 +5560,7 @@ msgstr "متصفحك لا يدعم مفاتيح المرور. يرجى استخ
msgid "Your device doesn't support the required security features."
msgstr "جهازك لا يدعم ميزات الأمن المطلوبة."
-#: packages/admin/src/components/SetupWizard.tsx:222
+#: packages/admin/src/components/SetupWizard.tsx:224
msgid "Your Email"
msgstr "بريدك الإلكتروني"
@@ -5567,7 +5580,7 @@ msgstr "اسم مستخدم Instagram الخاص بك"
msgid "Your LinkedIn profile username"
msgstr "اسم مستخدم ملف LinkedIn الشخصي"
-#: packages/admin/src/components/SetupWizard.tsx:234
+#: packages/admin/src/components/SetupWizard.tsx:236
msgid "Your Name"
msgstr "اسمك"
diff --git a/packages/admin/src/locales/de/messages.po b/packages/admin/src/locales/de/messages.po
index d13a9b0c1..48eb9ba5c 100644
--- a/packages/admin/src/locales/de/messages.po
+++ b/packages/admin/src/locales/de/messages.po
@@ -59,7 +59,7 @@ msgid "{0, plural, one {(# item)} other {(# items)}}"
msgstr ""
#. placeholder {0}: seedInfo.collections
-#: packages/admin/src/components/SetupWizard.tsx:181
+#: packages/admin/src/components/SetupWizard.tsx:183
msgid "{0, plural, one {# collection} other {# collections}}"
msgstr ""
@@ -315,8 +315,9 @@ msgstr ""
msgid "• URLs in your content are updated automatically"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:250
-#: packages/admin/src/components/SetupWizard.tsx:310
+#: packages/admin/src/components/SetupWizard.tsx:252
+#: packages/admin/src/components/SetupWizard.tsx:304
+#: packages/admin/src/components/SetupWizard.tsx:359
#: packages/admin/src/components/WordPressImport.tsx:1431
msgid "← Back"
msgstr ""
@@ -409,7 +410,7 @@ msgstr ""
msgid "Accept & Update"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:332
+#: packages/admin/src/components/SetupWizard.tsx:381
msgid "Account"
msgstr ""
@@ -694,7 +695,7 @@ msgstr ""
msgid "Authentication error: {0}"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:253
+#: packages/admin/src/components/LoginPage.tsx:195
msgid "Authentication error: {error}"
msgstr "Authentifizierungsfehler: {error}"
@@ -764,8 +765,9 @@ msgstr ""
msgid "Back to {collectionLabel} list"
msgstr "Zurück zur Liste ({collectionLabel})"
-#: packages/admin/src/components/LoginPage.tsx:174
-#: packages/admin/src/components/LoginPage.tsx:210
+#: packages/admin/src/components/LoginPage.tsx:117
+#: packages/admin/src/components/LoginPage.tsx:153
+#: packages/admin/src/components/LoginPage.tsx:319
#: packages/admin/src/components/SignupPage.tsx:280
msgid "Back to login"
msgstr "Zurück zur Anmeldung"
@@ -957,7 +959,7 @@ msgstr ""
msgid "Check Site"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:158
+#: packages/admin/src/components/LoginPage.tsx:101
#: packages/admin/src/components/SignupPage.tsx:129
#: packages/admin/src/components/SignupPage.tsx:401
msgid "Check your email"
@@ -971,6 +973,10 @@ msgstr ""
msgid "Checking authentication..."
msgstr ""
+#: packages/admin/src/components/SetupWizard.tsx:315
+msgid "Choose how to sign in"
+msgstr ""
+
#: packages/admin/src/components/Settings.tsx:130
msgid "Choose your preferred admin language"
msgstr "Wähle deine bevorzugte Admin-Sprache"
@@ -979,7 +985,7 @@ msgstr "Wähle deine bevorzugte Admin-Sprache"
msgid "Click the link in the email to continue setting up your account."
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:169
+#: packages/admin/src/components/LoginPage.tsx:112
msgid "Click the link in the email to sign in."
msgstr "Klicke auf den Link in der E-Mail, um dich anzumelden."
@@ -1185,8 +1191,8 @@ msgstr "Inhalte bearbeiten"
msgid "Continue"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:175
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:177
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Continue →"
msgstr ""
@@ -1291,7 +1297,7 @@ msgstr "Neues Token erstellen"
msgid "Create one in WordPress: Users → Profile → Application Passwords"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:305
+#: packages/admin/src/components/SetupWizard.tsx:325
msgid "Create Passkey"
msgstr ""
@@ -1336,7 +1342,7 @@ msgstr ""
msgid "Create Token"
msgstr "Token erstellen"
-#: packages/admin/src/components/SetupWizard.tsx:505
+#: packages/admin/src/components/SetupWizard.tsx:571
msgid "Create your account"
msgstr ""
@@ -1724,7 +1730,7 @@ msgstr ""
msgid "Domain updated"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:366
+#: packages/admin/src/components/LoginPage.tsx:340
msgid "Don't have an account? <0>Sign up0>"
msgstr "Noch kein Konto? <0>Registrieren0>"
@@ -1853,13 +1859,13 @@ msgstr "E-Mail"
msgid "Email (optional)"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:183
+#: packages/admin/src/components/LoginPage.tsx:126
#: packages/admin/src/components/SignupPage.tsx:65
#: packages/admin/src/components/users/InviteUserModal.tsx:154
msgid "Email address"
msgstr "E-Mail-Adresse"
-#: packages/admin/src/components/SetupWizard.tsx:204
+#: packages/admin/src/components/SetupWizard.tsx:206
#: packages/admin/src/components/SignupPage.tsx:48
msgid "Email is required"
msgstr ""
@@ -1965,6 +1971,10 @@ msgstr ""
msgid "Enter the code from your terminal"
msgstr ""
+#: packages/admin/src/components/LoginPage.tsx:333
+msgid "Enter your handle to sign in."
+msgstr ""
+
#: packages/admin/src/components/WordPressImport.tsx:1289
msgid "Enter your WordPress credentials to import content directly."
msgstr ""
@@ -1975,7 +1985,7 @@ msgstr ""
#: packages/admin/src/components/MenuEditor.tsx:83
#: packages/admin/src/components/MenuEditor.tsx:122
-#: packages/admin/src/components/SetupWizard.tsx:484
+#: packages/admin/src/components/SetupWizard.tsx:550
msgid "Error"
msgstr ""
@@ -2089,7 +2099,7 @@ msgstr ""
msgid "Failed to load revisions"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:486
+#: packages/admin/src/components/SetupWizard.tsx:552
msgid "Failed to load setup"
msgstr ""
@@ -2120,8 +2130,8 @@ msgstr ""
msgid "Failed to save settings"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:127
-#: packages/admin/src/components/LoginPage.tsx:132
+#: packages/admin/src/components/LoginPage.tsx:70
+#: packages/admin/src/components/LoginPage.tsx:75
msgid "Failed to send magic link"
msgstr "Magic Link konnte nicht gesendet werden"
@@ -2328,7 +2338,7 @@ msgstr ""
msgid "ID"
msgstr "ID"
-#: packages/admin/src/components/LoginPage.tsx:160
+#: packages/admin/src/components/LoginPage.tsx:103
msgid "If an account exists for <0>{email}0>, we've sent a sign-in link."
msgstr "Wenn es ein Konto für <0>{email}0> gibt, haben wir dir einen Anmeldelink geschickt."
@@ -2448,7 +2458,7 @@ msgstr ""
msgid "Importing Media"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:163
+#: packages/admin/src/components/SetupWizard.tsx:165
msgid "Include sample content (recommended for new sites)"
msgstr ""
@@ -2561,7 +2571,7 @@ msgstr ""
msgid "Item updated"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:238
+#: packages/admin/src/components/SetupWizard.tsx:240
#: packages/admin/src/components/SignupPage.tsx:204
msgid "Jane Doe"
msgstr ""
@@ -2585,7 +2595,7 @@ msgstr ""
msgid "Label"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:379
+#: packages/admin/src/components/LoginPage.tsx:353
#: packages/admin/src/components/Settings.tsx:129
#: packages/admin/src/components/Settings.tsx:134
msgid "Language"
@@ -2725,7 +2735,7 @@ msgstr ""
msgid "Loading settings..."
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:473
+#: packages/admin/src/components/SetupWizard.tsx:539
msgid "Loading setup..."
msgstr ""
@@ -3021,7 +3031,7 @@ msgstr ""
msgid "Multiple choices from options"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:145
+#: packages/admin/src/components/SetupWizard.tsx:147
msgid "My Awesome Blog"
msgstr ""
@@ -3403,7 +3413,7 @@ msgstr ""
msgid "Or click to browse. Accepts .xml files exported from WordPress."
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:321
+#: packages/admin/src/components/LoginPage.tsx:269
msgid "Or continue with"
msgstr "Oder fortfahren mit"
@@ -3462,10 +3472,6 @@ msgstr ""
msgid "Pass"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:333
-msgid "Passkey"
-msgstr ""
-
#: packages/admin/src/components/settings/SecuritySettings.tsx:98
msgid "Passkey added successfully"
msgstr ""
@@ -3507,10 +3513,6 @@ msgstr ""
msgid "Passkeys are a secure, passwordless way to sign in using your device's biometrics, PIN, or security key."
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:297
-msgid "Passkeys are more secure than passwords. You'll use your device's biometrics, PIN, or security key to sign in."
-msgstr ""
-
#: packages/admin/src/components/auth/PasskeyLogin.tsx:303
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:300
msgid "Passkeys Not Available Here"
@@ -3558,12 +3560,16 @@ msgstr "{title} endgültig löschen"
msgid "Permissions"
msgstr ""
+#: packages/admin/src/components/SetupWizard.tsx:317
+msgid "Pick any method to create your admin account."
+msgstr ""
+
#: packages/admin/src/components/auth/PasskeyLogin.tsx:313
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:310
msgid "Plain"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:206
+#: packages/admin/src/components/SetupWizard.tsx:208
msgid "Please enter a valid email"
msgstr ""
@@ -3637,7 +3643,7 @@ msgid "Preparing to download files from WordPress..."
msgstr ""
#: packages/admin/src/components/auth/PasskeyLogin.tsx:166
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Preparing..."
msgstr ""
@@ -4264,7 +4270,7 @@ msgstr "Abschnitte"
msgid "secure context"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:506
+#: packages/admin/src/components/SetupWizard.tsx:572
msgid "Secure your account"
msgstr ""
@@ -4388,7 +4394,7 @@ msgstr ""
msgid "Send Invite"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
msgid "Send magic link"
msgstr "Magic Link senden"
@@ -4404,7 +4410,7 @@ msgstr ""
msgid "Send Test Email"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
#: packages/admin/src/components/settings/EmailSettings.tsx:162
#: packages/admin/src/components/SignupPage.tsx:87
#: packages/admin/src/components/SignupPage.tsx:150
@@ -4440,15 +4446,11 @@ msgstr ""
msgid "Set a custom display size for this image instance."
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:295
-msgid "Set up your passkey"
-msgstr ""
-
-#: packages/admin/src/components/SetupWizard.tsx:504
+#: packages/admin/src/components/SetupWizard.tsx:570
msgid "Set up your site"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:175
+#: packages/admin/src/components/SetupWizard.tsx:177
msgid "Setting up..."
msgstr ""
@@ -4496,24 +4498,35 @@ msgstr ""
msgid "Sign in"
msgstr ""
+#: packages/admin/src/components/SetupWizard.tsx:382
+msgid "Sign In"
+msgstr ""
+
#: packages/admin/src/components/SignupPage.tsx:269
msgid "Sign in instead"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:291
+#: packages/admin/src/components/LoginPage.tsx:240
msgid "Sign in to your site"
msgstr "Melde dich bei deiner Website an"
-#: packages/admin/src/components/LoginPage.tsx:292
+#. placeholder {0}: authProviderList.find((p) => p.id === activeProvider)?.label ?? activeProvider
+#. placeholder {0}: provider.label
+#: packages/admin/src/components/LoginPage.tsx:239
+#: packages/admin/src/components/SetupWizard.tsx:291
+msgid "Sign in with {0}"
+msgstr ""
+
+#: packages/admin/src/components/LoginPage.tsx:237
msgid "Sign in with email"
msgstr "Mit E-Mail anmelden"
-#: packages/admin/src/components/LoginPage.tsx:348
+#: packages/admin/src/components/LoginPage.tsx:298
msgid "Sign in with email link"
msgstr "Mit E-Mail-Link anmelden"
#: packages/admin/src/components/auth/PasskeyLogin.tsx:136
-#: packages/admin/src/components/LoginPage.tsx:312
+#: packages/admin/src/components/LoginPage.tsx:260
msgid "Sign in with Passkey"
msgstr "Mit Passkey anmelden"
@@ -4530,7 +4543,7 @@ msgstr ""
msgid "Single line text input"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:331
+#: packages/admin/src/components/SetupWizard.tsx:380
msgid "Site"
msgstr ""
@@ -4542,12 +4555,12 @@ msgstr ""
msgid "Site identity, logo, favicon, and reading preferences"
msgstr "Website-Identität, Logo, Favicon und Leseeinstellungen"
-#: packages/admin/src/components/SetupWizard.tsx:329
+#: packages/admin/src/components/SetupWizard.tsx:378
msgid "Site Settings"
msgstr ""
#: packages/admin/src/components/settings/GeneralSettings.tsx:156
-#: packages/admin/src/components/SetupWizard.tsx:141
+#: packages/admin/src/components/SetupWizard.tsx:143
msgid "Site Title"
msgstr ""
@@ -4555,7 +4568,7 @@ msgstr ""
msgid "Site title & tagline"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:125
+#: packages/admin/src/components/SetupWizard.tsx:127
msgid "Site title is required"
msgstr ""
@@ -4699,7 +4712,7 @@ msgid "System ({resolvedLabel})"
msgstr "System ({resolvedLabel})"
#: packages/admin/src/components/settings/GeneralSettings.tsx:162
-#: packages/admin/src/components/SetupWizard.tsx:152
+#: packages/admin/src/components/SetupWizard.tsx:154
msgid "Tagline"
msgstr ""
@@ -4737,7 +4750,7 @@ msgstr ""
msgid "Taxonomy not found:"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:180
+#: packages/admin/src/components/SetupWizard.tsx:182
msgid "Template:"
msgstr ""
@@ -4770,7 +4783,7 @@ msgstr ""
msgid "The invited user will have this role once they complete registration."
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:170
+#: packages/admin/src/components/LoginPage.tsx:113
#: packages/admin/src/components/SignupPage.tsx:138
msgid "The link will expire in 15 minutes."
msgstr "Der Link ist 15 Minuten gültig."
@@ -4895,7 +4908,7 @@ msgstr ""
msgid "This will revert to the published version. Your draft changes will be lost."
msgstr "Dies setzt alles auf die veröffentlichte Version zurück. Deine Entwurfsänderungen gehen verloren."
-#: packages/admin/src/components/SetupWizard.tsx:156
+#: packages/admin/src/components/SetupWizard.tsx:158
msgid "Thoughts, tutorials, and more"
msgstr ""
@@ -5261,7 +5274,7 @@ msgstr ""
msgid "Use your device's biometric authentication, security key, or PIN to sign in."
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:359
+#: packages/admin/src/components/LoginPage.tsx:334
msgid "Use your registered passkey to sign in securely."
msgstr "Verwende deinen registrierten Passkey, um dich sicher anzumelden."
@@ -5379,7 +5392,7 @@ msgstr ""
msgid "We'll check what import options are available for your site."
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:360
+#: packages/admin/src/components/LoginPage.tsx:331
msgid "We'll send you a link to sign in without a password."
msgstr "Wir schicken dir einen Link, um dich ohne Passwort anzumelden."
@@ -5516,7 +5529,7 @@ msgstr ""
msgid "You'll be signing up as"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:509
+#: packages/admin/src/components/SetupWizard.tsx:575
msgid "You're signed in via Cloudflare Access"
msgstr ""
@@ -5525,7 +5538,7 @@ msgid "you@company.com"
msgstr ""
#: packages/admin/src/components/auth/PasskeyLogin.tsx:336
-#: packages/admin/src/components/SetupWizard.tsx:226
+#: packages/admin/src/components/SetupWizard.tsx:228
msgid "you@example.com"
msgstr ""
@@ -5543,7 +5556,7 @@ msgstr ""
msgid "Your device doesn't support the required security features."
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:222
+#: packages/admin/src/components/SetupWizard.tsx:224
msgid "Your Email"
msgstr ""
@@ -5563,7 +5576,7 @@ msgstr ""
msgid "Your LinkedIn profile username"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:234
+#: packages/admin/src/components/SetupWizard.tsx:236
msgid "Your Name"
msgstr ""
diff --git a/packages/admin/src/locales/en/messages.po b/packages/admin/src/locales/en/messages.po
index b39e490d7..f06d067bf 100644
--- a/packages/admin/src/locales/en/messages.po
+++ b/packages/admin/src/locales/en/messages.po
@@ -59,7 +59,7 @@ msgid "{0, plural, one {(# item)} other {(# items)}}"
msgstr "{0, plural, one {(# item)} other {(# items)}}"
#. placeholder {0}: seedInfo.collections
-#: packages/admin/src/components/SetupWizard.tsx:181
+#: packages/admin/src/components/SetupWizard.tsx:183
msgid "{0, plural, one {# collection} other {# collections}}"
msgstr "{0, plural, one {# collection} other {# collections}}"
@@ -315,8 +315,9 @@ msgstr "• Uploaded to your EmDash media storage"
msgid "• URLs in your content are updated automatically"
msgstr "• URLs in your content are updated automatically"
-#: packages/admin/src/components/SetupWizard.tsx:250
-#: packages/admin/src/components/SetupWizard.tsx:310
+#: packages/admin/src/components/SetupWizard.tsx:252
+#: packages/admin/src/components/SetupWizard.tsx:304
+#: packages/admin/src/components/SetupWizard.tsx:359
#: packages/admin/src/components/WordPressImport.tsx:1431
msgid "← Back"
msgstr "← Back"
@@ -409,7 +410,7 @@ msgstr "Accept & Install"
msgid "Accept & Update"
msgstr "Accept & Update"
-#: packages/admin/src/components/SetupWizard.tsx:332
+#: packages/admin/src/components/SetupWizard.tsx:381
msgid "Account"
msgstr "Account"
@@ -694,7 +695,7 @@ msgstr "Assign WordPress authors to EmDash users. Posts will be attributed to th
msgid "Authentication error: {0}"
msgstr "Authentication error: {0}"
-#: packages/admin/src/components/LoginPage.tsx:253
+#: packages/admin/src/components/LoginPage.tsx:195
msgid "Authentication error: {error}"
msgstr "Authentication error: {error}"
@@ -764,8 +765,9 @@ msgstr "Back"
msgid "Back to {collectionLabel} list"
msgstr "Back to {collectionLabel} list"
-#: packages/admin/src/components/LoginPage.tsx:174
-#: packages/admin/src/components/LoginPage.tsx:210
+#: packages/admin/src/components/LoginPage.tsx:117
+#: packages/admin/src/components/LoginPage.tsx:153
+#: packages/admin/src/components/LoginPage.tsx:319
#: packages/admin/src/components/SignupPage.tsx:280
msgid "Back to login"
msgstr "Back to login"
@@ -957,7 +959,7 @@ msgstr "Check for updates"
msgid "Check Site"
msgstr "Check Site"
-#: packages/admin/src/components/LoginPage.tsx:158
+#: packages/admin/src/components/LoginPage.tsx:101
#: packages/admin/src/components/SignupPage.tsx:129
#: packages/admin/src/components/SignupPage.tsx:401
msgid "Check your email"
@@ -971,6 +973,10 @@ msgstr "Checking {urlInput}..."
msgid "Checking authentication..."
msgstr "Checking authentication..."
+#: packages/admin/src/components/SetupWizard.tsx:315
+msgid "Choose how to sign in"
+msgstr "Choose how to sign in"
+
#: packages/admin/src/components/Settings.tsx:130
msgid "Choose your preferred admin language"
msgstr "Choose your preferred admin language"
@@ -979,7 +985,7 @@ msgstr "Choose your preferred admin language"
msgid "Click the link in the email to continue setting up your account."
msgstr "Click the link in the email to continue setting up your account."
-#: packages/admin/src/components/LoginPage.tsx:169
+#: packages/admin/src/components/LoginPage.tsx:112
msgid "Click the link in the email to sign in."
msgstr "Click the link in the email to sign in."
@@ -1185,8 +1191,8 @@ msgstr "Content Write"
msgid "Continue"
msgstr "Continue"
-#: packages/admin/src/components/SetupWizard.tsx:175
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:177
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Continue →"
msgstr "Continue →"
@@ -1291,7 +1297,7 @@ msgstr "Create New Token"
msgid "Create one in WordPress: Users → Profile → Application Passwords"
msgstr "Create one in WordPress: Users → Profile → Application Passwords"
-#: packages/admin/src/components/SetupWizard.tsx:305
+#: packages/admin/src/components/SetupWizard.tsx:325
msgid "Create Passkey"
msgstr "Create Passkey"
@@ -1336,7 +1342,7 @@ msgstr "Create Taxonomy"
msgid "Create Token"
msgstr "Create Token"
-#: packages/admin/src/components/SetupWizard.tsx:505
+#: packages/admin/src/components/SetupWizard.tsx:571
msgid "Create your account"
msgstr "Create your account"
@@ -1724,7 +1730,7 @@ msgstr "Domain removed"
msgid "Domain updated"
msgstr "Domain updated"
-#: packages/admin/src/components/LoginPage.tsx:366
+#: packages/admin/src/components/LoginPage.tsx:340
msgid "Don't have an account? <0>Sign up0>"
msgstr "Don't have an account? <0>Sign up0>"
@@ -1853,13 +1859,13 @@ msgstr "Email"
msgid "Email (optional)"
msgstr "Email (optional)"
-#: packages/admin/src/components/LoginPage.tsx:183
+#: packages/admin/src/components/LoginPage.tsx:126
#: packages/admin/src/components/SignupPage.tsx:65
#: packages/admin/src/components/users/InviteUserModal.tsx:154
msgid "Email address"
msgstr "Email address"
-#: packages/admin/src/components/SetupWizard.tsx:204
+#: packages/admin/src/components/SetupWizard.tsx:206
#: packages/admin/src/components/SignupPage.tsx:48
msgid "Email is required"
msgstr "Email is required"
@@ -1965,6 +1971,10 @@ msgstr "Enter name"
msgid "Enter the code from your terminal"
msgstr "Enter the code from your terminal"
+#: packages/admin/src/components/LoginPage.tsx:333
+msgid "Enter your handle to sign in."
+msgstr "Enter your handle to sign in."
+
#: packages/admin/src/components/WordPressImport.tsx:1289
msgid "Enter your WordPress credentials to import content directly."
msgstr "Enter your WordPress credentials to import content directly."
@@ -1975,7 +1985,7 @@ msgstr "Enter your WordPress site URL"
#: packages/admin/src/components/MenuEditor.tsx:83
#: packages/admin/src/components/MenuEditor.tsx:122
-#: packages/admin/src/components/SetupWizard.tsx:484
+#: packages/admin/src/components/SetupWizard.tsx:550
msgid "Error"
msgstr "Error"
@@ -2089,7 +2099,7 @@ msgstr "Failed to load plugins: {0}"
msgid "Failed to load revisions"
msgstr "Failed to load revisions"
-#: packages/admin/src/components/SetupWizard.tsx:486
+#: packages/admin/src/components/SetupWizard.tsx:552
msgid "Failed to load setup"
msgstr "Failed to load setup"
@@ -2120,8 +2130,8 @@ msgstr "Failed to save"
msgid "Failed to save settings"
msgstr "Failed to save settings"
-#: packages/admin/src/components/LoginPage.tsx:127
-#: packages/admin/src/components/LoginPage.tsx:132
+#: packages/admin/src/components/LoginPage.tsx:70
+#: packages/admin/src/components/LoginPage.tsx:75
msgid "Failed to send magic link"
msgstr "Failed to send magic link"
@@ -2328,7 +2338,7 @@ msgstr "Icon blurred due to image audit"
msgid "ID"
msgstr "ID"
-#: packages/admin/src/components/LoginPage.tsx:160
+#: packages/admin/src/components/LoginPage.tsx:103
msgid "If an account exists for <0>{email}0>, we've sent a sign-in link."
msgstr "If an account exists for <0>{email}0>, we've sent a sign-in link."
@@ -2448,7 +2458,7 @@ msgstr "Importing content..."
msgid "Importing Media"
msgstr "Importing Media"
-#: packages/admin/src/components/SetupWizard.tsx:163
+#: packages/admin/src/components/SetupWizard.tsx:165
msgid "Include sample content (recommended for new sites)"
msgstr "Include sample content (recommended for new sites)"
@@ -2561,7 +2571,7 @@ msgstr "Item deleted"
msgid "Item updated"
msgstr "Item updated"
-#: packages/admin/src/components/SetupWizard.tsx:238
+#: packages/admin/src/components/SetupWizard.tsx:240
#: packages/admin/src/components/SignupPage.tsx:204
msgid "Jane Doe"
msgstr "Jane Doe"
@@ -2585,7 +2595,7 @@ msgstr "Keywords"
msgid "Label"
msgstr "Label"
-#: packages/admin/src/components/LoginPage.tsx:379
+#: packages/admin/src/components/LoginPage.tsx:353
#: packages/admin/src/components/Settings.tsx:129
#: packages/admin/src/components/Settings.tsx:134
msgid "Language"
@@ -2725,7 +2735,7 @@ msgstr "Loading sections..."
msgid "Loading settings..."
msgstr "Loading settings..."
-#: packages/admin/src/components/SetupWizard.tsx:473
+#: packages/admin/src/components/SetupWizard.tsx:539
msgid "Loading setup..."
msgstr "Loading setup..."
@@ -3021,7 +3031,7 @@ msgstr "Multi-line plain text"
msgid "Multiple choices from options"
msgstr "Multiple choices from options"
-#: packages/admin/src/components/SetupWizard.tsx:145
+#: packages/admin/src/components/SetupWizard.tsx:147
msgid "My Awesome Blog"
msgstr "My Awesome Blog"
@@ -3403,7 +3413,7 @@ msgstr "or choose from library"
msgid "Or click to browse. Accepts .xml files exported from WordPress."
msgstr "Or click to browse. Accepts .xml files exported from WordPress."
-#: packages/admin/src/components/LoginPage.tsx:321
+#: packages/admin/src/components/LoginPage.tsx:269
msgid "Or continue with"
msgstr "Or continue with"
@@ -3462,10 +3472,6 @@ msgstr "Part of a redirect loop"
msgid "Pass"
msgstr "Pass"
-#: packages/admin/src/components/SetupWizard.tsx:333
-msgid "Passkey"
-msgstr "Passkey"
-
#: packages/admin/src/components/settings/SecuritySettings.tsx:98
msgid "Passkey added successfully"
msgstr "Passkey added successfully"
@@ -3507,10 +3513,6 @@ msgstr "Passkeys are a secure, passwordless way to sign in to your account. You
msgid "Passkeys are a secure, passwordless way to sign in using your device's biometrics, PIN, or security key."
msgstr "Passkeys are a secure, passwordless way to sign in using your device's biometrics, PIN, or security key."
-#: packages/admin/src/components/SetupWizard.tsx:297
-msgid "Passkeys are more secure than passwords. You'll use your device's biometrics, PIN, or security key to sign in."
-msgstr "Passkeys are more secure than passwords. You'll use your device's biometrics, PIN, or security key to sign in."
-
#: packages/admin/src/components/auth/PasskeyLogin.tsx:303
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:300
msgid "Passkeys Not Available Here"
@@ -3558,12 +3560,16 @@ msgstr "Permanently delete {title}"
msgid "Permissions"
msgstr "Permissions"
+#: packages/admin/src/components/SetupWizard.tsx:317
+msgid "Pick any method to create your admin account."
+msgstr "Pick any method to create your admin account."
+
#: packages/admin/src/components/auth/PasskeyLogin.tsx:313
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:310
msgid "Plain"
msgstr "Plain"
-#: packages/admin/src/components/SetupWizard.tsx:206
+#: packages/admin/src/components/SetupWizard.tsx:208
msgid "Please enter a valid email"
msgstr "Please enter a valid email"
@@ -3637,7 +3643,7 @@ msgid "Preparing to download files from WordPress..."
msgstr "Preparing to download files from WordPress..."
#: packages/admin/src/components/auth/PasskeyLogin.tsx:166
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Preparing..."
msgstr "Preparing..."
@@ -4264,7 +4270,7 @@ msgstr "Sections"
msgid "secure context"
msgstr "secure context"
-#: packages/admin/src/components/SetupWizard.tsx:506
+#: packages/admin/src/components/SetupWizard.tsx:572
msgid "Secure your account"
msgstr "Secure your account"
@@ -4388,7 +4394,7 @@ msgstr "Send an invitation email to a new team member."
msgid "Send Invite"
msgstr "Send Invite"
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
msgid "Send magic link"
msgstr "Send magic link"
@@ -4404,7 +4410,7 @@ msgstr "Send Test"
msgid "Send Test Email"
msgstr "Send Test Email"
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
#: packages/admin/src/components/settings/EmailSettings.tsx:162
#: packages/admin/src/components/SignupPage.tsx:87
#: packages/admin/src/components/SignupPage.tsx:150
@@ -4440,15 +4446,11 @@ msgstr "SEO Title"
msgid "Set a custom display size for this image instance."
msgstr "Set a custom display size for this image instance."
-#: packages/admin/src/components/SetupWizard.tsx:295
-msgid "Set up your passkey"
-msgstr "Set up your passkey"
-
-#: packages/admin/src/components/SetupWizard.tsx:504
+#: packages/admin/src/components/SetupWizard.tsx:570
msgid "Set up your site"
msgstr "Set up your site"
-#: packages/admin/src/components/SetupWizard.tsx:175
+#: packages/admin/src/components/SetupWizard.tsx:177
msgid "Setting up..."
msgstr "Setting up..."
@@ -4496,24 +4498,35 @@ msgstr "Shown when hovering over the image."
msgid "Sign in"
msgstr "Sign in"
+#: packages/admin/src/components/SetupWizard.tsx:382
+msgid "Sign In"
+msgstr "Sign In"
+
#: packages/admin/src/components/SignupPage.tsx:269
msgid "Sign in instead"
msgstr "Sign in instead"
-#: packages/admin/src/components/LoginPage.tsx:291
+#: packages/admin/src/components/LoginPage.tsx:240
msgid "Sign in to your site"
msgstr "Sign in to your site"
-#: packages/admin/src/components/LoginPage.tsx:292
+#. placeholder {0}: authProviderList.find((p) => p.id === activeProvider)?.label ?? activeProvider
+#. placeholder {0}: provider.label
+#: packages/admin/src/components/LoginPage.tsx:239
+#: packages/admin/src/components/SetupWizard.tsx:291
+msgid "Sign in with {0}"
+msgstr "Sign in with {0}"
+
+#: packages/admin/src/components/LoginPage.tsx:237
msgid "Sign in with email"
msgstr "Sign in with email"
-#: packages/admin/src/components/LoginPage.tsx:348
+#: packages/admin/src/components/LoginPage.tsx:298
msgid "Sign in with email link"
msgstr "Sign in with email link"
#: packages/admin/src/components/auth/PasskeyLogin.tsx:136
-#: packages/admin/src/components/LoginPage.tsx:312
+#: packages/admin/src/components/LoginPage.tsx:260
msgid "Sign in with Passkey"
msgstr "Sign in with Passkey"
@@ -4530,7 +4543,7 @@ msgstr "Single choice from options"
msgid "Single line text input"
msgstr "Single line text input"
-#: packages/admin/src/components/SetupWizard.tsx:331
+#: packages/admin/src/components/SetupWizard.tsx:380
msgid "Site"
msgstr "Site"
@@ -4542,12 +4555,12 @@ msgstr "Site Identity"
msgid "Site identity, logo, favicon, and reading preferences"
msgstr "Site identity, logo, favicon, and reading preferences"
-#: packages/admin/src/components/SetupWizard.tsx:329
+#: packages/admin/src/components/SetupWizard.tsx:378
msgid "Site Settings"
msgstr "Site Settings"
#: packages/admin/src/components/settings/GeneralSettings.tsx:156
-#: packages/admin/src/components/SetupWizard.tsx:141
+#: packages/admin/src/components/SetupWizard.tsx:143
msgid "Site Title"
msgstr "Site Title"
@@ -4555,7 +4568,7 @@ msgstr "Site Title"
msgid "Site title & tagline"
msgstr "Site title & tagline"
-#: packages/admin/src/components/SetupWizard.tsx:125
+#: packages/admin/src/components/SetupWizard.tsx:127
msgid "Site title is required"
msgstr "Site title is required"
@@ -4699,7 +4712,7 @@ msgid "System ({resolvedLabel})"
msgstr "System ({resolvedLabel})"
#: packages/admin/src/components/settings/GeneralSettings.tsx:162
-#: packages/admin/src/components/SetupWizard.tsx:152
+#: packages/admin/src/components/SetupWizard.tsx:154
msgid "Tagline"
msgstr "Tagline"
@@ -4737,7 +4750,7 @@ msgstr "Taxonomy created"
msgid "Taxonomy not found:"
msgstr "Taxonomy not found:"
-#: packages/admin/src/components/SetupWizard.tsx:180
+#: packages/admin/src/components/SetupWizard.tsx:182
msgid "Template:"
msgstr "Template:"
@@ -4770,7 +4783,7 @@ msgstr "The following tables contain content but aren't registered as collection
msgid "The invited user will have this role once they complete registration."
msgstr "The invited user will have this role once they complete registration."
-#: packages/admin/src/components/LoginPage.tsx:170
+#: packages/admin/src/components/LoginPage.tsx:113
#: packages/admin/src/components/SignupPage.tsx:138
msgid "The link will expire in 15 minutes."
msgstr "The link will expire in 15 minutes."
@@ -4895,7 +4908,7 @@ msgstr "This will remove the plugin and its bundle from your site."
msgid "This will revert to the published version. Your draft changes will be lost."
msgstr "This will revert to the published version. Your draft changes will be lost."
-#: packages/admin/src/components/SetupWizard.tsx:156
+#: packages/admin/src/components/SetupWizard.tsx:158
msgid "Thoughts, tutorials, and more"
msgstr "Thoughts, tutorials, and more"
@@ -5261,7 +5274,7 @@ msgstr "Use [param] or [...rest] in paths for pattern matching."
msgid "Use your device's biometric authentication, security key, or PIN to sign in."
msgstr "Use your device's biometric authentication, security key, or PIN to sign in."
-#: packages/admin/src/components/LoginPage.tsx:359
+#: packages/admin/src/components/LoginPage.tsx:334
msgid "Use your registered passkey to sign in securely."
msgstr "Use your registered passkey to sign in securely."
@@ -5379,7 +5392,7 @@ msgstr "We couldn't connect to a WordPress site at {0}. This could mean the site
msgid "We'll check what import options are available for your site."
msgstr "We'll check what import options are available for your site."
-#: packages/admin/src/components/LoginPage.tsx:360
+#: packages/admin/src/components/LoginPage.tsx:331
msgid "We'll send you a link to sign in without a password."
msgstr "We'll send you a link to sign in without a password."
@@ -5516,7 +5529,7 @@ msgstr "You'll be redirected to WordPress to authorize the connection."
msgid "You'll be signing up as"
msgstr "You'll be signing up as"
-#: packages/admin/src/components/SetupWizard.tsx:509
+#: packages/admin/src/components/SetupWizard.tsx:575
msgid "You're signed in via Cloudflare Access"
msgstr "You're signed in via Cloudflare Access"
@@ -5525,7 +5538,7 @@ msgid "you@company.com"
msgstr "you@company.com"
#: packages/admin/src/components/auth/PasskeyLogin.tsx:336
-#: packages/admin/src/components/SetupWizard.tsx:226
+#: packages/admin/src/components/SetupWizard.tsx:228
msgid "you@example.com"
msgstr "you@example.com"
@@ -5543,7 +5556,7 @@ msgstr "Your browser doesn't support passkeys. Please use a modern browser like
msgid "Your device doesn't support the required security features."
msgstr "Your device doesn't support the required security features."
-#: packages/admin/src/components/SetupWizard.tsx:222
+#: packages/admin/src/components/SetupWizard.tsx:224
msgid "Your Email"
msgstr "Your Email"
@@ -5563,7 +5576,7 @@ msgstr "Your Instagram username"
msgid "Your LinkedIn profile username"
msgstr "Your LinkedIn profile username"
-#: packages/admin/src/components/SetupWizard.tsx:234
+#: packages/admin/src/components/SetupWizard.tsx:236
msgid "Your Name"
msgstr "Your Name"
diff --git a/packages/admin/src/locales/es-419/messages.po b/packages/admin/src/locales/es-419/messages.po
index 17e4d3fa2..0355745c7 100644
--- a/packages/admin/src/locales/es-419/messages.po
+++ b/packages/admin/src/locales/es-419/messages.po
@@ -59,7 +59,7 @@ msgid "{0, plural, one {(# item)} other {(# items)}}"
msgstr "{0, plural, one {(# elemento)} other {(# elementos)}}"
#. placeholder {0}: seedInfo.collections
-#: packages/admin/src/components/SetupWizard.tsx:181
+#: packages/admin/src/components/SetupWizard.tsx:183
msgid "{0, plural, one {# collection} other {# collections}}"
msgstr "{0, plural, one {# colección} other {# colecciones}}"
@@ -315,8 +315,9 @@ msgstr "• Subido a su almacenamiento multimedia EmDash"
msgid "• URLs in your content are updated automatically"
msgstr "• Las URL de su contenido se actualizan automáticamente"
-#: packages/admin/src/components/SetupWizard.tsx:250
-#: packages/admin/src/components/SetupWizard.tsx:310
+#: packages/admin/src/components/SetupWizard.tsx:252
+#: packages/admin/src/components/SetupWizard.tsx:304
+#: packages/admin/src/components/SetupWizard.tsx:359
#: packages/admin/src/components/WordPressImport.tsx:1431
msgid "← Back"
msgstr "← Volver"
@@ -409,7 +410,7 @@ msgstr "Aceptar e instalar"
msgid "Accept & Update"
msgstr "Aceptar y actualizar"
-#: packages/admin/src/components/SetupWizard.tsx:332
+#: packages/admin/src/components/SetupWizard.tsx:381
msgid "Account"
msgstr "Cuenta"
@@ -694,7 +695,7 @@ msgstr "Asigne autores de WordPress a usuarios de EmDash. Las entradas se atribu
msgid "Authentication error: {0}"
msgstr "Error de autenticación: {0}"
-#: packages/admin/src/components/LoginPage.tsx:253
+#: packages/admin/src/components/LoginPage.tsx:195
msgid "Authentication error: {error}"
msgstr "Error de autenticación: {error}"
@@ -764,8 +765,9 @@ msgstr "Atrás"
msgid "Back to {collectionLabel} list"
msgstr "Volver a la lista {collectionLabel}"
-#: packages/admin/src/components/LoginPage.tsx:174
-#: packages/admin/src/components/LoginPage.tsx:210
+#: packages/admin/src/components/LoginPage.tsx:117
+#: packages/admin/src/components/LoginPage.tsx:153
+#: packages/admin/src/components/LoginPage.tsx:319
#: packages/admin/src/components/SignupPage.tsx:280
msgid "Back to login"
msgstr "Volver a iniciar sesión"
@@ -957,7 +959,7 @@ msgstr "Buscar actualizaciones"
msgid "Check Site"
msgstr "Verificar sitio"
-#: packages/admin/src/components/LoginPage.tsx:158
+#: packages/admin/src/components/LoginPage.tsx:101
#: packages/admin/src/components/SignupPage.tsx:129
#: packages/admin/src/components/SignupPage.tsx:401
msgid "Check your email"
@@ -971,6 +973,10 @@ msgstr "Comprobando {urlInput}..."
msgid "Checking authentication..."
msgstr "Comprobando autenticación..."
+#: packages/admin/src/components/SetupWizard.tsx:315
+msgid "Choose how to sign in"
+msgstr ""
+
#: packages/admin/src/components/Settings.tsx:130
msgid "Choose your preferred admin language"
msgstr "Elija su idioma de administrador preferido"
@@ -979,7 +985,7 @@ msgstr "Elija su idioma de administrador preferido"
msgid "Click the link in the email to continue setting up your account."
msgstr "Haga clic en el enlace del correo electrónico para continuar configurando su cuenta."
-#: packages/admin/src/components/LoginPage.tsx:169
+#: packages/admin/src/components/LoginPage.tsx:112
msgid "Click the link in the email to sign in."
msgstr "Haga clic en el enlace del correo electrónico para iniciar sesión."
@@ -1185,8 +1191,8 @@ msgstr "Escritura de contenido"
msgid "Continue"
msgstr "Continuar"
-#: packages/admin/src/components/SetupWizard.tsx:175
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:177
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Continue →"
msgstr "Continuar →"
@@ -1291,7 +1297,7 @@ msgstr "Crear nuevo token"
msgid "Create one in WordPress: Users → Profile → Application Passwords"
msgstr "Cree uno en WordPress: Usuarios → Perfil → Contraseñas de aplicación"
-#: packages/admin/src/components/SetupWizard.tsx:305
+#: packages/admin/src/components/SetupWizard.tsx:325
msgid "Create Passkey"
msgstr "Crear Passkey"
@@ -1336,7 +1342,7 @@ msgstr "Crear taxonomía"
msgid "Create Token"
msgstr "Crear token"
-#: packages/admin/src/components/SetupWizard.tsx:505
+#: packages/admin/src/components/SetupWizard.tsx:571
msgid "Create your account"
msgstr "Crea tu cuenta"
@@ -1724,7 +1730,7 @@ msgstr "Dominio eliminado"
msgid "Domain updated"
msgstr "Dominio actualizado"
-#: packages/admin/src/components/LoginPage.tsx:366
+#: packages/admin/src/components/LoginPage.tsx:340
msgid "Don't have an account? <0>Sign up0>"
msgstr "¿No tienes una cuenta? <0>Registrarse0>"
@@ -1853,13 +1859,13 @@ msgstr "Correo electrónico"
msgid "Email (optional)"
msgstr "Correo electrónico (opcional)"
-#: packages/admin/src/components/LoginPage.tsx:183
+#: packages/admin/src/components/LoginPage.tsx:126
#: packages/admin/src/components/SignupPage.tsx:65
#: packages/admin/src/components/users/InviteUserModal.tsx:154
msgid "Email address"
msgstr "Dirección de correo electrónico"
-#: packages/admin/src/components/SetupWizard.tsx:204
+#: packages/admin/src/components/SetupWizard.tsx:206
#: packages/admin/src/components/SignupPage.tsx:48
msgid "Email is required"
msgstr "Se requiere correo electrónico"
@@ -1965,6 +1971,10 @@ msgstr "Introduce el nombre"
msgid "Enter the code from your terminal"
msgstr "Introduce el código desde tu terminal"
+#: packages/admin/src/components/LoginPage.tsx:333
+msgid "Enter your handle to sign in."
+msgstr ""
+
#: packages/admin/src/components/WordPressImport.tsx:1289
msgid "Enter your WordPress credentials to import content directly."
msgstr "Ingrese sus credenciales de WordPress para importar contenido directamente."
@@ -1975,7 +1985,7 @@ msgstr "Ingrese la URL de su sitio de WordPress"
#: packages/admin/src/components/MenuEditor.tsx:83
#: packages/admin/src/components/MenuEditor.tsx:122
-#: packages/admin/src/components/SetupWizard.tsx:484
+#: packages/admin/src/components/SetupWizard.tsx:550
msgid "Error"
msgstr "Error"
@@ -2089,7 +2099,7 @@ msgstr "No se pudieron cargar los plugins: {0}"
msgid "Failed to load revisions"
msgstr "No se pudieron cargar las revisiones"
-#: packages/admin/src/components/SetupWizard.tsx:486
+#: packages/admin/src/components/SetupWizard.tsx:552
msgid "Failed to load setup"
msgstr "No se pudo cargar la configuración"
@@ -2120,8 +2130,8 @@ msgstr ""
msgid "Failed to save settings"
msgstr "No se pudo guardar la configuración"
-#: packages/admin/src/components/LoginPage.tsx:127
-#: packages/admin/src/components/LoginPage.tsx:132
+#: packages/admin/src/components/LoginPage.tsx:70
+#: packages/admin/src/components/LoginPage.tsx:75
msgid "Failed to send magic link"
msgstr "No se pudo enviar el enlace mágico"
@@ -2328,7 +2338,7 @@ msgstr "Icono borroso debido a auditoría de imagen"
msgid "ID"
msgstr "IDENTIFICACIÓN"
-#: packages/admin/src/components/LoginPage.tsx:160
+#: packages/admin/src/components/LoginPage.tsx:103
msgid "If an account exists for <0>{email}0>, we've sent a sign-in link."
msgstr "Si existe una cuenta para <0>{email}0>, le enviamos un enlace de inicio de sesión."
@@ -2448,7 +2458,7 @@ msgstr "Importando contenido..."
msgid "Importing Media"
msgstr "Importación de medios"
-#: packages/admin/src/components/SetupWizard.tsx:163
+#: packages/admin/src/components/SetupWizard.tsx:165
msgid "Include sample content (recommended for new sites)"
msgstr "Incluir contenido de muestra (recomendado para sitios nuevos)"
@@ -2561,7 +2571,7 @@ msgstr "Artículo eliminado"
msgid "Item updated"
msgstr "Artículo actualizado"
-#: packages/admin/src/components/SetupWizard.tsx:238
+#: packages/admin/src/components/SetupWizard.tsx:240
#: packages/admin/src/components/SignupPage.tsx:204
msgid "Jane Doe"
msgstr "Jane Doe"
@@ -2585,7 +2595,7 @@ msgstr "Palabras clave"
msgid "Label"
msgstr "Etiqueta"
-#: packages/admin/src/components/LoginPage.tsx:379
+#: packages/admin/src/components/LoginPage.tsx:353
#: packages/admin/src/components/Settings.tsx:129
#: packages/admin/src/components/Settings.tsx:134
msgid "Language"
@@ -2725,7 +2735,7 @@ msgstr "Cargando secciones..."
msgid "Loading settings..."
msgstr "Cargando configuración..."
-#: packages/admin/src/components/SetupWizard.tsx:473
+#: packages/admin/src/components/SetupWizard.tsx:539
msgid "Loading setup..."
msgstr "Cargando configuración..."
@@ -3021,7 +3031,7 @@ msgstr "Texto sin formato de varias líneas"
msgid "Multiple choices from options"
msgstr "Múltiples opciones de opciones"
-#: packages/admin/src/components/SetupWizard.tsx:145
+#: packages/admin/src/components/SetupWizard.tsx:147
msgid "My Awesome Blog"
msgstr "Mi blog impresionante"
@@ -3403,7 +3413,7 @@ msgstr "o elegir de la biblioteca"
msgid "Or click to browse. Accepts .xml files exported from WordPress."
msgstr "O haga clic para navegar. Acepta archivos .xml exportados desde WordPress."
-#: packages/admin/src/components/LoginPage.tsx:321
+#: packages/admin/src/components/LoginPage.tsx:269
msgid "Or continue with"
msgstr "O continuar con"
@@ -3462,10 +3472,6 @@ msgstr "Parte de un bucle de redirección"
msgid "Pass"
msgstr "Aprobar"
-#: packages/admin/src/components/SetupWizard.tsx:333
-msgid "Passkey"
-msgstr "Passkey"
-
#: packages/admin/src/components/settings/SecuritySettings.tsx:98
msgid "Passkey added successfully"
msgstr "Passkey agregada exitosamente"
@@ -3507,10 +3513,6 @@ msgstr "Las claves de acceso son una forma segura y sin contraseña de iniciar s
msgid "Passkeys are a secure, passwordless way to sign in using your device's biometrics, PIN, or security key."
msgstr "Las claves de acceso son una forma segura y sin contraseña de iniciar sesión utilizando los datos biométricos, el PIN o la clave de seguridad de su dispositivo."
-#: packages/admin/src/components/SetupWizard.tsx:297
-msgid "Passkeys are more secure than passwords. You'll use your device's biometrics, PIN, or security key to sign in."
-msgstr "Las claves de acceso son más seguras que las contraseñas. Utilizará los datos biométricos, el PIN o la clave de seguridad de su dispositivo para iniciar sesión."
-
#: packages/admin/src/components/auth/PasskeyLogin.tsx:303
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:300
msgid "Passkeys Not Available Here"
@@ -3558,12 +3560,16 @@ msgstr "Eliminar permanentemente {title}"
msgid "Permissions"
msgstr "Permisos"
+#: packages/admin/src/components/SetupWizard.tsx:317
+msgid "Pick any method to create your admin account."
+msgstr ""
+
#: packages/admin/src/components/auth/PasskeyLogin.tsx:313
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:310
msgid "Plain"
msgstr "Plano"
-#: packages/admin/src/components/SetupWizard.tsx:206
+#: packages/admin/src/components/SetupWizard.tsx:208
msgid "Please enter a valid email"
msgstr "Por favor introduce un correo electrónico válido"
@@ -3637,7 +3643,7 @@ msgid "Preparing to download files from WordPress..."
msgstr "Preparándose para descargar archivos de WordPress..."
#: packages/admin/src/components/auth/PasskeyLogin.tsx:166
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Preparing..."
msgstr "Preparante..."
@@ -4264,7 +4270,7 @@ msgstr "Secciones"
msgid "secure context"
msgstr "contexto seguro"
-#: packages/admin/src/components/SetupWizard.tsx:506
+#: packages/admin/src/components/SetupWizard.tsx:572
msgid "Secure your account"
msgstr "Asegure su cuenta"
@@ -4388,7 +4394,7 @@ msgstr "Envíe un correo electrónico de invitación a un nuevo miembro del equi
msgid "Send Invite"
msgstr "Enviar invitación"
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
msgid "Send magic link"
msgstr "Enviar enlace mágico"
@@ -4404,7 +4410,7 @@ msgstr "Enviar prueba"
msgid "Send Test Email"
msgstr "Enviar correo electrónico de prueba"
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
#: packages/admin/src/components/settings/EmailSettings.tsx:162
#: packages/admin/src/components/SignupPage.tsx:87
#: packages/admin/src/components/SignupPage.tsx:150
@@ -4440,15 +4446,11 @@ msgstr "Título SEO"
msgid "Set a custom display size for this image instance."
msgstr "Establezca un tamaño de visualización personalizado para esta instancia de imagen."
-#: packages/admin/src/components/SetupWizard.tsx:295
-msgid "Set up your passkey"
-msgstr "Configura tu Passkey"
-
-#: packages/admin/src/components/SetupWizard.tsx:504
+#: packages/admin/src/components/SetupWizard.tsx:570
msgid "Set up your site"
msgstr "Configura tu sitio"
-#: packages/admin/src/components/SetupWizard.tsx:175
+#: packages/admin/src/components/SetupWizard.tsx:177
msgid "Setting up..."
msgstr "Configurando..."
@@ -4496,24 +4498,35 @@ msgstr "Se muestra al pasar el cursor sobre la imagen."
msgid "Sign in"
msgstr "Iniciar sesión"
+#: packages/admin/src/components/SetupWizard.tsx:382
+msgid "Sign In"
+msgstr ""
+
#: packages/admin/src/components/SignupPage.tsx:269
msgid "Sign in instead"
msgstr "Inicia sesión en su lugar"
-#: packages/admin/src/components/LoginPage.tsx:291
+#: packages/admin/src/components/LoginPage.tsx:240
msgid "Sign in to your site"
msgstr "Inicia sesión en tu sitio"
-#: packages/admin/src/components/LoginPage.tsx:292
+#. placeholder {0}: authProviderList.find((p) => p.id === activeProvider)?.label ?? activeProvider
+#. placeholder {0}: provider.label
+#: packages/admin/src/components/LoginPage.tsx:239
+#: packages/admin/src/components/SetupWizard.tsx:291
+msgid "Sign in with {0}"
+msgstr ""
+
+#: packages/admin/src/components/LoginPage.tsx:237
msgid "Sign in with email"
msgstr "Iniciar sesión con correo electrónico"
-#: packages/admin/src/components/LoginPage.tsx:348
+#: packages/admin/src/components/LoginPage.tsx:298
msgid "Sign in with email link"
msgstr "Iniciar sesión con enlace de correo electrónico"
#: packages/admin/src/components/auth/PasskeyLogin.tsx:136
-#: packages/admin/src/components/LoginPage.tsx:312
+#: packages/admin/src/components/LoginPage.tsx:260
msgid "Sign in with Passkey"
msgstr "Iniciar sesión con Passkey"
@@ -4530,7 +4543,7 @@ msgstr "Elección única entre opciones"
msgid "Single line text input"
msgstr "Entrada de texto de una sola línea"
-#: packages/admin/src/components/SetupWizard.tsx:331
+#: packages/admin/src/components/SetupWizard.tsx:380
msgid "Site"
msgstr "Sitio"
@@ -4542,12 +4555,12 @@ msgstr "Identidad del sitio"
msgid "Site identity, logo, favicon, and reading preferences"
msgstr "Identidad del sitio, logotipo, favicon y preferencias de lectura"
-#: packages/admin/src/components/SetupWizard.tsx:329
+#: packages/admin/src/components/SetupWizard.tsx:378
msgid "Site Settings"
msgstr "Configuración del sitio"
#: packages/admin/src/components/settings/GeneralSettings.tsx:156
-#: packages/admin/src/components/SetupWizard.tsx:141
+#: packages/admin/src/components/SetupWizard.tsx:143
msgid "Site Title"
msgstr "Título del sitio"
@@ -4555,7 +4568,7 @@ msgstr "Título del sitio"
msgid "Site title & tagline"
msgstr "Título y eslogan del sitio"
-#: packages/admin/src/components/SetupWizard.tsx:125
+#: packages/admin/src/components/SetupWizard.tsx:127
msgid "Site title is required"
msgstr "El título del sitio es obligatorio."
@@ -4699,7 +4712,7 @@ msgid "System ({resolvedLabel})"
msgstr "Sistema ({resolvedLabel})"
#: packages/admin/src/components/settings/GeneralSettings.tsx:162
-#: packages/admin/src/components/SetupWizard.tsx:152
+#: packages/admin/src/components/SetupWizard.tsx:154
msgid "Tagline"
msgstr "Lema"
@@ -4737,7 +4750,7 @@ msgstr "Taxonomía creada"
msgid "Taxonomy not found:"
msgstr "Taxonomía no encontrada:"
-#: packages/admin/src/components/SetupWizard.tsx:180
+#: packages/admin/src/components/SetupWizard.tsx:182
msgid "Template:"
msgstr "Plantilla:"
@@ -4770,7 +4783,7 @@ msgstr "Las siguientes tablas contienen contenido pero no están registradas com
msgid "The invited user will have this role once they complete registration."
msgstr "El usuario invitado tendrá este rol una vez que complete el registro."
-#: packages/admin/src/components/LoginPage.tsx:170
+#: packages/admin/src/components/LoginPage.tsx:113
#: packages/admin/src/components/SignupPage.tsx:138
msgid "The link will expire in 15 minutes."
msgstr "El enlace caducará en 15 minutos."
@@ -4895,7 +4908,7 @@ msgstr "Esto eliminará el plugin y su paquete de su sitio."
msgid "This will revert to the published version. Your draft changes will be lost."
msgstr "Esto volverá a la versión publicada. Los borradores de cambios se perderán."
-#: packages/admin/src/components/SetupWizard.tsx:156
+#: packages/admin/src/components/SetupWizard.tsx:158
msgid "Thoughts, tutorials, and more"
msgstr "Pensamientos, tutoriales y más"
@@ -5261,7 +5274,7 @@ msgstr "Utilice [param] o [...rest] en rutas para hacer coincidir patrones."
msgid "Use your device's biometric authentication, security key, or PIN to sign in."
msgstr "Utilice la autenticación biométrica, la clave de seguridad o el PIN de su dispositivo para iniciar sesión."
-#: packages/admin/src/components/LoginPage.tsx:359
+#: packages/admin/src/components/LoginPage.tsx:334
msgid "Use your registered passkey to sign in securely."
msgstr "Utilice su Passkey registrada para iniciar sesión de forma segura."
@@ -5379,7 +5392,7 @@ msgstr "No pudimos conectarnos a un sitio de WordPress en {0}. Esto podría sign
msgid "We'll check what import options are available for your site."
msgstr "Comprobaremos qué opciones de importación están disponibles para su sitio."
-#: packages/admin/src/components/LoginPage.tsx:360
+#: packages/admin/src/components/LoginPage.tsx:331
msgid "We'll send you a link to sign in without a password."
msgstr "Le enviaremos un enlace para iniciar sesión sin contraseña."
@@ -5516,7 +5529,7 @@ msgstr "Serás redirigido a WordPress para autorizar la conexión."
msgid "You'll be signing up as"
msgstr "Te registrarás como"
-#: packages/admin/src/components/SetupWizard.tsx:509
+#: packages/admin/src/components/SetupWizard.tsx:575
msgid "You're signed in via Cloudflare Access"
msgstr "Has iniciado sesión a través de Cloudflare Access"
@@ -5525,7 +5538,7 @@ msgid "you@company.com"
msgstr "usted@empresa.com"
#: packages/admin/src/components/auth/PasskeyLogin.tsx:336
-#: packages/admin/src/components/SetupWizard.tsx:226
+#: packages/admin/src/components/SetupWizard.tsx:228
msgid "you@example.com"
msgstr "tu@ejemplo.com"
@@ -5543,7 +5556,7 @@ msgstr "Su navegador no admite claves de acceso. Utilice un navegador moderno co
msgid "Your device doesn't support the required security features."
msgstr "Su dispositivo no admite las funciones de seguridad requeridas."
-#: packages/admin/src/components/SetupWizard.tsx:222
+#: packages/admin/src/components/SetupWizard.tsx:224
msgid "Your Email"
msgstr "Tu correo electrónico"
@@ -5563,7 +5576,7 @@ msgstr "Tu nombre de usuario de Instagram"
msgid "Your LinkedIn profile username"
msgstr "El nombre de usuario de tu perfil de LinkedIn"
-#: packages/admin/src/components/SetupWizard.tsx:234
+#: packages/admin/src/components/SetupWizard.tsx:236
msgid "Your Name"
msgstr "Su nombre"
diff --git a/packages/admin/src/locales/eu/messages.po b/packages/admin/src/locales/eu/messages.po
index 0ee4004f6..14c5ae088 100644
--- a/packages/admin/src/locales/eu/messages.po
+++ b/packages/admin/src/locales/eu/messages.po
@@ -59,7 +59,7 @@ msgid "{0, plural, one {(# item)} other {(# items)}}"
msgstr ""
#. placeholder {0}: seedInfo.collections
-#: packages/admin/src/components/SetupWizard.tsx:181
+#: packages/admin/src/components/SetupWizard.tsx:183
msgid "{0, plural, one {# collection} other {# collections}}"
msgstr ""
@@ -315,8 +315,9 @@ msgstr ""
msgid "• URLs in your content are updated automatically"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:250
-#: packages/admin/src/components/SetupWizard.tsx:310
+#: packages/admin/src/components/SetupWizard.tsx:252
+#: packages/admin/src/components/SetupWizard.tsx:304
+#: packages/admin/src/components/SetupWizard.tsx:359
#: packages/admin/src/components/WordPressImport.tsx:1431
msgid "← Back"
msgstr ""
@@ -409,7 +410,7 @@ msgstr ""
msgid "Accept & Update"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:332
+#: packages/admin/src/components/SetupWizard.tsx:381
msgid "Account"
msgstr ""
@@ -694,7 +695,7 @@ msgstr ""
msgid "Authentication error: {0}"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:253
+#: packages/admin/src/components/LoginPage.tsx:195
msgid "Authentication error: {error}"
msgstr "Autentifikazio errorea: {error}"
@@ -764,8 +765,9 @@ msgstr ""
msgid "Back to {collectionLabel} list"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:174
-#: packages/admin/src/components/LoginPage.tsx:210
+#: packages/admin/src/components/LoginPage.tsx:117
+#: packages/admin/src/components/LoginPage.tsx:153
+#: packages/admin/src/components/LoginPage.tsx:319
#: packages/admin/src/components/SignupPage.tsx:280
msgid "Back to login"
msgstr "Itzuli login egitera"
@@ -957,7 +959,7 @@ msgstr ""
msgid "Check Site"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:158
+#: packages/admin/src/components/LoginPage.tsx:101
#: packages/admin/src/components/SignupPage.tsx:129
#: packages/admin/src/components/SignupPage.tsx:401
msgid "Check your email"
@@ -971,6 +973,10 @@ msgstr ""
msgid "Checking authentication..."
msgstr ""
+#: packages/admin/src/components/SetupWizard.tsx:315
+msgid "Choose how to sign in"
+msgstr ""
+
#: packages/admin/src/components/Settings.tsx:130
msgid "Choose your preferred admin language"
msgstr "Aukeratu kudeaketa interfazearen hizkuntza"
@@ -979,7 +985,7 @@ msgstr "Aukeratu kudeaketa interfazearen hizkuntza"
msgid "Click the link in the email to continue setting up your account."
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:169
+#: packages/admin/src/components/LoginPage.tsx:112
msgid "Click the link in the email to sign in."
msgstr "Egin klik emailean webgunean sartzeko."
@@ -1185,8 +1191,8 @@ msgstr "Edukia idatzi"
msgid "Continue"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:175
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:177
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Continue →"
msgstr ""
@@ -1291,7 +1297,7 @@ msgstr "Sortu token berria"
msgid "Create one in WordPress: Users → Profile → Application Passwords"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:305
+#: packages/admin/src/components/SetupWizard.tsx:325
msgid "Create Passkey"
msgstr ""
@@ -1336,7 +1342,7 @@ msgstr ""
msgid "Create Token"
msgstr "Sortu tokena"
-#: packages/admin/src/components/SetupWizard.tsx:505
+#: packages/admin/src/components/SetupWizard.tsx:571
msgid "Create your account"
msgstr ""
@@ -1724,7 +1730,7 @@ msgstr ""
msgid "Domain updated"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:366
+#: packages/admin/src/components/LoginPage.tsx:340
msgid "Don't have an account? <0>Sign up0>"
msgstr "Ez duzu konturik? <0>Eman izena0>"
@@ -1853,13 +1859,13 @@ msgstr "Emaila"
msgid "Email (optional)"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:183
+#: packages/admin/src/components/LoginPage.tsx:126
#: packages/admin/src/components/SignupPage.tsx:65
#: packages/admin/src/components/users/InviteUserModal.tsx:154
msgid "Email address"
msgstr "Emaila"
-#: packages/admin/src/components/SetupWizard.tsx:204
+#: packages/admin/src/components/SetupWizard.tsx:206
#: packages/admin/src/components/SignupPage.tsx:48
msgid "Email is required"
msgstr ""
@@ -1965,6 +1971,10 @@ msgstr ""
msgid "Enter the code from your terminal"
msgstr ""
+#: packages/admin/src/components/LoginPage.tsx:333
+msgid "Enter your handle to sign in."
+msgstr ""
+
#: packages/admin/src/components/WordPressImport.tsx:1289
msgid "Enter your WordPress credentials to import content directly."
msgstr ""
@@ -1975,7 +1985,7 @@ msgstr ""
#: packages/admin/src/components/MenuEditor.tsx:83
#: packages/admin/src/components/MenuEditor.tsx:122
-#: packages/admin/src/components/SetupWizard.tsx:484
+#: packages/admin/src/components/SetupWizard.tsx:550
msgid "Error"
msgstr ""
@@ -2089,7 +2099,7 @@ msgstr ""
msgid "Failed to load revisions"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:486
+#: packages/admin/src/components/SetupWizard.tsx:552
msgid "Failed to load setup"
msgstr ""
@@ -2120,8 +2130,8 @@ msgstr ""
msgid "Failed to save settings"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:127
-#: packages/admin/src/components/LoginPage.tsx:132
+#: packages/admin/src/components/LoginPage.tsx:70
+#: packages/admin/src/components/LoginPage.tsx:75
msgid "Failed to send magic link"
msgstr "Huts egin du esteka magikoa bidaltzeak"
@@ -2328,7 +2338,7 @@ msgstr ""
msgid "ID"
msgstr "ID"
-#: packages/admin/src/components/LoginPage.tsx:160
+#: packages/admin/src/components/LoginPage.tsx:103
msgid "If an account exists for <0>{email}0>, we've sent a sign-in link."
msgstr "<0>{email}0> kontua existitzen bada, sartzeko esteka magikoa bidali dugu."
@@ -2448,7 +2458,7 @@ msgstr ""
msgid "Importing Media"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:163
+#: packages/admin/src/components/SetupWizard.tsx:165
msgid "Include sample content (recommended for new sites)"
msgstr ""
@@ -2561,7 +2571,7 @@ msgstr ""
msgid "Item updated"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:238
+#: packages/admin/src/components/SetupWizard.tsx:240
#: packages/admin/src/components/SignupPage.tsx:204
msgid "Jane Doe"
msgstr ""
@@ -2585,7 +2595,7 @@ msgstr ""
msgid "Label"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:379
+#: packages/admin/src/components/LoginPage.tsx:353
#: packages/admin/src/components/Settings.tsx:129
#: packages/admin/src/components/Settings.tsx:134
msgid "Language"
@@ -2725,7 +2735,7 @@ msgstr ""
msgid "Loading settings..."
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:473
+#: packages/admin/src/components/SetupWizard.tsx:539
msgid "Loading setup..."
msgstr ""
@@ -3021,7 +3031,7 @@ msgstr ""
msgid "Multiple choices from options"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:145
+#: packages/admin/src/components/SetupWizard.tsx:147
msgid "My Awesome Blog"
msgstr ""
@@ -3403,7 +3413,7 @@ msgstr ""
msgid "Or click to browse. Accepts .xml files exported from WordPress."
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:321
+#: packages/admin/src/components/LoginPage.tsx:269
msgid "Or continue with"
msgstr "Edo jarraitu honekin"
@@ -3462,10 +3472,6 @@ msgstr ""
msgid "Pass"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:333
-msgid "Passkey"
-msgstr ""
-
#: packages/admin/src/components/settings/SecuritySettings.tsx:98
msgid "Passkey added successfully"
msgstr ""
@@ -3507,10 +3513,6 @@ msgstr ""
msgid "Passkeys are a secure, passwordless way to sign in using your device's biometrics, PIN, or security key."
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:297
-msgid "Passkeys are more secure than passwords. You'll use your device's biometrics, PIN, or security key to sign in."
-msgstr ""
-
#: packages/admin/src/components/auth/PasskeyLogin.tsx:303
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:300
msgid "Passkeys Not Available Here"
@@ -3558,12 +3560,16 @@ msgstr ""
msgid "Permissions"
msgstr ""
+#: packages/admin/src/components/SetupWizard.tsx:317
+msgid "Pick any method to create your admin account."
+msgstr ""
+
#: packages/admin/src/components/auth/PasskeyLogin.tsx:313
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:310
msgid "Plain"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:206
+#: packages/admin/src/components/SetupWizard.tsx:208
msgid "Please enter a valid email"
msgstr ""
@@ -3637,7 +3643,7 @@ msgid "Preparing to download files from WordPress..."
msgstr ""
#: packages/admin/src/components/auth/PasskeyLogin.tsx:166
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Preparing..."
msgstr ""
@@ -4264,7 +4270,7 @@ msgstr "Atalak"
msgid "secure context"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:506
+#: packages/admin/src/components/SetupWizard.tsx:572
msgid "Secure your account"
msgstr ""
@@ -4388,7 +4394,7 @@ msgstr ""
msgid "Send Invite"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
msgid "Send magic link"
msgstr "Bidali esteka magikoa"
@@ -4404,7 +4410,7 @@ msgstr ""
msgid "Send Test Email"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
#: packages/admin/src/components/settings/EmailSettings.tsx:162
#: packages/admin/src/components/SignupPage.tsx:87
#: packages/admin/src/components/SignupPage.tsx:150
@@ -4440,15 +4446,11 @@ msgstr ""
msgid "Set a custom display size for this image instance."
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:295
-msgid "Set up your passkey"
-msgstr ""
-
-#: packages/admin/src/components/SetupWizard.tsx:504
+#: packages/admin/src/components/SetupWizard.tsx:570
msgid "Set up your site"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:175
+#: packages/admin/src/components/SetupWizard.tsx:177
msgid "Setting up..."
msgstr ""
@@ -4496,24 +4498,35 @@ msgstr ""
msgid "Sign in"
msgstr ""
+#: packages/admin/src/components/SetupWizard.tsx:382
+msgid "Sign In"
+msgstr ""
+
#: packages/admin/src/components/SignupPage.tsx:269
msgid "Sign in instead"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:291
+#: packages/admin/src/components/LoginPage.tsx:240
msgid "Sign in to your site"
msgstr "Sartu zure webgunera"
-#: packages/admin/src/components/LoginPage.tsx:292
+#. placeholder {0}: authProviderList.find((p) => p.id === activeProvider)?.label ?? activeProvider
+#. placeholder {0}: provider.label
+#: packages/admin/src/components/LoginPage.tsx:239
+#: packages/admin/src/components/SetupWizard.tsx:291
+msgid "Sign in with {0}"
+msgstr ""
+
+#: packages/admin/src/components/LoginPage.tsx:237
msgid "Sign in with email"
msgstr "Sartu emailarekin"
-#: packages/admin/src/components/LoginPage.tsx:348
+#: packages/admin/src/components/LoginPage.tsx:298
msgid "Sign in with email link"
msgstr "Sartu email bidezko estekarekin"
#: packages/admin/src/components/auth/PasskeyLogin.tsx:136
-#: packages/admin/src/components/LoginPage.tsx:312
+#: packages/admin/src/components/LoginPage.tsx:260
msgid "Sign in with Passkey"
msgstr "Sartu sarbide-gakoarekin"
@@ -4530,7 +4543,7 @@ msgstr ""
msgid "Single line text input"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:331
+#: packages/admin/src/components/SetupWizard.tsx:380
msgid "Site"
msgstr ""
@@ -4542,12 +4555,12 @@ msgstr ""
msgid "Site identity, logo, favicon, and reading preferences"
msgstr "Webgunearen identitatea, logoa, faviconak eta irakurtzeko hobespenak"
-#: packages/admin/src/components/SetupWizard.tsx:329
+#: packages/admin/src/components/SetupWizard.tsx:378
msgid "Site Settings"
msgstr ""
#: packages/admin/src/components/settings/GeneralSettings.tsx:156
-#: packages/admin/src/components/SetupWizard.tsx:141
+#: packages/admin/src/components/SetupWizard.tsx:143
msgid "Site Title"
msgstr ""
@@ -4555,7 +4568,7 @@ msgstr ""
msgid "Site title & tagline"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:125
+#: packages/admin/src/components/SetupWizard.tsx:127
msgid "Site title is required"
msgstr ""
@@ -4699,7 +4712,7 @@ msgid "System ({resolvedLabel})"
msgstr ""
#: packages/admin/src/components/settings/GeneralSettings.tsx:162
-#: packages/admin/src/components/SetupWizard.tsx:152
+#: packages/admin/src/components/SetupWizard.tsx:154
msgid "Tagline"
msgstr ""
@@ -4737,7 +4750,7 @@ msgstr ""
msgid "Taxonomy not found:"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:180
+#: packages/admin/src/components/SetupWizard.tsx:182
msgid "Template:"
msgstr ""
@@ -4770,7 +4783,7 @@ msgstr ""
msgid "The invited user will have this role once they complete registration."
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:170
+#: packages/admin/src/components/LoginPage.tsx:113
#: packages/admin/src/components/SignupPage.tsx:138
msgid "The link will expire in 15 minutes."
msgstr "Esteka hau 15 minutuan iraungiko da."
@@ -4895,7 +4908,7 @@ msgstr ""
msgid "This will revert to the published version. Your draft changes will be lost."
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:156
+#: packages/admin/src/components/SetupWizard.tsx:158
msgid "Thoughts, tutorials, and more"
msgstr ""
@@ -5261,7 +5274,7 @@ msgstr ""
msgid "Use your device's biometric authentication, security key, or PIN to sign in."
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:359
+#: packages/admin/src/components/LoginPage.tsx:334
msgid "Use your registered passkey to sign in securely."
msgstr "Erabili zuk erregistratutako sarbide-gakoa era seguruan sartzeko."
@@ -5379,7 +5392,7 @@ msgstr ""
msgid "We'll check what import options are available for your site."
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:360
+#: packages/admin/src/components/LoginPage.tsx:331
msgid "We'll send you a link to sign in without a password."
msgstr "Pasahitz gabe sartzeko esteka bat bidaliko dizugu."
@@ -5516,7 +5529,7 @@ msgstr ""
msgid "You'll be signing up as"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:509
+#: packages/admin/src/components/SetupWizard.tsx:575
msgid "You're signed in via Cloudflare Access"
msgstr ""
@@ -5525,7 +5538,7 @@ msgid "you@company.com"
msgstr ""
#: packages/admin/src/components/auth/PasskeyLogin.tsx:336
-#: packages/admin/src/components/SetupWizard.tsx:226
+#: packages/admin/src/components/SetupWizard.tsx:228
msgid "you@example.com"
msgstr ""
@@ -5543,7 +5556,7 @@ msgstr ""
msgid "Your device doesn't support the required security features."
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:222
+#: packages/admin/src/components/SetupWizard.tsx:224
msgid "Your Email"
msgstr ""
@@ -5563,7 +5576,7 @@ msgstr ""
msgid "Your LinkedIn profile username"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:234
+#: packages/admin/src/components/SetupWizard.tsx:236
msgid "Your Name"
msgstr ""
diff --git a/packages/admin/src/locales/fa/messages.po b/packages/admin/src/locales/fa/messages.po
index 30158a45a..7cf0f96a2 100644
--- a/packages/admin/src/locales/fa/messages.po
+++ b/packages/admin/src/locales/fa/messages.po
@@ -61,7 +61,7 @@ msgid "{0, plural, one {(# item)} other {(# items)}}"
msgstr "{0, plural, one {(# مورد)} other {(# مورد)}}"
#. placeholder {0}: seedInfo.collections
-#: packages/admin/src/components/SetupWizard.tsx:181
+#: packages/admin/src/components/SetupWizard.tsx:183
msgid "{0, plural, one {# collection} other {# collections}}"
msgstr "{0, plural, one {# مجموعه} other {# مجموعه}}"
@@ -317,8 +317,9 @@ msgstr "• در فضای رسانه EmDash شما بارگذاری میشو
msgid "• URLs in your content are updated automatically"
msgstr "• آدرسها در محتوای شما بهصورت خودکار بهروزرسانی میشوند"
-#: packages/admin/src/components/SetupWizard.tsx:250
-#: packages/admin/src/components/SetupWizard.tsx:310
+#: packages/admin/src/components/SetupWizard.tsx:252
+#: packages/admin/src/components/SetupWizard.tsx:304
+#: packages/admin/src/components/SetupWizard.tsx:359
#: packages/admin/src/components/WordPressImport.tsx:1431
msgid "← Back"
msgstr "← بازگشت"
@@ -411,7 +412,7 @@ msgstr "پذیرش و نصب"
msgid "Accept & Update"
msgstr "پذیرش و بهروزرسانی"
-#: packages/admin/src/components/SetupWizard.tsx:332
+#: packages/admin/src/components/SetupWizard.tsx:381
msgid "Account"
msgstr "حساب کاربری"
@@ -696,7 +697,7 @@ msgstr "نگارندگان وردپرس را به کاربران EmDash تخصی
msgid "Authentication error: {0}"
msgstr "خطای احراز هویت: {0}"
-#: packages/admin/src/components/LoginPage.tsx:253
+#: packages/admin/src/components/LoginPage.tsx:195
msgid "Authentication error: {error}"
msgstr "خطای احراز هویت: {error}"
@@ -766,8 +767,9 @@ msgstr "بازگشت"
msgid "Back to {collectionLabel} list"
msgstr "بازگشت به لیست {collectionLabel}"
-#: packages/admin/src/components/LoginPage.tsx:174
-#: packages/admin/src/components/LoginPage.tsx:210
+#: packages/admin/src/components/LoginPage.tsx:117
+#: packages/admin/src/components/LoginPage.tsx:153
+#: packages/admin/src/components/LoginPage.tsx:319
#: packages/admin/src/components/SignupPage.tsx:280
msgid "Back to login"
msgstr "بازگشت به ورود"
@@ -959,7 +961,7 @@ msgstr "بررسی بهروزرسانیها"
msgid "Check Site"
msgstr "بررسی وبسایت"
-#: packages/admin/src/components/LoginPage.tsx:158
+#: packages/admin/src/components/LoginPage.tsx:101
#: packages/admin/src/components/SignupPage.tsx:129
#: packages/admin/src/components/SignupPage.tsx:401
msgid "Check your email"
@@ -973,6 +975,10 @@ msgstr "در حال بررسی {urlInput}..."
msgid "Checking authentication..."
msgstr "در حال بررسی احراز هویت..."
+#: packages/admin/src/components/SetupWizard.tsx:315
+msgid "Choose how to sign in"
+msgstr ""
+
#: packages/admin/src/components/Settings.tsx:130
msgid "Choose your preferred admin language"
msgstr "زبان مدیریت مورد نظر خود را انتخاب کنید"
@@ -981,7 +987,7 @@ msgstr "زبان مدیریت مورد نظر خود را انتخاب کنید"
msgid "Click the link in the email to continue setting up your account."
msgstr "روی لینک ایمیل کلیک کنید تا راهاندازی حساب کاربری خود را ادامه دهید."
-#: packages/admin/src/components/LoginPage.tsx:169
+#: packages/admin/src/components/LoginPage.tsx:112
msgid "Click the link in the email to sign in."
msgstr "روی لینک ایمیل کلیک کنید تا وارد شوید."
@@ -1187,8 +1193,8 @@ msgstr "نوشتن محتوا"
msgid "Continue"
msgstr "ادامه"
-#: packages/admin/src/components/SetupWizard.tsx:175
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:177
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Continue →"
msgstr "ادامه →"
@@ -1293,7 +1299,7 @@ msgstr "ساخت توکن جدید"
msgid "Create one in WordPress: Users → Profile → Application Passwords"
msgstr "یکی در وردپرس بسازید: کاربران → نمایه → گذرواژههای برنامه"
-#: packages/admin/src/components/SetupWizard.tsx:305
+#: packages/admin/src/components/SetupWizard.tsx:325
msgid "Create Passkey"
msgstr "ساخت شاهکلید"
@@ -1338,7 +1344,7 @@ msgstr "ساخت دستهبندی"
msgid "Create Token"
msgstr "ساخت توکن"
-#: packages/admin/src/components/SetupWizard.tsx:505
+#: packages/admin/src/components/SetupWizard.tsx:571
msgid "Create your account"
msgstr "حساب کاربری خود را بسازید"
@@ -1726,7 +1732,7 @@ msgstr "دامنه حذف شد"
msgid "Domain updated"
msgstr "دامنه بهروزرسانی شد"
-#: packages/admin/src/components/LoginPage.tsx:366
+#: packages/admin/src/components/LoginPage.tsx:340
msgid "Don't have an account? <0>Sign up0>"
msgstr "حساب کاربری ندارید؟ <0>ثبتنام0>"
@@ -1855,13 +1861,13 @@ msgstr "ایمیل"
msgid "Email (optional)"
msgstr "ایمیل (اختیاری)"
-#: packages/admin/src/components/LoginPage.tsx:183
+#: packages/admin/src/components/LoginPage.tsx:126
#: packages/admin/src/components/SignupPage.tsx:65
#: packages/admin/src/components/users/InviteUserModal.tsx:154
msgid "Email address"
msgstr "آدرس ایمیل"
-#: packages/admin/src/components/SetupWizard.tsx:204
+#: packages/admin/src/components/SetupWizard.tsx:206
#: packages/admin/src/components/SignupPage.tsx:48
msgid "Email is required"
msgstr "ایمیل الزامی است"
@@ -1967,6 +1973,10 @@ msgstr "نام را وارد کنید"
msgid "Enter the code from your terminal"
msgstr "کد ترمینال خود را وارد کنید"
+#: packages/admin/src/components/LoginPage.tsx:333
+msgid "Enter your handle to sign in."
+msgstr ""
+
#: packages/admin/src/components/WordPressImport.tsx:1289
msgid "Enter your WordPress credentials to import content directly."
msgstr "اطلاعات ورود وردپرس خود را وارد کنید تا محتوا مستقیماً درونریزی شود."
@@ -1977,7 +1987,7 @@ msgstr "آدرس وبسایت (URL) وردپرس خود را وارد کنید"
#: packages/admin/src/components/MenuEditor.tsx:83
#: packages/admin/src/components/MenuEditor.tsx:122
-#: packages/admin/src/components/SetupWizard.tsx:484
+#: packages/admin/src/components/SetupWizard.tsx:550
msgid "Error"
msgstr "خطا"
@@ -2091,7 +2101,7 @@ msgstr "بارگذاری افزونهها ناموفق بود: {0}"
msgid "Failed to load revisions"
msgstr "بارگذاری بازبینیها ناموفق بود"
-#: packages/admin/src/components/SetupWizard.tsx:486
+#: packages/admin/src/components/SetupWizard.tsx:552
msgid "Failed to load setup"
msgstr "بارگذاری راهاندازی ناموفق بود"
@@ -2122,8 +2132,8 @@ msgstr "ذخیره ناموفق بود"
msgid "Failed to save settings"
msgstr "ذخیره تنظیمات ناموفق بود"
-#: packages/admin/src/components/LoginPage.tsx:127
-#: packages/admin/src/components/LoginPage.tsx:132
+#: packages/admin/src/components/LoginPage.tsx:70
+#: packages/admin/src/components/LoginPage.tsx:75
msgid "Failed to send magic link"
msgstr "ارسال لینک جادویی ناموفق بود"
@@ -2330,7 +2340,7 @@ msgstr "آیکون به دلیل بررسی تصویر محو شده"
msgid "ID"
msgstr "ID"
-#: packages/admin/src/components/LoginPage.tsx:160
+#: packages/admin/src/components/LoginPage.tsx:103
msgid "If an account exists for <0>{email}0>, we've sent a sign-in link."
msgstr "اگر حساب کاربری برای <0>{email}0> وجود داشته باشد، لینک ورود ارسال شده است."
@@ -2450,7 +2460,7 @@ msgstr "در حال درونریزی محتوا..."
msgid "Importing Media"
msgstr "در حال درونریزی رسانه"
-#: packages/admin/src/components/SetupWizard.tsx:163
+#: packages/admin/src/components/SetupWizard.tsx:165
msgid "Include sample content (recommended for new sites)"
msgstr "شامل محتوای نمونه (توصیهشده برای وبسایتهای جدید)"
@@ -2563,7 +2573,7 @@ msgstr "مورد حذف شد"
msgid "Item updated"
msgstr "مورد بهروزرسانی شد"
-#: packages/admin/src/components/SetupWizard.tsx:238
+#: packages/admin/src/components/SetupWizard.tsx:240
#: packages/admin/src/components/SignupPage.tsx:204
msgid "Jane Doe"
msgstr "نام نمونه"
@@ -2587,7 +2597,7 @@ msgstr "کلمات کلیدی"
msgid "Label"
msgstr "برچسب"
-#: packages/admin/src/components/LoginPage.tsx:379
+#: packages/admin/src/components/LoginPage.tsx:353
#: packages/admin/src/components/Settings.tsx:129
#: packages/admin/src/components/Settings.tsx:134
msgid "Language"
@@ -2727,7 +2737,7 @@ msgstr "در حال بارگذاری بخشها..."
msgid "Loading settings..."
msgstr "در حال بارگذاری تنظیمات..."
-#: packages/admin/src/components/SetupWizard.tsx:473
+#: packages/admin/src/components/SetupWizard.tsx:539
msgid "Loading setup..."
msgstr "در حال بارگذاری راهاندازی..."
@@ -3023,7 +3033,7 @@ msgstr "متن ساده چندخطی"
msgid "Multiple choices from options"
msgstr "انتخاب چندگانه از گزینهها"
-#: packages/admin/src/components/SetupWizard.tsx:145
+#: packages/admin/src/components/SetupWizard.tsx:147
msgid "My Awesome Blog"
msgstr "وبلاگ عالی من"
@@ -3405,7 +3415,7 @@ msgstr "یا از کتابخانه انتخاب کنید"
msgid "Or click to browse. Accepts .xml files exported from WordPress."
msgstr "یا برای مرور کلیک کنید. فایلهای .xml برونریزیشده از وردپرس پذیرفته میشوند."
-#: packages/admin/src/components/LoginPage.tsx:321
+#: packages/admin/src/components/LoginPage.tsx:269
msgid "Or continue with"
msgstr "یا ادامه با"
@@ -3464,10 +3474,6 @@ msgstr "بخشی از حلقه تغییر مسیر"
msgid "Pass"
msgstr "موفق"
-#: packages/admin/src/components/SetupWizard.tsx:333
-msgid "Passkey"
-msgstr "شاهکلید"
-
#: packages/admin/src/components/settings/SecuritySettings.tsx:98
msgid "Passkey added successfully"
msgstr "شاهکلید با موفقیت افزوده شد"
@@ -3509,10 +3515,6 @@ msgstr "شاهکلیدها روشی امن و بدون گذرواژه برا
msgid "Passkeys are a secure, passwordless way to sign in using your device's biometrics, PIN, or security key."
msgstr "شاهکلیدها روشی امن و بدون گذرواژه برای ورود با استفاده از بیومتریک، پین یا کلید امنیتی دستگاه شما هستند."
-#: packages/admin/src/components/SetupWizard.tsx:297
-msgid "Passkeys are more secure than passwords. You'll use your device's biometrics, PIN, or security key to sign in."
-msgstr "شاهکلیدها از گذرواژهها امنتر هستند. از بیومتریک، پین یا کلید امنیتی دستگاه خود برای ورود استفاده خواهید کرد."
-
#: packages/admin/src/components/auth/PasskeyLogin.tsx:303
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:300
msgid "Passkeys Not Available Here"
@@ -3560,12 +3562,16 @@ msgstr "حذف دائمی {title}"
msgid "Permissions"
msgstr "مجوزها"
+#: packages/admin/src/components/SetupWizard.tsx:317
+msgid "Pick any method to create your admin account."
+msgstr ""
+
#: packages/admin/src/components/auth/PasskeyLogin.tsx:313
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:310
msgid "Plain"
msgstr "ساده"
-#: packages/admin/src/components/SetupWizard.tsx:206
+#: packages/admin/src/components/SetupWizard.tsx:208
msgid "Please enter a valid email"
msgstr "لطفاً یک ایمیل معتبر وارد کنید"
@@ -3639,7 +3645,7 @@ msgid "Preparing to download files from WordPress..."
msgstr "در حال آمادهسازی دریافت فایلها از وردپرس..."
#: packages/admin/src/components/auth/PasskeyLogin.tsx:166
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Preparing..."
msgstr "در حال آمادهسازی..."
@@ -4266,7 +4272,7 @@ msgstr "بخشها"
msgid "secure context"
msgstr "محیط امن"
-#: packages/admin/src/components/SetupWizard.tsx:506
+#: packages/admin/src/components/SetupWizard.tsx:572
msgid "Secure your account"
msgstr "حساب کاربری خود را ایمن کنید"
@@ -4390,7 +4396,7 @@ msgstr "یک ایمیل دعوت به عضو جدید تیم ارسال کنید
msgid "Send Invite"
msgstr "ارسال دعوت"
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
msgid "Send magic link"
msgstr "ارسال لینک جادویی"
@@ -4406,7 +4412,7 @@ msgstr "ارسال آزمایشی"
msgid "Send Test Email"
msgstr "ارسال ایمیل آزمایشی"
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
#: packages/admin/src/components/settings/EmailSettings.tsx:162
#: packages/admin/src/components/SignupPage.tsx:87
#: packages/admin/src/components/SignupPage.tsx:150
@@ -4442,15 +4448,11 @@ msgstr "عنوان سئو"
msgid "Set a custom display size for this image instance."
msgstr "یک اندازه نمایش سفارشی برای این نمونه تصویر تنظیم کنید."
-#: packages/admin/src/components/SetupWizard.tsx:295
-msgid "Set up your passkey"
-msgstr "شاهکلید خود را تنظیم کنید"
-
-#: packages/admin/src/components/SetupWizard.tsx:504
+#: packages/admin/src/components/SetupWizard.tsx:570
msgid "Set up your site"
msgstr "وبسایت خود را راهاندازی کنید"
-#: packages/admin/src/components/SetupWizard.tsx:175
+#: packages/admin/src/components/SetupWizard.tsx:177
msgid "Setting up..."
msgstr "در حال راهاندازی..."
@@ -4498,24 +4500,35 @@ msgstr "هنگام قرارگیری ماوس روی تصویر نمایش داد
msgid "Sign in"
msgstr "ورود"
+#: packages/admin/src/components/SetupWizard.tsx:382
+msgid "Sign In"
+msgstr ""
+
#: packages/admin/src/components/SignupPage.tsx:269
msgid "Sign in instead"
msgstr "یا، وارد شوید"
-#: packages/admin/src/components/LoginPage.tsx:291
+#: packages/admin/src/components/LoginPage.tsx:240
msgid "Sign in to your site"
msgstr "وارد وبسایت خود شوید"
-#: packages/admin/src/components/LoginPage.tsx:292
+#. placeholder {0}: authProviderList.find((p) => p.id === activeProvider)?.label ?? activeProvider
+#. placeholder {0}: provider.label
+#: packages/admin/src/components/LoginPage.tsx:239
+#: packages/admin/src/components/SetupWizard.tsx:291
+msgid "Sign in with {0}"
+msgstr ""
+
+#: packages/admin/src/components/LoginPage.tsx:237
msgid "Sign in with email"
msgstr "ورود با ایمیل"
-#: packages/admin/src/components/LoginPage.tsx:348
+#: packages/admin/src/components/LoginPage.tsx:298
msgid "Sign in with email link"
msgstr "ورود با ارسال لینک به ایمیل"
#: packages/admin/src/components/auth/PasskeyLogin.tsx:136
-#: packages/admin/src/components/LoginPage.tsx:312
+#: packages/admin/src/components/LoginPage.tsx:260
msgid "Sign in with Passkey"
msgstr "ورود با شاهکلید"
@@ -4532,7 +4545,7 @@ msgstr "انتخاب تکی از گزینهها"
msgid "Single line text input"
msgstr "ورودی متن تکخطی"
-#: packages/admin/src/components/SetupWizard.tsx:331
+#: packages/admin/src/components/SetupWizard.tsx:380
msgid "Site"
msgstr "وبسایت"
@@ -4544,12 +4557,12 @@ msgstr "هویت وبسایت"
msgid "Site identity, logo, favicon, and reading preferences"
msgstr "هویت وبسایت، لوگو، فاوآیکون و ترجیحات خواندن"
-#: packages/admin/src/components/SetupWizard.tsx:329
+#: packages/admin/src/components/SetupWizard.tsx:378
msgid "Site Settings"
msgstr "تنظیمات وبسایت"
#: packages/admin/src/components/settings/GeneralSettings.tsx:156
-#: packages/admin/src/components/SetupWizard.tsx:141
+#: packages/admin/src/components/SetupWizard.tsx:143
msgid "Site Title"
msgstr "عنوان وبسایت"
@@ -4557,7 +4570,7 @@ msgstr "عنوان وبسایت"
msgid "Site title & tagline"
msgstr "عنوان وبسایت و شعار"
-#: packages/admin/src/components/SetupWizard.tsx:125
+#: packages/admin/src/components/SetupWizard.tsx:127
msgid "Site title is required"
msgstr "عنوان وبسایت الزامی است"
@@ -4701,7 +4714,7 @@ msgid "System ({resolvedLabel})"
msgstr "سیستم ({resolvedLabel})"
#: packages/admin/src/components/settings/GeneralSettings.tsx:162
-#: packages/admin/src/components/SetupWizard.tsx:152
+#: packages/admin/src/components/SetupWizard.tsx:154
msgid "Tagline"
msgstr "شعار"
@@ -4739,7 +4752,7 @@ msgstr "دستهبندی ساخته شد"
msgid "Taxonomy not found:"
msgstr "دستهبندی یافت نشد:"
-#: packages/admin/src/components/SetupWizard.tsx:180
+#: packages/admin/src/components/SetupWizard.tsx:182
msgid "Template:"
msgstr "قالب:"
@@ -4772,7 +4785,7 @@ msgstr "جداول زیر حاوی محتوا هستند اما بهعنوا
msgid "The invited user will have this role once they complete registration."
msgstr "کاربر دعوتشده پس از تکمیل ثبتنام این نقش را خواهد داشت."
-#: packages/admin/src/components/LoginPage.tsx:170
+#: packages/admin/src/components/LoginPage.tsx:113
#: packages/admin/src/components/SignupPage.tsx:138
msgid "The link will expire in 15 minutes."
msgstr "لینک در ۱۵ دقیقه منقضی خواهد شد."
@@ -4897,7 +4910,7 @@ msgstr "این افزونه و بسته آن را از وبسایت شما حذ
msgid "This will revert to the published version. Your draft changes will be lost."
msgstr "این به نسخه منتشرشده بازمیگردد. تغییرات پیشنویس شما از بین خواهد رفت."
-#: packages/admin/src/components/SetupWizard.tsx:156
+#: packages/admin/src/components/SetupWizard.tsx:158
msgid "Thoughts, tutorials, and more"
msgstr "اندیشهها، آموزشها و بیشتر"
@@ -5263,7 +5276,7 @@ msgstr "از [param] یا [...rest] در مسیرها برای تطبیق الگ
msgid "Use your device's biometric authentication, security key, or PIN to sign in."
msgstr "از احراز هویت بیومتریک، کلید امنیتی یا پین دستگاه خود برای ورود استفاده کنید."
-#: packages/admin/src/components/LoginPage.tsx:359
+#: packages/admin/src/components/LoginPage.tsx:334
msgid "Use your registered passkey to sign in securely."
msgstr "از شاهکلید ثبتشده خود برای ورود امن استفاده کنید."
@@ -5381,7 +5394,7 @@ msgstr "نتوانستیم به وبسایت وردپرس در {0} متصل شو
msgid "We'll check what import options are available for your site."
msgstr "بررسی خواهیم کرد چه گزینههای درونریزی برای وبسایت شما موجود است."
-#: packages/admin/src/components/LoginPage.tsx:360
+#: packages/admin/src/components/LoginPage.tsx:331
msgid "We'll send you a link to sign in without a password."
msgstr "لینکی برای ورود بدون گذرواژه برای شما ارسال خواهیم کرد."
@@ -5518,7 +5531,7 @@ msgstr "به وردپرس هدایت میشوید تا اتصال را مجا
msgid "You'll be signing up as"
msgstr "شما ثبتنام خواهید کرد بهعنوان"
-#: packages/admin/src/components/SetupWizard.tsx:509
+#: packages/admin/src/components/SetupWizard.tsx:575
msgid "You're signed in via Cloudflare Access"
msgstr "شما از طریق مجوز کلاودفلر (Cloudflare Access) وارد شدهاید"
@@ -5527,7 +5540,7 @@ msgid "you@company.com"
msgstr "you@company.com"
#: packages/admin/src/components/auth/PasskeyLogin.tsx:336
-#: packages/admin/src/components/SetupWizard.tsx:226
+#: packages/admin/src/components/SetupWizard.tsx:228
msgid "you@example.com"
msgstr "you@example.com"
@@ -5545,7 +5558,7 @@ msgstr "مرورگر شما از شاهکلیدها پشتیبانی نمی
msgid "Your device doesn't support the required security features."
msgstr "دستگاه شما از ویژگیهای امنیتی مورد نیاز پشتیبانی نمیکند."
-#: packages/admin/src/components/SetupWizard.tsx:222
+#: packages/admin/src/components/SetupWizard.tsx:224
msgid "Your Email"
msgstr "ایمیل شما"
@@ -5565,7 +5578,7 @@ msgstr "نام کاربری اینستاگرام شما"
msgid "Your LinkedIn profile username"
msgstr "نام کاربری پروفایل لینکدین شما"
-#: packages/admin/src/components/SetupWizard.tsx:234
+#: packages/admin/src/components/SetupWizard.tsx:236
msgid "Your Name"
msgstr "نام شما"
diff --git a/packages/admin/src/locales/fr/messages.po b/packages/admin/src/locales/fr/messages.po
index 4d079e2a6..7997a127d 100644
--- a/packages/admin/src/locales/fr/messages.po
+++ b/packages/admin/src/locales/fr/messages.po
@@ -59,7 +59,7 @@ msgid "{0, plural, one {(# item)} other {(# items)}}"
msgstr "{0, plural, one {(# élément)} other {(# éléments)}}"
#. placeholder {0}: seedInfo.collections
-#: packages/admin/src/components/SetupWizard.tsx:181
+#: packages/admin/src/components/SetupWizard.tsx:183
msgid "{0, plural, one {# collection} other {# collections}}"
msgstr "{0, plural, one {# collection} other {# collections}}"
@@ -315,8 +315,9 @@ msgstr "• Téléversé vers votre espace de stockage de fichiers multimédias
msgid "• URLs in your content are updated automatically"
msgstr "• Les URL dans votre contenu sont mises à jour automatiquement"
-#: packages/admin/src/components/SetupWizard.tsx:250
-#: packages/admin/src/components/SetupWizard.tsx:310
+#: packages/admin/src/components/SetupWizard.tsx:252
+#: packages/admin/src/components/SetupWizard.tsx:304
+#: packages/admin/src/components/SetupWizard.tsx:359
#: packages/admin/src/components/WordPressImport.tsx:1431
msgid "← Back"
msgstr "← Retour"
@@ -409,7 +410,7 @@ msgstr "Accepter et installer"
msgid "Accept & Update"
msgstr "Accepter et mettre à jour"
-#: packages/admin/src/components/SetupWizard.tsx:332
+#: packages/admin/src/components/SetupWizard.tsx:381
msgid "Account"
msgstr "Compte"
@@ -694,7 +695,7 @@ msgstr "Attribuez des auteurs WordPress aux utilisateurs d'EmDash. Les publicati
msgid "Authentication error: {0}"
msgstr "Erreur d'authentification : {0}"
-#: packages/admin/src/components/LoginPage.tsx:253
+#: packages/admin/src/components/LoginPage.tsx:195
msgid "Authentication error: {error}"
msgstr "Erreur d'authentification : {error}"
@@ -764,8 +765,9 @@ msgstr "Retour"
msgid "Back to {collectionLabel} list"
msgstr "Retour à la liste {collectionLabel}"
-#: packages/admin/src/components/LoginPage.tsx:174
-#: packages/admin/src/components/LoginPage.tsx:210
+#: packages/admin/src/components/LoginPage.tsx:117
+#: packages/admin/src/components/LoginPage.tsx:153
+#: packages/admin/src/components/LoginPage.tsx:319
#: packages/admin/src/components/SignupPage.tsx:280
msgid "Back to login"
msgstr "Retour à la page de connexion"
@@ -957,7 +959,7 @@ msgstr "Vérifier les mises à jour"
msgid "Check Site"
msgstr "Vérifier le site"
-#: packages/admin/src/components/LoginPage.tsx:158
+#: packages/admin/src/components/LoginPage.tsx:101
#: packages/admin/src/components/SignupPage.tsx:129
#: packages/admin/src/components/SignupPage.tsx:401
msgid "Check your email"
@@ -971,6 +973,10 @@ msgstr "En cours de vérification de {urlInput}..."
msgid "Checking authentication..."
msgstr "En cours de vérification de l'authentification..."
+#: packages/admin/src/components/SetupWizard.tsx:315
+msgid "Choose how to sign in"
+msgstr ""
+
#: packages/admin/src/components/Settings.tsx:130
msgid "Choose your preferred admin language"
msgstr "Choisir votre langue d'administration préférée"
@@ -979,7 +985,7 @@ msgstr "Choisir votre langue d'administration préférée"
msgid "Click the link in the email to continue setting up your account."
msgstr "Cliquez sur le lien dans l'e-mail pour continuer la configuration de votre compte."
-#: packages/admin/src/components/LoginPage.tsx:169
+#: packages/admin/src/components/LoginPage.tsx:112
msgid "Click the link in the email to sign in."
msgstr "Cliquez sur le lien dans l'e-mail pour vous connecter."
@@ -1185,8 +1191,8 @@ msgstr "Écriture du contenu"
msgid "Continue"
msgstr "Continuer"
-#: packages/admin/src/components/SetupWizard.tsx:175
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:177
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Continue →"
msgstr "Continuer →"
@@ -1291,7 +1297,7 @@ msgstr "Créer un nouveau jeton"
msgid "Create one in WordPress: Users → Profile → Application Passwords"
msgstr "En créer un dans WordPress : Utilisateurs → Profil → Mots de passe d'application"
-#: packages/admin/src/components/SetupWizard.tsx:305
+#: packages/admin/src/components/SetupWizard.tsx:325
msgid "Create Passkey"
msgstr "Créer une clé d'accès"
@@ -1336,7 +1342,7 @@ msgstr "Créer une taxonomie"
msgid "Create Token"
msgstr "Créer un jeton"
-#: packages/admin/src/components/SetupWizard.tsx:505
+#: packages/admin/src/components/SetupWizard.tsx:571
msgid "Create your account"
msgstr "Créez votre compte"
@@ -1724,7 +1730,7 @@ msgstr "Domaine supprimé"
msgid "Domain updated"
msgstr "Domaine mis à jour"
-#: packages/admin/src/components/LoginPage.tsx:366
+#: packages/admin/src/components/LoginPage.tsx:340
msgid "Don't have an account? <0>Sign up0>"
msgstr "Pas encore de compte ? <0>S'inscrire0>"
@@ -1853,13 +1859,13 @@ msgstr "E-mail"
msgid "Email (optional)"
msgstr "E-mail (facultatif)"
-#: packages/admin/src/components/LoginPage.tsx:183
+#: packages/admin/src/components/LoginPage.tsx:126
#: packages/admin/src/components/SignupPage.tsx:65
#: packages/admin/src/components/users/InviteUserModal.tsx:154
msgid "Email address"
msgstr "Adresse e-mail"
-#: packages/admin/src/components/SetupWizard.tsx:204
+#: packages/admin/src/components/SetupWizard.tsx:206
#: packages/admin/src/components/SignupPage.tsx:48
msgid "Email is required"
msgstr "L'e-mail est obligatoire"
@@ -1965,6 +1971,10 @@ msgstr "Saisissez le nom"
msgid "Enter the code from your terminal"
msgstr "Saisissez le code depuis votre terminal"
+#: packages/admin/src/components/LoginPage.tsx:333
+msgid "Enter your handle to sign in."
+msgstr ""
+
#: packages/admin/src/components/WordPressImport.tsx:1289
msgid "Enter your WordPress credentials to import content directly."
msgstr "Saisissez vos informations d'identification WordPress pour importer du contenu directement."
@@ -1975,7 +1985,7 @@ msgstr "Saisir l'URL de votre site WordPress"
#: packages/admin/src/components/MenuEditor.tsx:83
#: packages/admin/src/components/MenuEditor.tsx:122
-#: packages/admin/src/components/SetupWizard.tsx:484
+#: packages/admin/src/components/SetupWizard.tsx:550
msgid "Error"
msgstr "Erreur"
@@ -2089,7 +2099,7 @@ msgstr "Échec du chargement des modules d'extension : {0}"
msgid "Failed to load revisions"
msgstr "Échec du chargement des révisions"
-#: packages/admin/src/components/SetupWizard.tsx:486
+#: packages/admin/src/components/SetupWizard.tsx:552
msgid "Failed to load setup"
msgstr "Échec du chargement de la configuration"
@@ -2120,8 +2130,8 @@ msgstr ""
msgid "Failed to save settings"
msgstr "Échec de l'enregistrement des paramètres"
-#: packages/admin/src/components/LoginPage.tsx:127
-#: packages/admin/src/components/LoginPage.tsx:132
+#: packages/admin/src/components/LoginPage.tsx:70
+#: packages/admin/src/components/LoginPage.tsx:75
msgid "Failed to send magic link"
msgstr "Échec de l'envoi du lien de connexion"
@@ -2328,7 +2338,7 @@ msgstr "Icône floutée en raison de l'audit d'image"
msgid "ID"
msgstr "ID"
-#: packages/admin/src/components/LoginPage.tsx:160
+#: packages/admin/src/components/LoginPage.tsx:103
msgid "If an account exists for <0>{email}0>, we've sent a sign-in link."
msgstr "Si un compte existe pour <0>{email}0>, nous vous avons envoyé un lien de connexion."
@@ -2448,7 +2458,7 @@ msgstr "En cours d'importation de contenu..."
msgid "Importing Media"
msgstr "Importation de fichiers multimédias"
-#: packages/admin/src/components/SetupWizard.tsx:163
+#: packages/admin/src/components/SetupWizard.tsx:165
msgid "Include sample content (recommended for new sites)"
msgstr "Inclure un exemple de contenu (recommandé pour les nouveaux sites)"
@@ -2561,7 +2571,7 @@ msgstr "Élément supprimé"
msgid "Item updated"
msgstr "Article mis à jour"
-#: packages/admin/src/components/SetupWizard.tsx:238
+#: packages/admin/src/components/SetupWizard.tsx:240
#: packages/admin/src/components/SignupPage.tsx:204
msgid "Jane Doe"
msgstr "Jane Doe"
@@ -2585,7 +2595,7 @@ msgstr "Mots-clés"
msgid "Label"
msgstr "Libellé"
-#: packages/admin/src/components/LoginPage.tsx:379
+#: packages/admin/src/components/LoginPage.tsx:353
#: packages/admin/src/components/Settings.tsx:129
#: packages/admin/src/components/Settings.tsx:134
msgid "Language"
@@ -2725,7 +2735,7 @@ msgstr "En cours de chargement des rubriques..."
msgid "Loading settings..."
msgstr "En cours de chargement des paramètres..."
-#: packages/admin/src/components/SetupWizard.tsx:473
+#: packages/admin/src/components/SetupWizard.tsx:539
msgid "Loading setup..."
msgstr "En cours de chargement de la configuration..."
@@ -3021,7 +3031,7 @@ msgstr "Texte brut multiligne"
msgid "Multiple choices from options"
msgstr "Plusieurs choix parmi les options"
-#: packages/admin/src/components/SetupWizard.tsx:145
+#: packages/admin/src/components/SetupWizard.tsx:147
msgid "My Awesome Blog"
msgstr "Mon super blog"
@@ -3403,7 +3413,7 @@ msgstr "ou choisissez dans la bibliothèque"
msgid "Or click to browse. Accepts .xml files exported from WordPress."
msgstr "Ou cliquez pour parcourir. Accepte les fichiers .xml exportés depuis WordPress."
-#: packages/admin/src/components/LoginPage.tsx:321
+#: packages/admin/src/components/LoginPage.tsx:269
msgid "Or continue with"
msgstr "Ou se connecter avec"
@@ -3462,10 +3472,6 @@ msgstr "Partie d'une boucle de redirection"
msgid "Pass"
msgstr "Passer"
-#: packages/admin/src/components/SetupWizard.tsx:333
-msgid "Passkey"
-msgstr "Clé d'accès"
-
#: packages/admin/src/components/settings/SecuritySettings.tsx:98
msgid "Passkey added successfully"
msgstr "Clé d'accès ajoutée avec succès"
@@ -3507,10 +3513,6 @@ msgstr "Les clés d'accès sont un moyen sécurisé et sans mot de passe de vous
msgid "Passkeys are a secure, passwordless way to sign in using your device's biometrics, PIN, or security key."
msgstr "Les clés d'accès constituent un moyen sécurisé et sans mot de passe de vous connecter à l'aide des données biométriques, du code PIN ou de la clé de sécurité de votre appareil."
-#: packages/admin/src/components/SetupWizard.tsx:297
-msgid "Passkeys are more secure than passwords. You'll use your device's biometrics, PIN, or security key to sign in."
-msgstr "Les clés d'accès sont plus sécurisées que les mots de passe. Vous utiliserez les données biométriques, le code PIN ou la clé de sécurité de votre appareil pour vous connecter."
-
#: packages/admin/src/components/auth/PasskeyLogin.tsx:303
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:300
msgid "Passkeys Not Available Here"
@@ -3558,12 +3560,16 @@ msgstr "Supprimer définitivement {title}"
msgid "Permissions"
msgstr "Autorisations"
+#: packages/admin/src/components/SetupWizard.tsx:317
+msgid "Pick any method to create your admin account."
+msgstr ""
+
#: packages/admin/src/components/auth/PasskeyLogin.tsx:313
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:310
msgid "Plain"
msgstr "Un simple"
-#: packages/admin/src/components/SetupWizard.tsx:206
+#: packages/admin/src/components/SetupWizard.tsx:208
msgid "Please enter a valid email"
msgstr "Veuillez saisir un email valide"
@@ -3637,7 +3643,7 @@ msgid "Preparing to download files from WordPress..."
msgstr "En cours de préparation du téléchargement de fichiers depuis WordPress..."
#: packages/admin/src/components/auth/PasskeyLogin.tsx:166
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Preparing..."
msgstr "En cours de préparation..."
@@ -4264,7 +4270,7 @@ msgstr "Sections"
msgid "secure context"
msgstr "contexte sécurisé"
-#: packages/admin/src/components/SetupWizard.tsx:506
+#: packages/admin/src/components/SetupWizard.tsx:572
msgid "Secure your account"
msgstr "Sécuriser votre compte"
@@ -4388,7 +4394,7 @@ msgstr "Envoyer un e-mail d'invitation à un nouveau membre de l'équipe."
msgid "Send Invite"
msgstr "Envoyer une invitation"
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
msgid "Send magic link"
msgstr "Envoyer un lien de connexion"
@@ -4404,7 +4410,7 @@ msgstr "Envoyer le test"
msgid "Send Test Email"
msgstr "Envoyer un e-mail de test"
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
#: packages/admin/src/components/settings/EmailSettings.tsx:162
#: packages/admin/src/components/SignupPage.tsx:87
#: packages/admin/src/components/SignupPage.tsx:150
@@ -4440,15 +4446,11 @@ msgstr "Titre SEO"
msgid "Set a custom display size for this image instance."
msgstr "Définissez une taille d'affichage personnalisée pour cette instance d'image."
-#: packages/admin/src/components/SetupWizard.tsx:295
-msgid "Set up your passkey"
-msgstr "Configurer votre clé d'accès"
-
-#: packages/admin/src/components/SetupWizard.tsx:504
+#: packages/admin/src/components/SetupWizard.tsx:570
msgid "Set up your site"
msgstr "Configurer votre site"
-#: packages/admin/src/components/SetupWizard.tsx:175
+#: packages/admin/src/components/SetupWizard.tsx:177
msgid "Setting up..."
msgstr "Mise en place en cours..."
@@ -4496,24 +4498,35 @@ msgstr "S'affiche lorsque vous survolez l'image."
msgid "Sign in"
msgstr "Se connecter"
+#: packages/admin/src/components/SetupWizard.tsx:382
+msgid "Sign In"
+msgstr ""
+
#: packages/admin/src/components/SignupPage.tsx:269
msgid "Sign in instead"
msgstr "Se connecter"
-#: packages/admin/src/components/LoginPage.tsx:291
+#: packages/admin/src/components/LoginPage.tsx:240
msgid "Sign in to your site"
msgstr "Se connecter à votre site"
-#: packages/admin/src/components/LoginPage.tsx:292
+#. placeholder {0}: authProviderList.find((p) => p.id === activeProvider)?.label ?? activeProvider
+#. placeholder {0}: provider.label
+#: packages/admin/src/components/LoginPage.tsx:239
+#: packages/admin/src/components/SetupWizard.tsx:291
+msgid "Sign in with {0}"
+msgstr ""
+
+#: packages/admin/src/components/LoginPage.tsx:237
msgid "Sign in with email"
msgstr "Se connecter avec un e-mail"
-#: packages/admin/src/components/LoginPage.tsx:348
+#: packages/admin/src/components/LoginPage.tsx:298
msgid "Sign in with email link"
msgstr "Se connecter avec un lien envoyé par e-mail"
#: packages/admin/src/components/auth/PasskeyLogin.tsx:136
-#: packages/admin/src/components/LoginPage.tsx:312
+#: packages/admin/src/components/LoginPage.tsx:260
msgid "Sign in with Passkey"
msgstr "Se connecter avec une clé d'accès"
@@ -4530,7 +4543,7 @@ msgstr "Choix unique parmi les options"
msgid "Single line text input"
msgstr "Champ de texte sur une seule ligne"
-#: packages/admin/src/components/SetupWizard.tsx:331
+#: packages/admin/src/components/SetupWizard.tsx:380
msgid "Site"
msgstr "Site"
@@ -4542,12 +4555,12 @@ msgstr "Identité du site"
msgid "Site identity, logo, favicon, and reading preferences"
msgstr "Identité du site, logo, favicon et préférences de lecture"
-#: packages/admin/src/components/SetupWizard.tsx:329
+#: packages/admin/src/components/SetupWizard.tsx:378
msgid "Site Settings"
msgstr "Paramètres du site"
#: packages/admin/src/components/settings/GeneralSettings.tsx:156
-#: packages/admin/src/components/SetupWizard.tsx:141
+#: packages/admin/src/components/SetupWizard.tsx:143
msgid "Site Title"
msgstr "Titre du site"
@@ -4555,7 +4568,7 @@ msgstr "Titre du site"
msgid "Site title & tagline"
msgstr "Titre et slogan du site"
-#: packages/admin/src/components/SetupWizard.tsx:125
+#: packages/admin/src/components/SetupWizard.tsx:127
msgid "Site title is required"
msgstr "Le titre du site est obligatoire"
@@ -4699,7 +4712,7 @@ msgid "System ({resolvedLabel})"
msgstr "Système ({resolvedLabel})"
#: packages/admin/src/components/settings/GeneralSettings.tsx:162
-#: packages/admin/src/components/SetupWizard.tsx:152
+#: packages/admin/src/components/SetupWizard.tsx:154
msgid "Tagline"
msgstr "Slogan"
@@ -4737,7 +4750,7 @@ msgstr "Taxonomie créée"
msgid "Taxonomy not found:"
msgstr "Taxonomie introuvable :"
-#: packages/admin/src/components/SetupWizard.tsx:180
+#: packages/admin/src/components/SetupWizard.tsx:182
msgid "Template:"
msgstr "Modèle :"
@@ -4770,7 +4783,7 @@ msgstr "Les tableaux suivants contiennent du contenu mais ne sont pas enregistr
msgid "The invited user will have this role once they complete registration."
msgstr "L'utilisateur invité aura ce rôle une fois son inscription terminée."
-#: packages/admin/src/components/LoginPage.tsx:170
+#: packages/admin/src/components/LoginPage.tsx:113
#: packages/admin/src/components/SignupPage.tsx:138
msgid "The link will expire in 15 minutes."
msgstr "Le lien expirera dans 15 minutes."
@@ -4895,7 +4908,7 @@ msgstr "Cela supprimera le module d'extension et son paquet de votre site."
msgid "This will revert to the published version. Your draft changes will be lost."
msgstr "Cela rétablira la version publiée. Vos modifications en cours seront perdues."
-#: packages/admin/src/components/SetupWizard.tsx:156
+#: packages/admin/src/components/SetupWizard.tsx:158
msgid "Thoughts, tutorials, and more"
msgstr "Réflexions, tutoriels et bien plus encore"
@@ -5261,7 +5274,7 @@ msgstr "Utilisez [param] ou [...rest] dans les chemins pour la correspondance de
msgid "Use your device's biometric authentication, security key, or PIN to sign in."
msgstr "Utilisez l'authentification biométrique, la clé de sécurité ou le code PIN de votre appareil pour vous connecter."
-#: packages/admin/src/components/LoginPage.tsx:359
+#: packages/admin/src/components/LoginPage.tsx:334
msgid "Use your registered passkey to sign in securely."
msgstr "Utilisez votre clé d'accès enregistrée pour vous connecter en toute sécurité."
@@ -5379,7 +5392,7 @@ msgstr "Nous n'avons pas pu nous connecter à un site WordPress à l'adresse {0}
msgid "We'll check what import options are available for your site."
msgstr "Nous vérifierons quelles options d'importation sont disponibles pour votre site."
-#: packages/admin/src/components/LoginPage.tsx:360
+#: packages/admin/src/components/LoginPage.tsx:331
msgid "We'll send you a link to sign in without a password."
msgstr "Nous vous enverrons un lien pour vous connecter sans mot de passe."
@@ -5516,7 +5529,7 @@ msgstr "Vous serez redirigé vers WordPress pour autoriser la connexion."
msgid "You'll be signing up as"
msgstr "Vous serez inscrit en tant que"
-#: packages/admin/src/components/SetupWizard.tsx:509
+#: packages/admin/src/components/SetupWizard.tsx:575
msgid "You're signed in via Cloudflare Access"
msgstr "Vous êtes connecté via Cloudflare Access"
@@ -5525,7 +5538,7 @@ msgid "you@company.com"
msgstr "vous@entreprise.com"
#: packages/admin/src/components/auth/PasskeyLogin.tsx:336
-#: packages/admin/src/components/SetupWizard.tsx:226
+#: packages/admin/src/components/SetupWizard.tsx:228
msgid "you@example.com"
msgstr "vous@exemple.com"
@@ -5543,7 +5556,7 @@ msgstr "Votre navigateur ne prend pas en charge les mots de passe. Veuillez util
msgid "Your device doesn't support the required security features."
msgstr "Votre appareil ne prend pas en charge les fonctionnalités de sécurité requises."
-#: packages/admin/src/components/SetupWizard.tsx:222
+#: packages/admin/src/components/SetupWizard.tsx:224
msgid "Your Email"
msgstr "Votre e-mail"
@@ -5563,7 +5576,7 @@ msgstr "Le nom d'utilisateur de votre compte Instagram"
msgid "Your LinkedIn profile username"
msgstr "Le nom d'utilisateur de votre compte LinkedIn"
-#: packages/admin/src/components/SetupWizard.tsx:234
+#: packages/admin/src/components/SetupWizard.tsx:236
msgid "Your Name"
msgstr "Votre nom"
diff --git a/packages/admin/src/locales/ja/messages.po b/packages/admin/src/locales/ja/messages.po
index 6fb945796..bf2bc0f28 100644
--- a/packages/admin/src/locales/ja/messages.po
+++ b/packages/admin/src/locales/ja/messages.po
@@ -59,7 +59,7 @@ msgid "{0, plural, one {(# item)} other {(# items)}}"
msgstr "{0, plural, one {(#件)} other {(#件)}}"
#. placeholder {0}: seedInfo.collections
-#: packages/admin/src/components/SetupWizard.tsx:181
+#: packages/admin/src/components/SetupWizard.tsx:183
msgid "{0, plural, one {# collection} other {# collections}}"
msgstr "{0, plural, one {#個のコレクション} other {#個のコレクション}}"
@@ -315,8 +315,9 @@ msgstr "• EmDashのメディアストレージにアップロード"
msgid "• URLs in your content are updated automatically"
msgstr "• コンテンツ内のURLが自動的に更新されます"
-#: packages/admin/src/components/SetupWizard.tsx:250
-#: packages/admin/src/components/SetupWizard.tsx:310
+#: packages/admin/src/components/SetupWizard.tsx:252
+#: packages/admin/src/components/SetupWizard.tsx:304
+#: packages/admin/src/components/SetupWizard.tsx:359
#: packages/admin/src/components/WordPressImport.tsx:1431
msgid "← Back"
msgstr "← 戻る"
@@ -409,7 +410,7 @@ msgstr "承認してインストール"
msgid "Accept & Update"
msgstr "承認して更新"
-#: packages/admin/src/components/SetupWizard.tsx:332
+#: packages/admin/src/components/SetupWizard.tsx:381
msgid "Account"
msgstr "アカウント"
@@ -694,7 +695,7 @@ msgstr "WordPressの著者をEmDashユーザーに割り当てます。投稿は
msgid "Authentication error: {0}"
msgstr "認証エラー: {0}"
-#: packages/admin/src/components/LoginPage.tsx:253
+#: packages/admin/src/components/LoginPage.tsx:195
msgid "Authentication error: {error}"
msgstr "認証エラー: {error}"
@@ -764,8 +765,9 @@ msgstr "戻る"
msgid "Back to {collectionLabel} list"
msgstr "{collectionLabel}一覧に戻る"
-#: packages/admin/src/components/LoginPage.tsx:174
-#: packages/admin/src/components/LoginPage.tsx:210
+#: packages/admin/src/components/LoginPage.tsx:117
+#: packages/admin/src/components/LoginPage.tsx:153
+#: packages/admin/src/components/LoginPage.tsx:319
#: packages/admin/src/components/SignupPage.tsx:280
msgid "Back to login"
msgstr "ログインに戻る"
@@ -957,7 +959,7 @@ msgstr "更新を確認"
msgid "Check Site"
msgstr "サイトを確認"
-#: packages/admin/src/components/LoginPage.tsx:158
+#: packages/admin/src/components/LoginPage.tsx:101
#: packages/admin/src/components/SignupPage.tsx:129
#: packages/admin/src/components/SignupPage.tsx:401
msgid "Check your email"
@@ -971,6 +973,10 @@ msgstr "{urlInput}を確認中..."
msgid "Checking authentication..."
msgstr "認証を確認中..."
+#: packages/admin/src/components/SetupWizard.tsx:315
+msgid "Choose how to sign in"
+msgstr ""
+
#: packages/admin/src/components/Settings.tsx:130
msgid "Choose your preferred admin language"
msgstr "管理画面の言語を選択"
@@ -979,7 +985,7 @@ msgstr "管理画面の言語を選択"
msgid "Click the link in the email to continue setting up your account."
msgstr "メール内のリンクをクリックしてアカウントの設定を続けてください。"
-#: packages/admin/src/components/LoginPage.tsx:169
+#: packages/admin/src/components/LoginPage.tsx:112
msgid "Click the link in the email to sign in."
msgstr "メール内のリンクをクリックしてサインインしてください。"
@@ -1185,8 +1191,8 @@ msgstr "コンテンツ書き込み"
msgid "Continue"
msgstr "続行"
-#: packages/admin/src/components/SetupWizard.tsx:175
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:177
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Continue →"
msgstr "続行 →"
@@ -1291,7 +1297,7 @@ msgstr "新しいトークンを作成"
msgid "Create one in WordPress: Users → Profile → Application Passwords"
msgstr "WordPressで作成: ユーザー → プロフィール → アプリケーションパスワード"
-#: packages/admin/src/components/SetupWizard.tsx:305
+#: packages/admin/src/components/SetupWizard.tsx:325
msgid "Create Passkey"
msgstr "パスキーを作成"
@@ -1336,7 +1342,7 @@ msgstr "タクソノミーを作成"
msgid "Create Token"
msgstr "トークンを作成"
-#: packages/admin/src/components/SetupWizard.tsx:505
+#: packages/admin/src/components/SetupWizard.tsx:571
msgid "Create your account"
msgstr "アカウントを作成"
@@ -1724,7 +1730,7 @@ msgstr "ドメインを削除しました"
msgid "Domain updated"
msgstr "ドメインを更新しました"
-#: packages/admin/src/components/LoginPage.tsx:366
+#: packages/admin/src/components/LoginPage.tsx:340
msgid "Don't have an account? <0>Sign up0>"
msgstr "アカウントをお持ちでないですか? <0>新規登録0>"
@@ -1853,13 +1859,13 @@ msgstr "メール"
msgid "Email (optional)"
msgstr "メール(任意)"
-#: packages/admin/src/components/LoginPage.tsx:183
+#: packages/admin/src/components/LoginPage.tsx:126
#: packages/admin/src/components/SignupPage.tsx:65
#: packages/admin/src/components/users/InviteUserModal.tsx:154
msgid "Email address"
msgstr "メールアドレス"
-#: packages/admin/src/components/SetupWizard.tsx:204
+#: packages/admin/src/components/SetupWizard.tsx:206
#: packages/admin/src/components/SignupPage.tsx:48
msgid "Email is required"
msgstr "メールアドレスは必須です"
@@ -1965,6 +1971,10 @@ msgstr "名前を入力"
msgid "Enter the code from your terminal"
msgstr "ターミナルに表示されたコードを入力"
+#: packages/admin/src/components/LoginPage.tsx:333
+msgid "Enter your handle to sign in."
+msgstr ""
+
#: packages/admin/src/components/WordPressImport.tsx:1289
msgid "Enter your WordPress credentials to import content directly."
msgstr "WordPressの認証情報を入力して直接コンテンツをインポートします。"
@@ -1975,7 +1985,7 @@ msgstr "WordPressサイトのURLを入力"
#: packages/admin/src/components/MenuEditor.tsx:83
#: packages/admin/src/components/MenuEditor.tsx:122
-#: packages/admin/src/components/SetupWizard.tsx:484
+#: packages/admin/src/components/SetupWizard.tsx:550
msgid "Error"
msgstr "エラー"
@@ -2089,7 +2099,7 @@ msgstr "プラグインの読み込みに失敗しました: {0}"
msgid "Failed to load revisions"
msgstr "リビジョンの読み込みに失敗しました"
-#: packages/admin/src/components/SetupWizard.tsx:486
+#: packages/admin/src/components/SetupWizard.tsx:552
msgid "Failed to load setup"
msgstr "セットアップの読み込みに失敗しました"
@@ -2120,8 +2130,8 @@ msgstr "保存に失敗しました"
msgid "Failed to save settings"
msgstr "設定の保存に失敗しました"
-#: packages/admin/src/components/LoginPage.tsx:127
-#: packages/admin/src/components/LoginPage.tsx:132
+#: packages/admin/src/components/LoginPage.tsx:70
+#: packages/admin/src/components/LoginPage.tsx:75
msgid "Failed to send magic link"
msgstr "マジックリンクの送信に失敗しました"
@@ -2328,7 +2338,7 @@ msgstr "画像監査によりアイコンがぼかされています"
msgid "ID"
msgstr "ID"
-#: packages/admin/src/components/LoginPage.tsx:160
+#: packages/admin/src/components/LoginPage.tsx:103
msgid "If an account exists for <0>{email}0>, we've sent a sign-in link."
msgstr "<0>{email}0>のアカウントが存在する場合、サインインリンクを送信しました。"
@@ -2448,7 +2458,7 @@ msgstr "コンテンツをインポート中..."
msgid "Importing Media"
msgstr "メディアをインポート中"
-#: packages/admin/src/components/SetupWizard.tsx:163
+#: packages/admin/src/components/SetupWizard.tsx:165
msgid "Include sample content (recommended for new sites)"
msgstr "サンプルコンテンツを含める(新しいサイトに推奨)"
@@ -2561,7 +2571,7 @@ msgstr "アイテムを削除しました"
msgid "Item updated"
msgstr "アイテムを更新しました"
-#: packages/admin/src/components/SetupWizard.tsx:238
+#: packages/admin/src/components/SetupWizard.tsx:240
#: packages/admin/src/components/SignupPage.tsx:204
msgid "Jane Doe"
msgstr "山田太郎"
@@ -2585,7 +2595,7 @@ msgstr "キーワード"
msgid "Label"
msgstr "ラベル"
-#: packages/admin/src/components/LoginPage.tsx:379
+#: packages/admin/src/components/LoginPage.tsx:353
#: packages/admin/src/components/Settings.tsx:129
#: packages/admin/src/components/Settings.tsx:134
msgid "Language"
@@ -2725,7 +2735,7 @@ msgstr "セクションを読み込み中..."
msgid "Loading settings..."
msgstr "設定を読み込み中..."
-#: packages/admin/src/components/SetupWizard.tsx:473
+#: packages/admin/src/components/SetupWizard.tsx:539
msgid "Loading setup..."
msgstr "セットアップを読み込み中..."
@@ -3021,7 +3031,7 @@ msgstr "複数行プレーンテキスト"
msgid "Multiple choices from options"
msgstr "選択肢から複数選択"
-#: packages/admin/src/components/SetupWizard.tsx:145
+#: packages/admin/src/components/SetupWizard.tsx:147
msgid "My Awesome Blog"
msgstr "マイブログ"
@@ -3403,7 +3413,7 @@ msgstr "またはライブラリから選択"
msgid "Or click to browse. Accepts .xml files exported from WordPress."
msgstr "または、クリックして参照。WordPressからエクスポートした.xmlファイルを受け付けます。"
-#: packages/admin/src/components/LoginPage.tsx:321
+#: packages/admin/src/components/LoginPage.tsx:269
msgid "Or continue with"
msgstr "または次の方法で続行"
@@ -3462,10 +3472,6 @@ msgstr "リダイレクトループの一部"
msgid "Pass"
msgstr "合格"
-#: packages/admin/src/components/SetupWizard.tsx:333
-msgid "Passkey"
-msgstr "パスキー"
-
#: packages/admin/src/components/settings/SecuritySettings.tsx:98
msgid "Passkey added successfully"
msgstr "パスキーを追加しました"
@@ -3507,10 +3513,6 @@ msgstr "パスキーは安全でパスワード不要のサインイン方法で
msgid "Passkeys are a secure, passwordless way to sign in using your device's biometrics, PIN, or security key."
msgstr "パスキーはデバイスの生体認証、PIN、またはセキュリティキーを使って安全にサインインできるパスワード不要の方法です。"
-#: packages/admin/src/components/SetupWizard.tsx:297
-msgid "Passkeys are more secure than passwords. You'll use your device's biometrics, PIN, or security key to sign in."
-msgstr "パスキーはパスワードよりも安全です。デバイスの生体認証、PIN、またはセキュリティキーでサインインします。"
-
#: packages/admin/src/components/auth/PasskeyLogin.tsx:303
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:300
msgid "Passkeys Not Available Here"
@@ -3558,12 +3560,16 @@ msgstr "{title}を完全に削除"
msgid "Permissions"
msgstr "権限"
+#: packages/admin/src/components/SetupWizard.tsx:317
+msgid "Pick any method to create your admin account."
+msgstr ""
+
#: packages/admin/src/components/auth/PasskeyLogin.tsx:313
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:310
msgid "Plain"
msgstr "プレーン"
-#: packages/admin/src/components/SetupWizard.tsx:206
+#: packages/admin/src/components/SetupWizard.tsx:208
msgid "Please enter a valid email"
msgstr "有効なメールアドレスを入力してください"
@@ -3637,7 +3643,7 @@ msgid "Preparing to download files from WordPress..."
msgstr "WordPressからのファイルダウンロードを準備中..."
#: packages/admin/src/components/auth/PasskeyLogin.tsx:166
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Preparing..."
msgstr "準備中..."
@@ -4264,7 +4270,7 @@ msgstr "セクション"
msgid "secure context"
msgstr "セキュアコンテキスト"
-#: packages/admin/src/components/SetupWizard.tsx:506
+#: packages/admin/src/components/SetupWizard.tsx:572
msgid "Secure your account"
msgstr "アカウントを保護"
@@ -4388,7 +4394,7 @@ msgstr "新しいチームメンバーに招待メールを送信します。"
msgid "Send Invite"
msgstr "招待を送信"
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
msgid "Send magic link"
msgstr "マジックリンクを送信"
@@ -4404,7 +4410,7 @@ msgstr "テストを送信"
msgid "Send Test Email"
msgstr "テストメールを送信"
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
#: packages/admin/src/components/settings/EmailSettings.tsx:162
#: packages/admin/src/components/SignupPage.tsx:87
#: packages/admin/src/components/SignupPage.tsx:150
@@ -4440,15 +4446,11 @@ msgstr "SEOタイトル"
msgid "Set a custom display size for this image instance."
msgstr "この画像インスタンスのカスタム表示サイズを設定します。"
-#: packages/admin/src/components/SetupWizard.tsx:295
-msgid "Set up your passkey"
-msgstr "パスキーを設定"
-
-#: packages/admin/src/components/SetupWizard.tsx:504
+#: packages/admin/src/components/SetupWizard.tsx:570
msgid "Set up your site"
msgstr "サイトをセットアップ"
-#: packages/admin/src/components/SetupWizard.tsx:175
+#: packages/admin/src/components/SetupWizard.tsx:177
msgid "Setting up..."
msgstr "セットアップ中..."
@@ -4496,24 +4498,35 @@ msgstr "画像にカーソルを合わせた時に表示されます。"
msgid "Sign in"
msgstr "サインイン"
+#: packages/admin/src/components/SetupWizard.tsx:382
+msgid "Sign In"
+msgstr ""
+
#: packages/admin/src/components/SignupPage.tsx:269
msgid "Sign in instead"
msgstr "サインインする"
-#: packages/admin/src/components/LoginPage.tsx:291
+#: packages/admin/src/components/LoginPage.tsx:240
msgid "Sign in to your site"
msgstr "サイトにサインイン"
-#: packages/admin/src/components/LoginPage.tsx:292
+#. placeholder {0}: authProviderList.find((p) => p.id === activeProvider)?.label ?? activeProvider
+#. placeholder {0}: provider.label
+#: packages/admin/src/components/LoginPage.tsx:239
+#: packages/admin/src/components/SetupWizard.tsx:291
+msgid "Sign in with {0}"
+msgstr ""
+
+#: packages/admin/src/components/LoginPage.tsx:237
msgid "Sign in with email"
msgstr "メールでサインイン"
-#: packages/admin/src/components/LoginPage.tsx:348
+#: packages/admin/src/components/LoginPage.tsx:298
msgid "Sign in with email link"
msgstr "メールリンクでサインイン"
#: packages/admin/src/components/auth/PasskeyLogin.tsx:136
-#: packages/admin/src/components/LoginPage.tsx:312
+#: packages/admin/src/components/LoginPage.tsx:260
msgid "Sign in with Passkey"
msgstr "パスキーでサインイン"
@@ -4530,7 +4543,7 @@ msgstr "選択肢から1つ選択"
msgid "Single line text input"
msgstr "1行テキスト入力"
-#: packages/admin/src/components/SetupWizard.tsx:331
+#: packages/admin/src/components/SetupWizard.tsx:380
msgid "Site"
msgstr "サイト"
@@ -4542,12 +4555,12 @@ msgstr "サイト情報"
msgid "Site identity, logo, favicon, and reading preferences"
msgstr "サイト情報、ロゴ、ファビコン、閲覧設定"
-#: packages/admin/src/components/SetupWizard.tsx:329
+#: packages/admin/src/components/SetupWizard.tsx:378
msgid "Site Settings"
msgstr "サイト設定"
#: packages/admin/src/components/settings/GeneralSettings.tsx:156
-#: packages/admin/src/components/SetupWizard.tsx:141
+#: packages/admin/src/components/SetupWizard.tsx:143
msgid "Site Title"
msgstr "サイトタイトル"
@@ -4555,7 +4568,7 @@ msgstr "サイトタイトル"
msgid "Site title & tagline"
msgstr "サイトタイトルとキャッチフレーズ"
-#: packages/admin/src/components/SetupWizard.tsx:125
+#: packages/admin/src/components/SetupWizard.tsx:127
msgid "Site title is required"
msgstr "サイトタイトルは必須です"
@@ -4699,7 +4712,7 @@ msgid "System ({resolvedLabel})"
msgstr "システム ({resolvedLabel})"
#: packages/admin/src/components/settings/GeneralSettings.tsx:162
-#: packages/admin/src/components/SetupWizard.tsx:152
+#: packages/admin/src/components/SetupWizard.tsx:154
msgid "Tagline"
msgstr "キャッチフレーズ"
@@ -4737,7 +4750,7 @@ msgstr "タクソノミーを作成しました"
msgid "Taxonomy not found:"
msgstr "タクソノミーが見つかりません:"
-#: packages/admin/src/components/SetupWizard.tsx:180
+#: packages/admin/src/components/SetupWizard.tsx:182
msgid "Template:"
msgstr "テンプレート:"
@@ -4770,7 +4783,7 @@ msgstr "以下のテーブルにはコンテンツがありますが、コレク
msgid "The invited user will have this role once they complete registration."
msgstr "招待されたユーザーは登録完了後にこのロールが割り当てられます。"
-#: packages/admin/src/components/LoginPage.tsx:170
+#: packages/admin/src/components/LoginPage.tsx:113
#: packages/admin/src/components/SignupPage.tsx:138
msgid "The link will expire in 15 minutes."
msgstr "リンクの有効期限は15分です。"
@@ -4895,7 +4908,7 @@ msgstr "プラグインとそのバンドルがサイトから削除されます
msgid "This will revert to the published version. Your draft changes will be lost."
msgstr "公開済みバージョンに戻ります。下書きの変更は失われます。"
-#: packages/admin/src/components/SetupWizard.tsx:156
+#: packages/admin/src/components/SetupWizard.tsx:158
msgid "Thoughts, tutorials, and more"
msgstr "記事、チュートリアルなど"
@@ -5261,7 +5274,7 @@ msgstr "パスパターンマッチングには[param]や[...rest]を使用し
msgid "Use your device's biometric authentication, security key, or PIN to sign in."
msgstr "デバイスの生体認証、セキュリティキー、またはPINでサインインしてください。"
-#: packages/admin/src/components/LoginPage.tsx:359
+#: packages/admin/src/components/LoginPage.tsx:334
msgid "Use your registered passkey to sign in securely."
msgstr "登録済みのパスキーで安全にサインインできます。"
@@ -5379,7 +5392,7 @@ msgstr "{0}のWordPressサイトに接続できませんでした。サイトが
msgid "We'll check what import options are available for your site."
msgstr "サイトで利用可能なインポートオプションを確認します。"
-#: packages/admin/src/components/LoginPage.tsx:360
+#: packages/admin/src/components/LoginPage.tsx:331
msgid "We'll send you a link to sign in without a password."
msgstr "パスワード不要のサインインリンクをお送りします。"
@@ -5516,7 +5529,7 @@ msgstr "接続を認可するためにWordPressにリダイレクトされます
msgid "You'll be signing up as"
msgstr "登録されるアカウント:"
-#: packages/admin/src/components/SetupWizard.tsx:509
+#: packages/admin/src/components/SetupWizard.tsx:575
msgid "You're signed in via Cloudflare Access"
msgstr "Cloudflare Accessでサインイン中です"
@@ -5525,7 +5538,7 @@ msgid "you@company.com"
msgstr "you@company.com"
#: packages/admin/src/components/auth/PasskeyLogin.tsx:336
-#: packages/admin/src/components/SetupWizard.tsx:226
+#: packages/admin/src/components/SetupWizard.tsx:228
msgid "you@example.com"
msgstr "you@example.com"
@@ -5543,7 +5556,7 @@ msgstr "お使いのブラウザはパスキーに対応していません。Chr
msgid "Your device doesn't support the required security features."
msgstr "お使いのデバイスは必要なセキュリティ機能に対応していません。"
-#: packages/admin/src/components/SetupWizard.tsx:222
+#: packages/admin/src/components/SetupWizard.tsx:224
msgid "Your Email"
msgstr "メールアドレス"
@@ -5563,7 +5576,7 @@ msgstr "Instagramのユーザー名"
msgid "Your LinkedIn profile username"
msgstr "LinkedInのプロフィールユーザー名"
-#: packages/admin/src/components/SetupWizard.tsx:234
+#: packages/admin/src/components/SetupWizard.tsx:236
msgid "Your Name"
msgstr "名前"
diff --git a/packages/admin/src/locales/ko/messages.po b/packages/admin/src/locales/ko/messages.po
index 044105bf6..08f238e4f 100644
--- a/packages/admin/src/locales/ko/messages.po
+++ b/packages/admin/src/locales/ko/messages.po
@@ -59,7 +59,7 @@ msgid "{0, plural, one {(# item)} other {(# items)}}"
msgstr "{0, plural, one {(#개 항목)} other {(#개 항목)}}"
#. placeholder {0}: seedInfo.collections
-#: packages/admin/src/components/SetupWizard.tsx:181
+#: packages/admin/src/components/SetupWizard.tsx:183
msgid "{0, plural, one {# collection} other {# collections}}"
msgstr "{0, plural, one {#개 컬렉션} other {#개 컬렉션}}"
@@ -315,8 +315,9 @@ msgstr "• EmDash 미디어 저장소에 업로드됨"
msgid "• URLs in your content are updated automatically"
msgstr "• 콘텐츠의 URL이 자동으로 업데이트됩니다."
-#: packages/admin/src/components/SetupWizard.tsx:250
-#: packages/admin/src/components/SetupWizard.tsx:310
+#: packages/admin/src/components/SetupWizard.tsx:252
+#: packages/admin/src/components/SetupWizard.tsx:304
+#: packages/admin/src/components/SetupWizard.tsx:359
#: packages/admin/src/components/WordPressImport.tsx:1431
msgid "← Back"
msgstr "← 뒤로"
@@ -409,7 +410,7 @@ msgstr "동의 및 설치"
msgid "Accept & Update"
msgstr "동의 및 업데이트"
-#: packages/admin/src/components/SetupWizard.tsx:332
+#: packages/admin/src/components/SetupWizard.tsx:381
msgid "Account"
msgstr "계정"
@@ -694,7 +695,7 @@ msgstr "EmDash 사용자에게 WordPress 작성자를 할당합니다. 게시물
msgid "Authentication error: {0}"
msgstr "인증 오류: {0}"
-#: packages/admin/src/components/LoginPage.tsx:253
+#: packages/admin/src/components/LoginPage.tsx:195
msgid "Authentication error: {error}"
msgstr "인증 오류: {error}"
@@ -764,8 +765,9 @@ msgstr "뒤로"
msgid "Back to {collectionLabel} list"
msgstr "{collectionLabel} 목록으로 돌아가기"
-#: packages/admin/src/components/LoginPage.tsx:174
-#: packages/admin/src/components/LoginPage.tsx:210
+#: packages/admin/src/components/LoginPage.tsx:117
+#: packages/admin/src/components/LoginPage.tsx:153
+#: packages/admin/src/components/LoginPage.tsx:319
#: packages/admin/src/components/SignupPage.tsx:280
msgid "Back to login"
msgstr "로그인으로 돌아가기"
@@ -957,7 +959,7 @@ msgstr "업데이트 확인"
msgid "Check Site"
msgstr "사이트 확인"
-#: packages/admin/src/components/LoginPage.tsx:158
+#: packages/admin/src/components/LoginPage.tsx:101
#: packages/admin/src/components/SignupPage.tsx:129
#: packages/admin/src/components/SignupPage.tsx:401
msgid "Check your email"
@@ -971,6 +973,10 @@ msgstr "{urlInput} 확인 중..."
msgid "Checking authentication..."
msgstr "인증 확인 중..."
+#: packages/admin/src/components/SetupWizard.tsx:315
+msgid "Choose how to sign in"
+msgstr ""
+
#: packages/admin/src/components/Settings.tsx:130
msgid "Choose your preferred admin language"
msgstr "선호하는 관리 언어를 선택하세요"
@@ -979,7 +985,7 @@ msgstr "선호하는 관리 언어를 선택하세요"
msgid "Click the link in the email to continue setting up your account."
msgstr "계정 설정을 계속하려면 이메일에 있는 링크를 클릭하세요."
-#: packages/admin/src/components/LoginPage.tsx:169
+#: packages/admin/src/components/LoginPage.tsx:112
msgid "Click the link in the email to sign in."
msgstr "이메일에 있는 링크를 클릭하여 로그인하세요."
@@ -1185,8 +1191,8 @@ msgstr "콘텐츠 쓰기"
msgid "Continue"
msgstr "계속"
-#: packages/admin/src/components/SetupWizard.tsx:175
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:177
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Continue →"
msgstr "계속 →"
@@ -1291,7 +1297,7 @@ msgstr "새 토큰 만들기"
msgid "Create one in WordPress: Users → Profile → Application Passwords"
msgstr "WordPress에서 하나 만들기: 사용자 → 프로필 → 애플리케이션 비밀번호"
-#: packages/admin/src/components/SetupWizard.tsx:305
+#: packages/admin/src/components/SetupWizard.tsx:325
msgid "Create Passkey"
msgstr "패스키 생성"
@@ -1336,7 +1342,7 @@ msgstr "분류 만들기"
msgid "Create Token"
msgstr "토큰 만들기"
-#: packages/admin/src/components/SetupWizard.tsx:505
+#: packages/admin/src/components/SetupWizard.tsx:571
msgid "Create your account"
msgstr "계정 만들기"
@@ -1724,7 +1730,7 @@ msgstr "도메인이 삭제됨"
msgid "Domain updated"
msgstr "도메인이 업데이트되었습니다."
-#: packages/admin/src/components/LoginPage.tsx:366
+#: packages/admin/src/components/LoginPage.tsx:340
msgid "Don't have an account? <0>Sign up0>"
msgstr "계정이 없으신가요? <0>회원가입0>"
@@ -1853,13 +1859,13 @@ msgstr "이메일"
msgid "Email (optional)"
msgstr "이메일(선택사항)"
-#: packages/admin/src/components/LoginPage.tsx:183
+#: packages/admin/src/components/LoginPage.tsx:126
#: packages/admin/src/components/SignupPage.tsx:65
#: packages/admin/src/components/users/InviteUserModal.tsx:154
msgid "Email address"
msgstr "이메일 주소"
-#: packages/admin/src/components/SetupWizard.tsx:204
+#: packages/admin/src/components/SetupWizard.tsx:206
#: packages/admin/src/components/SignupPage.tsx:48
msgid "Email is required"
msgstr "이메일은 필수입니다"
@@ -1965,6 +1971,10 @@ msgstr "이름을 입력하세요"
msgid "Enter the code from your terminal"
msgstr "터미널에서 코드를 입력하세요"
+#: packages/admin/src/components/LoginPage.tsx:333
+msgid "Enter your handle to sign in."
+msgstr ""
+
#: packages/admin/src/components/WordPressImport.tsx:1289
msgid "Enter your WordPress credentials to import content directly."
msgstr "콘텐츠를 직접 가져오려면 WordPress 자격 증명을 입력하세요."
@@ -1975,7 +1985,7 @@ msgstr "WordPress 사이트 URL을 입력하세요."
#: packages/admin/src/components/MenuEditor.tsx:83
#: packages/admin/src/components/MenuEditor.tsx:122
-#: packages/admin/src/components/SetupWizard.tsx:484
+#: packages/admin/src/components/SetupWizard.tsx:550
msgid "Error"
msgstr "오류"
@@ -2089,7 +2099,7 @@ msgstr "플러그인을 로드하지 못했습니다: {0}"
msgid "Failed to load revisions"
msgstr "버전을 로드하지 못했습니다."
-#: packages/admin/src/components/SetupWizard.tsx:486
+#: packages/admin/src/components/SetupWizard.tsx:552
msgid "Failed to load setup"
msgstr "설정을 로드하지 못했습니다."
@@ -2120,8 +2130,8 @@ msgstr ""
msgid "Failed to save settings"
msgstr "설정을 저장하지 못했습니다."
-#: packages/admin/src/components/LoginPage.tsx:127
-#: packages/admin/src/components/LoginPage.tsx:132
+#: packages/admin/src/components/LoginPage.tsx:70
+#: packages/admin/src/components/LoginPage.tsx:75
msgid "Failed to send magic link"
msgstr "매직 링크를 보내지 못했습니다."
@@ -2328,7 +2338,7 @@ msgstr "이미지 검수로 인해 아이콘이 흐리게 표시됨"
msgid "ID"
msgstr "ID"
-#: packages/admin/src/components/LoginPage.tsx:160
+#: packages/admin/src/components/LoginPage.tsx:103
msgid "If an account exists for <0>{email}0>, we've sent a sign-in link."
msgstr "<0>{email}0>에 대한 계정이 존재하는 경우 로그인 링크가 전송되었습니다."
@@ -2448,7 +2458,7 @@ msgstr "콘텐츠 가져오는 중..."
msgid "Importing Media"
msgstr "미디어 가져오기"
-#: packages/admin/src/components/SetupWizard.tsx:163
+#: packages/admin/src/components/SetupWizard.tsx:165
msgid "Include sample content (recommended for new sites)"
msgstr "샘플 콘텐츠 포함(새 사이트에 권장)"
@@ -2561,7 +2571,7 @@ msgstr "항목이 삭제되었습니다."
msgid "Item updated"
msgstr "항목이 업데이트되었습니다."
-#: packages/admin/src/components/SetupWizard.tsx:238
+#: packages/admin/src/components/SetupWizard.tsx:240
#: packages/admin/src/components/SignupPage.tsx:204
msgid "Jane Doe"
msgstr "제인 도"
@@ -2585,7 +2595,7 @@ msgstr "키워드"
msgid "Label"
msgstr "라벨"
-#: packages/admin/src/components/LoginPage.tsx:379
+#: packages/admin/src/components/LoginPage.tsx:353
#: packages/admin/src/components/Settings.tsx:129
#: packages/admin/src/components/Settings.tsx:134
msgid "Language"
@@ -2725,7 +2735,7 @@ msgstr "섹션 로드 중..."
msgid "Loading settings..."
msgstr "설정 로드 중..."
-#: packages/admin/src/components/SetupWizard.tsx:473
+#: packages/admin/src/components/SetupWizard.tsx:539
msgid "Loading setup..."
msgstr "설정 로드 중..."
@@ -3021,7 +3031,7 @@ msgstr "여러 줄 일반 텍스트"
msgid "Multiple choices from options"
msgstr "옵션의 다중 선택"
-#: packages/admin/src/components/SetupWizard.tsx:145
+#: packages/admin/src/components/SetupWizard.tsx:147
msgid "My Awesome Blog"
msgstr "내 멋진 블로그"
@@ -3403,7 +3413,7 @@ msgstr "또는 라이브러리에서 선택"
msgid "Or click to browse. Accepts .xml files exported from WordPress."
msgstr "또는 클릭하여 찾아보세요. WordPress에서 내보낸 .xml 파일을 허용합니다."
-#: packages/admin/src/components/LoginPage.tsx:321
+#: packages/admin/src/components/LoginPage.tsx:269
msgid "Or continue with"
msgstr "또는 다음으로 계속"
@@ -3462,10 +3472,6 @@ msgstr "리디렉션 루프의 일부"
msgid "Pass"
msgstr "통과"
-#: packages/admin/src/components/SetupWizard.tsx:333
-msgid "Passkey"
-msgstr "패스키"
-
#: packages/admin/src/components/settings/SecuritySettings.tsx:98
msgid "Passkey added successfully"
msgstr "패스키가 성공적으로 추가되었습니다"
@@ -3507,10 +3513,6 @@ msgstr "패스키는 비밀번호 없이 안전하게 계정에 로그인할 수
msgid "Passkeys are a secure, passwordless way to sign in using your device's biometrics, PIN, or security key."
msgstr "패스키는 장치의 생체 인식, PIN 또는 보안 키를 사용하여 비밀번호 없이 안전하게 로그인하는 방법입니다."
-#: packages/admin/src/components/SetupWizard.tsx:297
-msgid "Passkeys are more secure than passwords. You'll use your device's biometrics, PIN, or security key to sign in."
-msgstr "패스키는 비밀번호보다 더 안전합니다. 로그인하려면 기기의 생체 인식, PIN 또는 보안 키를 사용하세요."
-
#: packages/admin/src/components/auth/PasskeyLogin.tsx:303
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:300
msgid "Passkeys Not Available Here"
@@ -3558,12 +3560,16 @@ msgstr "{title}을(를) 영구적으로 삭제"
msgid "Permissions"
msgstr "권한"
+#: packages/admin/src/components/SetupWizard.tsx:317
+msgid "Pick any method to create your admin account."
+msgstr ""
+
#: packages/admin/src/components/auth/PasskeyLogin.tsx:313
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:310
msgid "Plain"
msgstr "일반"
-#: packages/admin/src/components/SetupWizard.tsx:206
+#: packages/admin/src/components/SetupWizard.tsx:208
msgid "Please enter a valid email"
msgstr "유효한 이메일을 입력해주세요"
@@ -3637,7 +3643,7 @@ msgid "Preparing to download files from WordPress..."
msgstr "WordPress에서 파일 다운로드 준비 중..."
#: packages/admin/src/components/auth/PasskeyLogin.tsx:166
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Preparing..."
msgstr "준비 중..."
@@ -4264,7 +4270,7 @@ msgstr "섹션"
msgid "secure context"
msgstr "보안 환경"
-#: packages/admin/src/components/SetupWizard.tsx:506
+#: packages/admin/src/components/SetupWizard.tsx:572
msgid "Secure your account"
msgstr "계정을 보호하세요"
@@ -4388,7 +4394,7 @@ msgstr "새 팀 구성원에게 초대 이메일을 보냅니다."
msgid "Send Invite"
msgstr "초대 보내기"
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
msgid "Send magic link"
msgstr "로그인 링크 보내기"
@@ -4404,7 +4410,7 @@ msgstr "테스트 보내기"
msgid "Send Test Email"
msgstr "테스트 이메일 보내기"
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
#: packages/admin/src/components/settings/EmailSettings.tsx:162
#: packages/admin/src/components/SignupPage.tsx:87
#: packages/admin/src/components/SignupPage.tsx:150
@@ -4440,15 +4446,11 @@ msgstr "SEO 제목"
msgid "Set a custom display size for this image instance."
msgstr "이 이미지의 표시 크기를 직접 지정합니다."
-#: packages/admin/src/components/SetupWizard.tsx:295
-msgid "Set up your passkey"
-msgstr "패스키 설정"
-
-#: packages/admin/src/components/SetupWizard.tsx:504
+#: packages/admin/src/components/SetupWizard.tsx:570
msgid "Set up your site"
msgstr "사이트 설정"
-#: packages/admin/src/components/SetupWizard.tsx:175
+#: packages/admin/src/components/SetupWizard.tsx:177
msgid "Setting up..."
msgstr "설정 중..."
@@ -4496,24 +4498,35 @@ msgstr "이미지 위로 마우스를 가져가면 표시됩니다."
msgid "Sign in"
msgstr "로그인"
+#: packages/admin/src/components/SetupWizard.tsx:382
+msgid "Sign In"
+msgstr ""
+
#: packages/admin/src/components/SignupPage.tsx:269
msgid "Sign in instead"
msgstr "대신 로그인하세요"
-#: packages/admin/src/components/LoginPage.tsx:291
+#: packages/admin/src/components/LoginPage.tsx:240
msgid "Sign in to your site"
msgstr "사이트에 로그인"
-#: packages/admin/src/components/LoginPage.tsx:292
+#. placeholder {0}: authProviderList.find((p) => p.id === activeProvider)?.label ?? activeProvider
+#. placeholder {0}: provider.label
+#: packages/admin/src/components/LoginPage.tsx:239
+#: packages/admin/src/components/SetupWizard.tsx:291
+msgid "Sign in with {0}"
+msgstr ""
+
+#: packages/admin/src/components/LoginPage.tsx:237
msgid "Sign in with email"
msgstr "이메일로 로그인"
-#: packages/admin/src/components/LoginPage.tsx:348
+#: packages/admin/src/components/LoginPage.tsx:298
msgid "Sign in with email link"
msgstr "이메일 링크로 로그인"
#: packages/admin/src/components/auth/PasskeyLogin.tsx:136
-#: packages/admin/src/components/LoginPage.tsx:312
+#: packages/admin/src/components/LoginPage.tsx:260
msgid "Sign in with Passkey"
msgstr "패스키로 로그인"
@@ -4530,7 +4543,7 @@ msgstr "옵션 중 단일 선택"
msgid "Single line text input"
msgstr "한 줄 텍스트 입력"
-#: packages/admin/src/components/SetupWizard.tsx:331
+#: packages/admin/src/components/SetupWizard.tsx:380
msgid "Site"
msgstr "사이트"
@@ -4542,12 +4555,12 @@ msgstr "사이트 기본 정보"
msgid "Site identity, logo, favicon, and reading preferences"
msgstr "사이트 기본 정보, 로고, 파비콘, 읽기 설정"
-#: packages/admin/src/components/SetupWizard.tsx:329
+#: packages/admin/src/components/SetupWizard.tsx:378
msgid "Site Settings"
msgstr "사이트 설정"
#: packages/admin/src/components/settings/GeneralSettings.tsx:156
-#: packages/admin/src/components/SetupWizard.tsx:141
+#: packages/admin/src/components/SetupWizard.tsx:143
msgid "Site Title"
msgstr "사이트 제목"
@@ -4555,7 +4568,7 @@ msgstr "사이트 제목"
msgid "Site title & tagline"
msgstr "사이트 제목 및 태그라인"
-#: packages/admin/src/components/SetupWizard.tsx:125
+#: packages/admin/src/components/SetupWizard.tsx:127
msgid "Site title is required"
msgstr "사이트 제목이 필요합니다"
@@ -4699,7 +4712,7 @@ msgid "System ({resolvedLabel})"
msgstr "시스템({resolvedLabel})"
#: packages/admin/src/components/settings/GeneralSettings.tsx:162
-#: packages/admin/src/components/SetupWizard.tsx:152
+#: packages/admin/src/components/SetupWizard.tsx:154
msgid "Tagline"
msgstr "사이트 설명"
@@ -4737,7 +4750,7 @@ msgstr "분류가 생성되었습니다."
msgid "Taxonomy not found:"
msgstr "분류를 찾을 수 없습니다:"
-#: packages/admin/src/components/SetupWizard.tsx:180
+#: packages/admin/src/components/SetupWizard.tsx:182
msgid "Template:"
msgstr "템플릿:"
@@ -4770,7 +4783,7 @@ msgstr "다음 테이블에는 콘텐츠가 포함되어 있지만 컬렉션으
msgid "The invited user will have this role once they complete registration."
msgstr "초대된 사용자는 등록을 완료하면 이 역할이 부여 됩니다."
-#: packages/admin/src/components/LoginPage.tsx:170
+#: packages/admin/src/components/LoginPage.tsx:113
#: packages/admin/src/components/SignupPage.tsx:138
msgid "The link will expire in 15 minutes."
msgstr "링크는 15분 후에 만료됩니다."
@@ -4895,7 +4908,7 @@ msgstr "그러면 사이트에서 플러그인과 해당 번들이 제거됩니
msgid "This will revert to the published version. Your draft changes will be lost."
msgstr "게시된 버전으로 되돌아갑니다. 초안 변경사항이 손실됩니다."
-#: packages/admin/src/components/SetupWizard.tsx:156
+#: packages/admin/src/components/SetupWizard.tsx:158
msgid "Thoughts, tutorials, and more"
msgstr "생각, 튜토리얼 등"
@@ -5261,7 +5274,7 @@ msgstr "패턴 일치를 위해 경로에 [param] 또는 [...rest]를 사용하
msgid "Use your device's biometric authentication, security key, or PIN to sign in."
msgstr "기기의 생체 인증, 보안 키 또는 PIN을 사용하여 로그인하세요."
-#: packages/admin/src/components/LoginPage.tsx:359
+#: packages/admin/src/components/LoginPage.tsx:334
msgid "Use your registered passkey to sign in securely."
msgstr "등록된 패스키를 사용하여 안전하게 로그인하세요."
@@ -5379,7 +5392,7 @@ msgstr "{0}에서 WordPress 사이트에 연결하지 못했습니다. WordPress
msgid "We'll check what import options are available for your site."
msgstr "사이트에서 사용 가능한 가져오기 옵션을 확인합니다."
-#: packages/admin/src/components/LoginPage.tsx:360
+#: packages/admin/src/components/LoginPage.tsx:331
msgid "We'll send you a link to sign in without a password."
msgstr "비밀번호 없이 로그인할 수 있는 링크를 이메일로 보내드립니다."
@@ -5516,7 +5529,7 @@ msgstr "연결을 승인하기 위해 WordPress로 이동합니다."
msgid "You'll be signing up as"
msgstr "다음 계정으로 가입합니다:"
-#: packages/admin/src/components/SetupWizard.tsx:509
+#: packages/admin/src/components/SetupWizard.tsx:575
msgid "You're signed in via Cloudflare Access"
msgstr "Cloudflare Access로 로그인되어 있습니다"
@@ -5525,7 +5538,7 @@ msgid "you@company.com"
msgstr "you@company.com"
#: packages/admin/src/components/auth/PasskeyLogin.tsx:336
-#: packages/admin/src/components/SetupWizard.tsx:226
+#: packages/admin/src/components/SetupWizard.tsx:228
msgid "you@example.com"
msgstr "you@example.com"
@@ -5543,7 +5556,7 @@ msgstr "브라우저가 패스키를 지원하지 않습니다. Chrome, Safari,
msgid "Your device doesn't support the required security features."
msgstr "기기가 필수 보안 기능을 지원하지 않습니다."
-#: packages/admin/src/components/SetupWizard.tsx:222
+#: packages/admin/src/components/SetupWizard.tsx:224
msgid "Your Email"
msgstr "이메일"
@@ -5563,7 +5576,7 @@ msgstr "Instagram 사용자 이름"
msgid "Your LinkedIn profile username"
msgstr "LinkedIn 프로필 사용자 이름"
-#: packages/admin/src/components/SetupWizard.tsx:234
+#: packages/admin/src/components/SetupWizard.tsx:236
msgid "Your Name"
msgstr "이름 입력"
diff --git a/packages/admin/src/locales/pseudo/messages.po b/packages/admin/src/locales/pseudo/messages.po
index 5c51b29bd..d8f0f9553 100644
--- a/packages/admin/src/locales/pseudo/messages.po
+++ b/packages/admin/src/locales/pseudo/messages.po
@@ -59,7 +59,7 @@ msgid "{0, plural, one {(# item)} other {(# items)}}"
msgstr ""
#. placeholder {0}: seedInfo.collections
-#: packages/admin/src/components/SetupWizard.tsx:181
+#: packages/admin/src/components/SetupWizard.tsx:183
msgid "{0, plural, one {# collection} other {# collections}}"
msgstr ""
@@ -315,8 +315,9 @@ msgstr ""
msgid "• URLs in your content are updated automatically"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:250
-#: packages/admin/src/components/SetupWizard.tsx:310
+#: packages/admin/src/components/SetupWizard.tsx:252
+#: packages/admin/src/components/SetupWizard.tsx:304
+#: packages/admin/src/components/SetupWizard.tsx:359
#: packages/admin/src/components/WordPressImport.tsx:1431
msgid "← Back"
msgstr ""
@@ -409,7 +410,7 @@ msgstr ""
msgid "Accept & Update"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:332
+#: packages/admin/src/components/SetupWizard.tsx:381
msgid "Account"
msgstr ""
@@ -694,7 +695,7 @@ msgstr ""
msgid "Authentication error: {0}"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:253
+#: packages/admin/src/components/LoginPage.tsx:195
msgid "Authentication error: {error}"
msgstr ""
@@ -764,8 +765,9 @@ msgstr ""
msgid "Back to {collectionLabel} list"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:174
-#: packages/admin/src/components/LoginPage.tsx:210
+#: packages/admin/src/components/LoginPage.tsx:117
+#: packages/admin/src/components/LoginPage.tsx:153
+#: packages/admin/src/components/LoginPage.tsx:319
#: packages/admin/src/components/SignupPage.tsx:280
msgid "Back to login"
msgstr ""
@@ -957,7 +959,7 @@ msgstr ""
msgid "Check Site"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:158
+#: packages/admin/src/components/LoginPage.tsx:101
#: packages/admin/src/components/SignupPage.tsx:129
#: packages/admin/src/components/SignupPage.tsx:401
msgid "Check your email"
@@ -971,6 +973,10 @@ msgstr ""
msgid "Checking authentication..."
msgstr ""
+#: packages/admin/src/components/SetupWizard.tsx:315
+msgid "Choose how to sign in"
+msgstr ""
+
#: packages/admin/src/components/Settings.tsx:130
msgid "Choose your preferred admin language"
msgstr ""
@@ -979,7 +985,7 @@ msgstr ""
msgid "Click the link in the email to continue setting up your account."
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:169
+#: packages/admin/src/components/LoginPage.tsx:112
msgid "Click the link in the email to sign in."
msgstr ""
@@ -1185,8 +1191,8 @@ msgstr ""
msgid "Continue"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:175
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:177
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Continue →"
msgstr ""
@@ -1291,7 +1297,7 @@ msgstr ""
msgid "Create one in WordPress: Users → Profile → Application Passwords"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:305
+#: packages/admin/src/components/SetupWizard.tsx:325
msgid "Create Passkey"
msgstr ""
@@ -1336,7 +1342,7 @@ msgstr ""
msgid "Create Token"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:505
+#: packages/admin/src/components/SetupWizard.tsx:571
msgid "Create your account"
msgstr ""
@@ -1724,7 +1730,7 @@ msgstr ""
msgid "Domain updated"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:366
+#: packages/admin/src/components/LoginPage.tsx:340
msgid "Don't have an account? <0>Sign up0>"
msgstr ""
@@ -1853,13 +1859,13 @@ msgstr ""
msgid "Email (optional)"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:183
+#: packages/admin/src/components/LoginPage.tsx:126
#: packages/admin/src/components/SignupPage.tsx:65
#: packages/admin/src/components/users/InviteUserModal.tsx:154
msgid "Email address"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:204
+#: packages/admin/src/components/SetupWizard.tsx:206
#: packages/admin/src/components/SignupPage.tsx:48
msgid "Email is required"
msgstr ""
@@ -1965,6 +1971,10 @@ msgstr ""
msgid "Enter the code from your terminal"
msgstr ""
+#: packages/admin/src/components/LoginPage.tsx:333
+msgid "Enter your handle to sign in."
+msgstr ""
+
#: packages/admin/src/components/WordPressImport.tsx:1289
msgid "Enter your WordPress credentials to import content directly."
msgstr ""
@@ -1975,7 +1985,7 @@ msgstr ""
#: packages/admin/src/components/MenuEditor.tsx:83
#: packages/admin/src/components/MenuEditor.tsx:122
-#: packages/admin/src/components/SetupWizard.tsx:484
+#: packages/admin/src/components/SetupWizard.tsx:550
msgid "Error"
msgstr ""
@@ -2089,7 +2099,7 @@ msgstr ""
msgid "Failed to load revisions"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:486
+#: packages/admin/src/components/SetupWizard.tsx:552
msgid "Failed to load setup"
msgstr ""
@@ -2120,8 +2130,8 @@ msgstr ""
msgid "Failed to save settings"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:127
-#: packages/admin/src/components/LoginPage.tsx:132
+#: packages/admin/src/components/LoginPage.tsx:70
+#: packages/admin/src/components/LoginPage.tsx:75
msgid "Failed to send magic link"
msgstr ""
@@ -2328,7 +2338,7 @@ msgstr ""
msgid "ID"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:160
+#: packages/admin/src/components/LoginPage.tsx:103
msgid "If an account exists for <0>{email}0>, we've sent a sign-in link."
msgstr ""
@@ -2448,7 +2458,7 @@ msgstr ""
msgid "Importing Media"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:163
+#: packages/admin/src/components/SetupWizard.tsx:165
msgid "Include sample content (recommended for new sites)"
msgstr ""
@@ -2561,7 +2571,7 @@ msgstr ""
msgid "Item updated"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:238
+#: packages/admin/src/components/SetupWizard.tsx:240
#: packages/admin/src/components/SignupPage.tsx:204
msgid "Jane Doe"
msgstr ""
@@ -2585,7 +2595,7 @@ msgstr ""
msgid "Label"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:379
+#: packages/admin/src/components/LoginPage.tsx:353
#: packages/admin/src/components/Settings.tsx:129
#: packages/admin/src/components/Settings.tsx:134
msgid "Language"
@@ -2725,7 +2735,7 @@ msgstr ""
msgid "Loading settings..."
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:473
+#: packages/admin/src/components/SetupWizard.tsx:539
msgid "Loading setup..."
msgstr ""
@@ -3021,7 +3031,7 @@ msgstr ""
msgid "Multiple choices from options"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:145
+#: packages/admin/src/components/SetupWizard.tsx:147
msgid "My Awesome Blog"
msgstr ""
@@ -3403,7 +3413,7 @@ msgstr ""
msgid "Or click to browse. Accepts .xml files exported from WordPress."
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:321
+#: packages/admin/src/components/LoginPage.tsx:269
msgid "Or continue with"
msgstr ""
@@ -3462,10 +3472,6 @@ msgstr ""
msgid "Pass"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:333
-msgid "Passkey"
-msgstr ""
-
#: packages/admin/src/components/settings/SecuritySettings.tsx:98
msgid "Passkey added successfully"
msgstr ""
@@ -3507,10 +3513,6 @@ msgstr ""
msgid "Passkeys are a secure, passwordless way to sign in using your device's biometrics, PIN, or security key."
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:297
-msgid "Passkeys are more secure than passwords. You'll use your device's biometrics, PIN, or security key to sign in."
-msgstr ""
-
#: packages/admin/src/components/auth/PasskeyLogin.tsx:303
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:300
msgid "Passkeys Not Available Here"
@@ -3558,12 +3560,16 @@ msgstr ""
msgid "Permissions"
msgstr ""
+#: packages/admin/src/components/SetupWizard.tsx:317
+msgid "Pick any method to create your admin account."
+msgstr ""
+
#: packages/admin/src/components/auth/PasskeyLogin.tsx:313
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:310
msgid "Plain"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:206
+#: packages/admin/src/components/SetupWizard.tsx:208
msgid "Please enter a valid email"
msgstr ""
@@ -3637,7 +3643,7 @@ msgid "Preparing to download files from WordPress..."
msgstr ""
#: packages/admin/src/components/auth/PasskeyLogin.tsx:166
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Preparing..."
msgstr ""
@@ -4264,7 +4270,7 @@ msgstr ""
msgid "secure context"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:506
+#: packages/admin/src/components/SetupWizard.tsx:572
msgid "Secure your account"
msgstr ""
@@ -4388,7 +4394,7 @@ msgstr ""
msgid "Send Invite"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
msgid "Send magic link"
msgstr ""
@@ -4404,7 +4410,7 @@ msgstr ""
msgid "Send Test Email"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
#: packages/admin/src/components/settings/EmailSettings.tsx:162
#: packages/admin/src/components/SignupPage.tsx:87
#: packages/admin/src/components/SignupPage.tsx:150
@@ -4440,15 +4446,11 @@ msgstr ""
msgid "Set a custom display size for this image instance."
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:295
-msgid "Set up your passkey"
-msgstr ""
-
-#: packages/admin/src/components/SetupWizard.tsx:504
+#: packages/admin/src/components/SetupWizard.tsx:570
msgid "Set up your site"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:175
+#: packages/admin/src/components/SetupWizard.tsx:177
msgid "Setting up..."
msgstr ""
@@ -4496,24 +4498,35 @@ msgstr ""
msgid "Sign in"
msgstr ""
+#: packages/admin/src/components/SetupWizard.tsx:382
+msgid "Sign In"
+msgstr ""
+
#: packages/admin/src/components/SignupPage.tsx:269
msgid "Sign in instead"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:291
+#: packages/admin/src/components/LoginPage.tsx:240
msgid "Sign in to your site"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:292
+#. placeholder {0}: authProviderList.find((p) => p.id === activeProvider)?.label ?? activeProvider
+#. placeholder {0}: provider.label
+#: packages/admin/src/components/LoginPage.tsx:239
+#: packages/admin/src/components/SetupWizard.tsx:291
+msgid "Sign in with {0}"
+msgstr ""
+
+#: packages/admin/src/components/LoginPage.tsx:237
msgid "Sign in with email"
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:348
+#: packages/admin/src/components/LoginPage.tsx:298
msgid "Sign in with email link"
msgstr ""
#: packages/admin/src/components/auth/PasskeyLogin.tsx:136
-#: packages/admin/src/components/LoginPage.tsx:312
+#: packages/admin/src/components/LoginPage.tsx:260
msgid "Sign in with Passkey"
msgstr ""
@@ -4530,7 +4543,7 @@ msgstr ""
msgid "Single line text input"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:331
+#: packages/admin/src/components/SetupWizard.tsx:380
msgid "Site"
msgstr ""
@@ -4542,12 +4555,12 @@ msgstr ""
msgid "Site identity, logo, favicon, and reading preferences"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:329
+#: packages/admin/src/components/SetupWizard.tsx:378
msgid "Site Settings"
msgstr ""
#: packages/admin/src/components/settings/GeneralSettings.tsx:156
-#: packages/admin/src/components/SetupWizard.tsx:141
+#: packages/admin/src/components/SetupWizard.tsx:143
msgid "Site Title"
msgstr ""
@@ -4555,7 +4568,7 @@ msgstr ""
msgid "Site title & tagline"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:125
+#: packages/admin/src/components/SetupWizard.tsx:127
msgid "Site title is required"
msgstr ""
@@ -4699,7 +4712,7 @@ msgid "System ({resolvedLabel})"
msgstr ""
#: packages/admin/src/components/settings/GeneralSettings.tsx:162
-#: packages/admin/src/components/SetupWizard.tsx:152
+#: packages/admin/src/components/SetupWizard.tsx:154
msgid "Tagline"
msgstr ""
@@ -4737,7 +4750,7 @@ msgstr ""
msgid "Taxonomy not found:"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:180
+#: packages/admin/src/components/SetupWizard.tsx:182
msgid "Template:"
msgstr ""
@@ -4770,7 +4783,7 @@ msgstr ""
msgid "The invited user will have this role once they complete registration."
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:170
+#: packages/admin/src/components/LoginPage.tsx:113
#: packages/admin/src/components/SignupPage.tsx:138
msgid "The link will expire in 15 minutes."
msgstr ""
@@ -4895,7 +4908,7 @@ msgstr ""
msgid "This will revert to the published version. Your draft changes will be lost."
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:156
+#: packages/admin/src/components/SetupWizard.tsx:158
msgid "Thoughts, tutorials, and more"
msgstr ""
@@ -5261,7 +5274,7 @@ msgstr ""
msgid "Use your device's biometric authentication, security key, or PIN to sign in."
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:359
+#: packages/admin/src/components/LoginPage.tsx:334
msgid "Use your registered passkey to sign in securely."
msgstr ""
@@ -5379,7 +5392,7 @@ msgstr ""
msgid "We'll check what import options are available for your site."
msgstr ""
-#: packages/admin/src/components/LoginPage.tsx:360
+#: packages/admin/src/components/LoginPage.tsx:331
msgid "We'll send you a link to sign in without a password."
msgstr ""
@@ -5516,7 +5529,7 @@ msgstr ""
msgid "You'll be signing up as"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:509
+#: packages/admin/src/components/SetupWizard.tsx:575
msgid "You're signed in via Cloudflare Access"
msgstr ""
@@ -5525,7 +5538,7 @@ msgid "you@company.com"
msgstr ""
#: packages/admin/src/components/auth/PasskeyLogin.tsx:336
-#: packages/admin/src/components/SetupWizard.tsx:226
+#: packages/admin/src/components/SetupWizard.tsx:228
msgid "you@example.com"
msgstr ""
@@ -5543,7 +5556,7 @@ msgstr ""
msgid "Your device doesn't support the required security features."
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:222
+#: packages/admin/src/components/SetupWizard.tsx:224
msgid "Your Email"
msgstr ""
@@ -5563,7 +5576,7 @@ msgstr ""
msgid "Your LinkedIn profile username"
msgstr ""
-#: packages/admin/src/components/SetupWizard.tsx:234
+#: packages/admin/src/components/SetupWizard.tsx:236
msgid "Your Name"
msgstr ""
diff --git a/packages/admin/src/locales/pt-BR/messages.po b/packages/admin/src/locales/pt-BR/messages.po
index dcdb11900..233a278bc 100644
--- a/packages/admin/src/locales/pt-BR/messages.po
+++ b/packages/admin/src/locales/pt-BR/messages.po
@@ -59,7 +59,7 @@ msgid "{0, plural, one {(# item)} other {(# items)}}"
msgstr "{0, plural, one {(# item)} other {(# itens)}}"
#. placeholder {0}: seedInfo.collections
-#: packages/admin/src/components/SetupWizard.tsx:181
+#: packages/admin/src/components/SetupWizard.tsx:183
msgid "{0, plural, one {# collection} other {# collections}}"
msgstr "{0, plural, one {# coleção} other {# coleções}}"
@@ -315,8 +315,9 @@ msgstr "• Enviados para o armazenamento de mídia do EmDash"
msgid "• URLs in your content are updated automatically"
msgstr "• As URLs no seu conteúdo são atualizadas automaticamente"
-#: packages/admin/src/components/SetupWizard.tsx:250
-#: packages/admin/src/components/SetupWizard.tsx:310
+#: packages/admin/src/components/SetupWizard.tsx:252
+#: packages/admin/src/components/SetupWizard.tsx:304
+#: packages/admin/src/components/SetupWizard.tsx:359
#: packages/admin/src/components/WordPressImport.tsx:1431
msgid "← Back"
msgstr "← Voltar"
@@ -409,7 +410,7 @@ msgstr "Aceitar e instalar"
msgid "Accept & Update"
msgstr "Aceitar e atualizar"
-#: packages/admin/src/components/SetupWizard.tsx:332
+#: packages/admin/src/components/SetupWizard.tsx:381
msgid "Account"
msgstr "Conta"
@@ -694,7 +695,7 @@ msgstr "Atribua autores do WordPress a usuários do EmDash. Os posts serão atri
msgid "Authentication error: {0}"
msgstr "Erro de autenticação: {0}"
-#: packages/admin/src/components/LoginPage.tsx:253
+#: packages/admin/src/components/LoginPage.tsx:195
msgid "Authentication error: {error}"
msgstr "Erro de autenticação: {error}"
@@ -764,8 +765,9 @@ msgstr "Voltar"
msgid "Back to {collectionLabel} list"
msgstr "Voltar para a lista de {collectionLabel}"
-#: packages/admin/src/components/LoginPage.tsx:174
-#: packages/admin/src/components/LoginPage.tsx:210
+#: packages/admin/src/components/LoginPage.tsx:117
+#: packages/admin/src/components/LoginPage.tsx:153
+#: packages/admin/src/components/LoginPage.tsx:319
#: packages/admin/src/components/SignupPage.tsx:280
msgid "Back to login"
msgstr "Voltar ao acesso"
@@ -957,7 +959,7 @@ msgstr "Verificar atualizações"
msgid "Check Site"
msgstr "Verificar site"
-#: packages/admin/src/components/LoginPage.tsx:158
+#: packages/admin/src/components/LoginPage.tsx:101
#: packages/admin/src/components/SignupPage.tsx:129
#: packages/admin/src/components/SignupPage.tsx:401
msgid "Check your email"
@@ -971,6 +973,10 @@ msgstr "Verificando {urlInput}..."
msgid "Checking authentication..."
msgstr "Verificando autenticação..."
+#: packages/admin/src/components/SetupWizard.tsx:315
+msgid "Choose how to sign in"
+msgstr ""
+
#: packages/admin/src/components/Settings.tsx:130
msgid "Choose your preferred admin language"
msgstr "Escolha o idioma preferido do painel"
@@ -979,7 +985,7 @@ msgstr "Escolha o idioma preferido do painel"
msgid "Click the link in the email to continue setting up your account."
msgstr "Clique no link no e-mail para continuar configurando sua conta."
-#: packages/admin/src/components/LoginPage.tsx:169
+#: packages/admin/src/components/LoginPage.tsx:112
msgid "Click the link in the email to sign in."
msgstr "Clique no link no e-mail para entrar."
@@ -1185,8 +1191,8 @@ msgstr "Escrita de conteúdo"
msgid "Continue"
msgstr "Continuar"
-#: packages/admin/src/components/SetupWizard.tsx:175
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:177
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Continue →"
msgstr "Continuar →"
@@ -1291,7 +1297,7 @@ msgstr "Criar novo token"
msgid "Create one in WordPress: Users → Profile → Application Passwords"
msgstr "Crie um no WordPress: Usuários → Perfil → Senhas de aplicativo"
-#: packages/admin/src/components/SetupWizard.tsx:305
+#: packages/admin/src/components/SetupWizard.tsx:325
msgid "Create Passkey"
msgstr "Criar chave de acesso"
@@ -1336,7 +1342,7 @@ msgstr "Criar taxonomia"
msgid "Create Token"
msgstr "Criar token"
-#: packages/admin/src/components/SetupWizard.tsx:505
+#: packages/admin/src/components/SetupWizard.tsx:571
msgid "Create your account"
msgstr "Crie sua conta"
@@ -1724,7 +1730,7 @@ msgstr "Domínio removido"
msgid "Domain updated"
msgstr "Domínio atualizado"
-#: packages/admin/src/components/LoginPage.tsx:366
+#: packages/admin/src/components/LoginPage.tsx:340
msgid "Don't have an account? <0>Sign up0>"
msgstr "Não tem uma conta? <0>Cadastre-se0>"
@@ -1853,13 +1859,13 @@ msgstr "E-mail"
msgid "Email (optional)"
msgstr "E-mail (opcional)"
-#: packages/admin/src/components/LoginPage.tsx:183
+#: packages/admin/src/components/LoginPage.tsx:126
#: packages/admin/src/components/SignupPage.tsx:65
#: packages/admin/src/components/users/InviteUserModal.tsx:154
msgid "Email address"
msgstr "Endereço de e-mail"
-#: packages/admin/src/components/SetupWizard.tsx:204
+#: packages/admin/src/components/SetupWizard.tsx:206
#: packages/admin/src/components/SignupPage.tsx:48
msgid "Email is required"
msgstr "O e-mail é obrigatório"
@@ -1965,6 +1971,10 @@ msgstr "Digite o nome"
msgid "Enter the code from your terminal"
msgstr "Digite o código do seu terminal"
+#: packages/admin/src/components/LoginPage.tsx:333
+msgid "Enter your handle to sign in."
+msgstr ""
+
#: packages/admin/src/components/WordPressImport.tsx:1289
msgid "Enter your WordPress credentials to import content directly."
msgstr "Digite suas credenciais do WordPress para importar o conteúdo diretamente."
@@ -1975,7 +1985,7 @@ msgstr "Digite a URL do seu site WordPress"
#: packages/admin/src/components/MenuEditor.tsx:83
#: packages/admin/src/components/MenuEditor.tsx:122
-#: packages/admin/src/components/SetupWizard.tsx:484
+#: packages/admin/src/components/SetupWizard.tsx:550
msgid "Error"
msgstr "Erro"
@@ -2089,7 +2099,7 @@ msgstr "Falha ao carregar Plugins: {0}"
msgid "Failed to load revisions"
msgstr "Falha ao carregar revisões"
-#: packages/admin/src/components/SetupWizard.tsx:486
+#: packages/admin/src/components/SetupWizard.tsx:552
msgid "Failed to load setup"
msgstr "Falha ao carregar configuração"
@@ -2120,8 +2130,8 @@ msgstr ""
msgid "Failed to save settings"
msgstr "Falha ao salvar configurações"
-#: packages/admin/src/components/LoginPage.tsx:127
-#: packages/admin/src/components/LoginPage.tsx:132
+#: packages/admin/src/components/LoginPage.tsx:70
+#: packages/admin/src/components/LoginPage.tsx:75
msgid "Failed to send magic link"
msgstr "Falha ao enviar link mágico"
@@ -2328,7 +2338,7 @@ msgstr "Ícone desfocado devido à auditoria de imagem"
msgid "ID"
msgstr "ID"
-#: packages/admin/src/components/LoginPage.tsx:160
+#: packages/admin/src/components/LoginPage.tsx:103
msgid "If an account exists for <0>{email}0>, we've sent a sign-in link."
msgstr "Se existir uma conta para <0>{email}0>, enviamos um link de acesso."
@@ -2448,7 +2458,7 @@ msgstr "Importando conteúdo..."
msgid "Importing Media"
msgstr "Importando mídia"
-#: packages/admin/src/components/SetupWizard.tsx:163
+#: packages/admin/src/components/SetupWizard.tsx:165
msgid "Include sample content (recommended for new sites)"
msgstr "Incluir conteúdo de exemplo (recomendado para novos sites)"
@@ -2561,7 +2571,7 @@ msgstr "Item excluído"
msgid "Item updated"
msgstr "Item atualizado"
-#: packages/admin/src/components/SetupWizard.tsx:238
+#: packages/admin/src/components/SetupWizard.tsx:240
#: packages/admin/src/components/SignupPage.tsx:204
msgid "Jane Doe"
msgstr "Jane Doe"
@@ -2585,7 +2595,7 @@ msgstr "Palavras-chave"
msgid "Label"
msgstr "Rótulo"
-#: packages/admin/src/components/LoginPage.tsx:379
+#: packages/admin/src/components/LoginPage.tsx:353
#: packages/admin/src/components/Settings.tsx:129
#: packages/admin/src/components/Settings.tsx:134
msgid "Language"
@@ -2725,7 +2735,7 @@ msgstr "Carregando seções..."
msgid "Loading settings..."
msgstr "Carregando configurações..."
-#: packages/admin/src/components/SetupWizard.tsx:473
+#: packages/admin/src/components/SetupWizard.tsx:539
msgid "Loading setup..."
msgstr "Carregando configuração..."
@@ -3021,7 +3031,7 @@ msgstr "Texto simples com várias linhas"
msgid "Multiple choices from options"
msgstr "Múltiplas escolhas entre as opções"
-#: packages/admin/src/components/SetupWizard.tsx:145
+#: packages/admin/src/components/SetupWizard.tsx:147
msgid "My Awesome Blog"
msgstr "Meu Blog Incrível"
@@ -3403,7 +3413,7 @@ msgstr "ou escolha na biblioteca"
msgid "Or click to browse. Accepts .xml files exported from WordPress."
msgstr "Ou clique para navegar. Aceita arquivos .xml exportados do WordPress."
-#: packages/admin/src/components/LoginPage.tsx:321
+#: packages/admin/src/components/LoginPage.tsx:269
msgid "Or continue with"
msgstr "Ou continue com"
@@ -3462,10 +3472,6 @@ msgstr "Parte de um loop de redirecionamento"
msgid "Pass"
msgstr "Aprovar"
-#: packages/admin/src/components/SetupWizard.tsx:333
-msgid "Passkey"
-msgstr "Chave de acesso"
-
#: packages/admin/src/components/settings/SecuritySettings.tsx:98
msgid "Passkey added successfully"
msgstr "Chave de acesso adicionada com sucesso"
@@ -3507,10 +3513,6 @@ msgstr "Chaves de acesso são uma forma segura de acessar sua conta sem senha. V
msgid "Passkeys are a secure, passwordless way to sign in using your device's biometrics, PIN, or security key."
msgstr "Chaves de acesso são uma forma segura de acessar usando a biometria do seu dispositivo, PIN ou chave de segurança."
-#: packages/admin/src/components/SetupWizard.tsx:297
-msgid "Passkeys are more secure than passwords. You'll use your device's biometrics, PIN, or security key to sign in."
-msgstr "Chaves de acesso são mais seguras que senhas. Você usará a biometria do seu dispositivo, PIN ou chave de segurança para acessar."
-
#: packages/admin/src/components/auth/PasskeyLogin.tsx:303
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:300
msgid "Passkeys Not Available Here"
@@ -3558,12 +3560,16 @@ msgstr "Excluir {title} permanentemente"
msgid "Permissions"
msgstr "Permissões"
+#: packages/admin/src/components/SetupWizard.tsx:317
+msgid "Pick any method to create your admin account."
+msgstr ""
+
#: packages/admin/src/components/auth/PasskeyLogin.tsx:313
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:310
msgid "Plain"
msgstr "Simples"
-#: packages/admin/src/components/SetupWizard.tsx:206
+#: packages/admin/src/components/SetupWizard.tsx:208
msgid "Please enter a valid email"
msgstr "Insira um e-mail válido"
@@ -3637,7 +3643,7 @@ msgid "Preparing to download files from WordPress..."
msgstr "Preparando para baixar arquivos do WordPress..."
#: packages/admin/src/components/auth/PasskeyLogin.tsx:166
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Preparing..."
msgstr "Preparando..."
@@ -4264,7 +4270,7 @@ msgstr "Seções"
msgid "secure context"
msgstr "contexto seguro"
-#: packages/admin/src/components/SetupWizard.tsx:506
+#: packages/admin/src/components/SetupWizard.tsx:572
msgid "Secure your account"
msgstr "Proteja sua conta"
@@ -4388,7 +4394,7 @@ msgstr "Envie um e-mail de convite para um novo membro da equipe."
msgid "Send Invite"
msgstr "Enviar convite"
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
msgid "Send magic link"
msgstr "Enviar link mágico"
@@ -4404,7 +4410,7 @@ msgstr "Enviar teste"
msgid "Send Test Email"
msgstr "Enviar e-mail de teste"
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
#: packages/admin/src/components/settings/EmailSettings.tsx:162
#: packages/admin/src/components/SignupPage.tsx:87
#: packages/admin/src/components/SignupPage.tsx:150
@@ -4440,15 +4446,11 @@ msgstr "Título SEO"
msgid "Set a custom display size for this image instance."
msgstr "Defina um tamanho de exibição personalizado para esta instância da imagem."
-#: packages/admin/src/components/SetupWizard.tsx:295
-msgid "Set up your passkey"
-msgstr "Configure sua chave de acesso"
-
-#: packages/admin/src/components/SetupWizard.tsx:504
+#: packages/admin/src/components/SetupWizard.tsx:570
msgid "Set up your site"
msgstr "Configure seu site"
-#: packages/admin/src/components/SetupWizard.tsx:175
+#: packages/admin/src/components/SetupWizard.tsx:177
msgid "Setting up..."
msgstr "Configurando..."
@@ -4496,24 +4498,35 @@ msgstr "Exibido ao passar o mouse sobre a imagem."
msgid "Sign in"
msgstr "Acessar"
+#: packages/admin/src/components/SetupWizard.tsx:382
+msgid "Sign In"
+msgstr ""
+
#: packages/admin/src/components/SignupPage.tsx:269
msgid "Sign in instead"
msgstr "Acessar em vez disso"
-#: packages/admin/src/components/LoginPage.tsx:291
+#: packages/admin/src/components/LoginPage.tsx:240
msgid "Sign in to your site"
msgstr "Acesse seu site"
-#: packages/admin/src/components/LoginPage.tsx:292
+#. placeholder {0}: authProviderList.find((p) => p.id === activeProvider)?.label ?? activeProvider
+#. placeholder {0}: provider.label
+#: packages/admin/src/components/LoginPage.tsx:239
+#: packages/admin/src/components/SetupWizard.tsx:291
+msgid "Sign in with {0}"
+msgstr ""
+
+#: packages/admin/src/components/LoginPage.tsx:237
msgid "Sign in with email"
msgstr "Acesse com e-mail"
-#: packages/admin/src/components/LoginPage.tsx:348
+#: packages/admin/src/components/LoginPage.tsx:298
msgid "Sign in with email link"
msgstr "Acesse com link de e-mail"
#: packages/admin/src/components/auth/PasskeyLogin.tsx:136
-#: packages/admin/src/components/LoginPage.tsx:312
+#: packages/admin/src/components/LoginPage.tsx:260
msgid "Sign in with Passkey"
msgstr "Acesse com chave de acesso"
@@ -4530,7 +4543,7 @@ msgstr "Escolha única entre opções"
msgid "Single line text input"
msgstr "Entrada de texto de linha única"
-#: packages/admin/src/components/SetupWizard.tsx:331
+#: packages/admin/src/components/SetupWizard.tsx:380
msgid "Site"
msgstr "Site"
@@ -4542,12 +4555,12 @@ msgstr "Identidade do site"
msgid "Site identity, logo, favicon, and reading preferences"
msgstr "Identidade do site, logo, favicon e preferências de leitura"
-#: packages/admin/src/components/SetupWizard.tsx:329
+#: packages/admin/src/components/SetupWizard.tsx:378
msgid "Site Settings"
msgstr "Configurações do site"
#: packages/admin/src/components/settings/GeneralSettings.tsx:156
-#: packages/admin/src/components/SetupWizard.tsx:141
+#: packages/admin/src/components/SetupWizard.tsx:143
msgid "Site Title"
msgstr "Título do site"
@@ -4555,7 +4568,7 @@ msgstr "Título do site"
msgid "Site title & tagline"
msgstr "Título e slogan do site"
-#: packages/admin/src/components/SetupWizard.tsx:125
+#: packages/admin/src/components/SetupWizard.tsx:127
msgid "Site title is required"
msgstr "O título do site é obrigatório"
@@ -4699,7 +4712,7 @@ msgid "System ({resolvedLabel})"
msgstr "Sistema ({resolvedLabel})"
#: packages/admin/src/components/settings/GeneralSettings.tsx:162
-#: packages/admin/src/components/SetupWizard.tsx:152
+#: packages/admin/src/components/SetupWizard.tsx:154
msgid "Tagline"
msgstr "Slogan"
@@ -4737,7 +4750,7 @@ msgstr "Taxonomia criada"
msgid "Taxonomy not found:"
msgstr "Taxonomia não encontrada:"
-#: packages/admin/src/components/SetupWizard.tsx:180
+#: packages/admin/src/components/SetupWizard.tsx:182
msgid "Template:"
msgstr "Modelo:"
@@ -4770,7 +4783,7 @@ msgstr "As tabelas a seguir contêm conteúdo, mas não estão registradas como
msgid "The invited user will have this role once they complete registration."
msgstr "O usuário convidado terá esta função após concluir o registro."
-#: packages/admin/src/components/LoginPage.tsx:170
+#: packages/admin/src/components/LoginPage.tsx:113
#: packages/admin/src/components/SignupPage.tsx:138
msgid "The link will expire in 15 minutes."
msgstr "O link expirará em 15 minutos."
@@ -4895,7 +4908,7 @@ msgstr "Isso removerá o plugin e seu pacote do seu site."
msgid "This will revert to the published version. Your draft changes will be lost."
msgstr "Isso reverterá para a versão publicada. Suas alterações de rascunho serão perdidas."
-#: packages/admin/src/components/SetupWizard.tsx:156
+#: packages/admin/src/components/SetupWizard.tsx:158
msgid "Thoughts, tutorials, and more"
msgstr "Pensamentos, tutoriais e mais"
@@ -5261,7 +5274,7 @@ msgstr "Use [param] ou [...rest] nos caminhos para correspondência de padrão."
msgid "Use your device's biometric authentication, security key, or PIN to sign in."
msgstr "Use a autenticação biométrica, chave de segurança ou PIN do seu dispositivo para acessar."
-#: packages/admin/src/components/LoginPage.tsx:359
+#: packages/admin/src/components/LoginPage.tsx:334
msgid "Use your registered passkey to sign in securely."
msgstr "Use sua chave de acesso cadastrada para acessar com segurança."
@@ -5379,7 +5392,7 @@ msgstr "Não foi possível conectar a um site WordPress em {0}. Isso pode signif
msgid "We'll check what import options are available for your site."
msgstr "Verificaremos quais opções de importação estão disponíveis para o seu site."
-#: packages/admin/src/components/LoginPage.tsx:360
+#: packages/admin/src/components/LoginPage.tsx:331
msgid "We'll send you a link to sign in without a password."
msgstr "Enviaremos um link para você acessar sem senha."
@@ -5516,7 +5529,7 @@ msgstr "Você será redirecionado ao WordPress para autorizar a conexão."
msgid "You'll be signing up as"
msgstr "Você se registrará como"
-#: packages/admin/src/components/SetupWizard.tsx:509
+#: packages/admin/src/components/SetupWizard.tsx:575
msgid "You're signed in via Cloudflare Access"
msgstr "Você está conectado via Cloudflare Access"
@@ -5525,7 +5538,7 @@ msgid "you@company.com"
msgstr "you@company.com"
#: packages/admin/src/components/auth/PasskeyLogin.tsx:336
-#: packages/admin/src/components/SetupWizard.tsx:226
+#: packages/admin/src/components/SetupWizard.tsx:228
msgid "you@example.com"
msgstr "you@example.com"
@@ -5543,7 +5556,7 @@ msgstr "Seu navegador não suporta chaves de acesso. Use um navegador moderno co
msgid "Your device doesn't support the required security features."
msgstr "Seu dispositivo não suporta os recursos de segurança necessários."
-#: packages/admin/src/components/SetupWizard.tsx:222
+#: packages/admin/src/components/SetupWizard.tsx:224
msgid "Your Email"
msgstr "Seu e-mail"
@@ -5563,7 +5576,7 @@ msgstr "Seu nome de usuário do Instagram"
msgid "Your LinkedIn profile username"
msgstr "Seu nome de usuário de perfil do LinkedIn"
-#: packages/admin/src/components/SetupWizard.tsx:234
+#: packages/admin/src/components/SetupWizard.tsx:236
msgid "Your Name"
msgstr "Seu nome"
diff --git a/packages/admin/src/locales/zh-CN/messages.po b/packages/admin/src/locales/zh-CN/messages.po
index 5eff0caec..aba831aa9 100644
--- a/packages/admin/src/locales/zh-CN/messages.po
+++ b/packages/admin/src/locales/zh-CN/messages.po
@@ -59,7 +59,7 @@ msgid "{0, plural, one {(# item)} other {(# items)}}"
msgstr "{0, plural, one {(# 个项目)} other {(# 个项目)}}"
#. placeholder {0}: seedInfo.collections
-#: packages/admin/src/components/SetupWizard.tsx:181
+#: packages/admin/src/components/SetupWizard.tsx:183
msgid "{0, plural, one {# collection} other {# collections}}"
msgstr "{0, plural, one {# 个合集} other {# 个合集}}"
@@ -315,8 +315,9 @@ msgstr "• 已上传到您的 EmDash 媒体存储"
msgid "• URLs in your content are updated automatically"
msgstr "• 您内容中的 URL 将自动更新"
-#: packages/admin/src/components/SetupWizard.tsx:250
-#: packages/admin/src/components/SetupWizard.tsx:310
+#: packages/admin/src/components/SetupWizard.tsx:252
+#: packages/admin/src/components/SetupWizard.tsx:304
+#: packages/admin/src/components/SetupWizard.tsx:359
#: packages/admin/src/components/WordPressImport.tsx:1431
msgid "← Back"
msgstr "← 返回"
@@ -409,7 +410,7 @@ msgstr "接受并安装"
msgid "Accept & Update"
msgstr "接受并更新"
-#: packages/admin/src/components/SetupWizard.tsx:332
+#: packages/admin/src/components/SetupWizard.tsx:381
msgid "Account"
msgstr "账户"
@@ -694,7 +695,7 @@ msgstr "将 WordPress 作者分配给 EmDash 用户。文章将归属于所选
msgid "Authentication error: {0}"
msgstr "认证错误:{0}"
-#: packages/admin/src/components/LoginPage.tsx:253
+#: packages/admin/src/components/LoginPage.tsx:195
msgid "Authentication error: {error}"
msgstr "认证错误:{error}"
@@ -764,8 +765,9 @@ msgstr "返回"
msgid "Back to {collectionLabel} list"
msgstr "返回到 {collectionLabel} 列表"
-#: packages/admin/src/components/LoginPage.tsx:174
-#: packages/admin/src/components/LoginPage.tsx:210
+#: packages/admin/src/components/LoginPage.tsx:117
+#: packages/admin/src/components/LoginPage.tsx:153
+#: packages/admin/src/components/LoginPage.tsx:319
#: packages/admin/src/components/SignupPage.tsx:280
msgid "Back to login"
msgstr "返回登录"
@@ -957,7 +959,7 @@ msgstr "检查更新"
msgid "Check Site"
msgstr "检查站点"
-#: packages/admin/src/components/LoginPage.tsx:158
+#: packages/admin/src/components/LoginPage.tsx:101
#: packages/admin/src/components/SignupPage.tsx:129
#: packages/admin/src/components/SignupPage.tsx:401
msgid "Check your email"
@@ -971,6 +973,10 @@ msgstr "正在检查 {urlInput}..."
msgid "Checking authentication..."
msgstr "正在检查认证..."
+#: packages/admin/src/components/SetupWizard.tsx:315
+msgid "Choose how to sign in"
+msgstr ""
+
#: packages/admin/src/components/Settings.tsx:130
msgid "Choose your preferred admin language"
msgstr "选择您的首选管理语言"
@@ -979,7 +985,7 @@ msgstr "选择您的首选管理语言"
msgid "Click the link in the email to continue setting up your account."
msgstr "点击邮件中的链接以继续设置您的账户。"
-#: packages/admin/src/components/LoginPage.tsx:169
+#: packages/admin/src/components/LoginPage.tsx:112
msgid "Click the link in the email to sign in."
msgstr "点击邮件中的链接即可登录。"
@@ -1185,8 +1191,8 @@ msgstr "内容写入"
msgid "Continue"
msgstr "继续"
-#: packages/admin/src/components/SetupWizard.tsx:175
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:177
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Continue →"
msgstr "继续 →"
@@ -1291,7 +1297,7 @@ msgstr "创建新令牌"
msgid "Create one in WordPress: Users → Profile → Application Passwords"
msgstr "在 WordPress 中创建:用户 → 个人资料 → 应用程序密码"
-#: packages/admin/src/components/SetupWizard.tsx:305
+#: packages/admin/src/components/SetupWizard.tsx:325
msgid "Create Passkey"
msgstr "创建通行密钥"
@@ -1336,7 +1342,7 @@ msgstr "创建分类"
msgid "Create Token"
msgstr "创建令牌"
-#: packages/admin/src/components/SetupWizard.tsx:505
+#: packages/admin/src/components/SetupWizard.tsx:571
msgid "Create your account"
msgstr "创建您的账户"
@@ -1724,7 +1730,7 @@ msgstr "域名已移除"
msgid "Domain updated"
msgstr "域名已更新"
-#: packages/admin/src/components/LoginPage.tsx:366
+#: packages/admin/src/components/LoginPage.tsx:340
msgid "Don't have an account? <0>Sign up0>"
msgstr "还没有账号?<0>立即注册0>"
@@ -1853,13 +1859,13 @@ msgstr "邮箱"
msgid "Email (optional)"
msgstr "邮箱(可选)"
-#: packages/admin/src/components/LoginPage.tsx:183
+#: packages/admin/src/components/LoginPage.tsx:126
#: packages/admin/src/components/SignupPage.tsx:65
#: packages/admin/src/components/users/InviteUserModal.tsx:154
msgid "Email address"
msgstr "邮箱地址"
-#: packages/admin/src/components/SetupWizard.tsx:204
+#: packages/admin/src/components/SetupWizard.tsx:206
#: packages/admin/src/components/SignupPage.tsx:48
msgid "Email is required"
msgstr "邮箱为必填项"
@@ -1965,6 +1971,10 @@ msgstr "输入名称"
msgid "Enter the code from your terminal"
msgstr "输入终端中的代码"
+#: packages/admin/src/components/LoginPage.tsx:333
+msgid "Enter your handle to sign in."
+msgstr ""
+
#: packages/admin/src/components/WordPressImport.tsx:1289
msgid "Enter your WordPress credentials to import content directly."
msgstr "输入您的 WordPress 凭据以直接导入内容。"
@@ -1975,7 +1985,7 @@ msgstr "输入您的 WordPress 站点 URL"
#: packages/admin/src/components/MenuEditor.tsx:83
#: packages/admin/src/components/MenuEditor.tsx:122
-#: packages/admin/src/components/SetupWizard.tsx:484
+#: packages/admin/src/components/SetupWizard.tsx:550
msgid "Error"
msgstr "错误"
@@ -2089,7 +2099,7 @@ msgstr "加载插件失败:{0}"
msgid "Failed to load revisions"
msgstr "加载修订历史失败"
-#: packages/admin/src/components/SetupWizard.tsx:486
+#: packages/admin/src/components/SetupWizard.tsx:552
msgid "Failed to load setup"
msgstr "加载设置失败"
@@ -2120,8 +2130,8 @@ msgstr "保存失败"
msgid "Failed to save settings"
msgstr "保存设置失败"
-#: packages/admin/src/components/LoginPage.tsx:127
-#: packages/admin/src/components/LoginPage.tsx:132
+#: packages/admin/src/components/LoginPage.tsx:70
+#: packages/admin/src/components/LoginPage.tsx:75
msgid "Failed to send magic link"
msgstr "发送免密登录链接失败"
@@ -2328,7 +2338,7 @@ msgstr "由于图像审计,图标已模糊"
msgid "ID"
msgstr "ID"
-#: packages/admin/src/components/LoginPage.tsx:160
+#: packages/admin/src/components/LoginPage.tsx:103
msgid "If an account exists for <0>{email}0>, we've sent a sign-in link."
msgstr "如果存在与 <0>{email}0> 关联的账号,我们已发送登录链接。"
@@ -2448,7 +2458,7 @@ msgstr "正在导入内容..."
msgid "Importing Media"
msgstr "正在导入媒体"
-#: packages/admin/src/components/SetupWizard.tsx:163
+#: packages/admin/src/components/SetupWizard.tsx:165
msgid "Include sample content (recommended for new sites)"
msgstr "包含示例内容(推荐新站点使用)"
@@ -2561,7 +2571,7 @@ msgstr "项目已删除"
msgid "Item updated"
msgstr "项目已更新"
-#: packages/admin/src/components/SetupWizard.tsx:238
+#: packages/admin/src/components/SetupWizard.tsx:240
#: packages/admin/src/components/SignupPage.tsx:204
msgid "Jane Doe"
msgstr "张三"
@@ -2585,7 +2595,7 @@ msgstr "关键词"
msgid "Label"
msgstr "标签"
-#: packages/admin/src/components/LoginPage.tsx:379
+#: packages/admin/src/components/LoginPage.tsx:353
#: packages/admin/src/components/Settings.tsx:129
#: packages/admin/src/components/Settings.tsx:134
msgid "Language"
@@ -2725,7 +2735,7 @@ msgstr "正在加载区块..."
msgid "Loading settings..."
msgstr "正在加载设置..."
-#: packages/admin/src/components/SetupWizard.tsx:473
+#: packages/admin/src/components/SetupWizard.tsx:539
msgid "Loading setup..."
msgstr "正在加载设置..."
@@ -3021,7 +3031,7 @@ msgstr "多行纯文本"
msgid "Multiple choices from options"
msgstr "从选项中选择多个"
-#: packages/admin/src/components/SetupWizard.tsx:145
+#: packages/admin/src/components/SetupWizard.tsx:147
msgid "My Awesome Blog"
msgstr "我的精彩博客"
@@ -3403,7 +3413,7 @@ msgstr "或从库中选择"
msgid "Or click to browse. Accepts .xml files exported from WordPress."
msgstr "或点击浏览。接受从 WordPress 导出的 .xml 文件。"
-#: packages/admin/src/components/LoginPage.tsx:321
+#: packages/admin/src/components/LoginPage.tsx:269
msgid "Or continue with"
msgstr "或继续使用"
@@ -3462,10 +3472,6 @@ msgstr "重定向循环的一部分"
msgid "Pass"
msgstr "通过"
-#: packages/admin/src/components/SetupWizard.tsx:333
-msgid "Passkey"
-msgstr "通行密钥"
-
#: packages/admin/src/components/settings/SecuritySettings.tsx:98
msgid "Passkey added successfully"
msgstr "通行密钥添加成功"
@@ -3507,10 +3513,6 @@ msgstr "通行密钥是一种安全的无密码登录方式。您可以为不同
msgid "Passkeys are a secure, passwordless way to sign in using your device's biometrics, PIN, or security key."
msgstr "通行密钥是一种安全的无密码登录方式,使用设备的生物识别、PIN 码或安全密钥登录。"
-#: packages/admin/src/components/SetupWizard.tsx:297
-msgid "Passkeys are more secure than passwords. You'll use your device's biometrics, PIN, or security key to sign in."
-msgstr "通行密钥比密码更安全。您将使用设备的生物识别、PIN 码或安全密钥登录。"
-
#: packages/admin/src/components/auth/PasskeyLogin.tsx:303
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:300
msgid "Passkeys Not Available Here"
@@ -3558,12 +3560,16 @@ msgstr "永久删除 {title}"
msgid "Permissions"
msgstr "权限"
+#: packages/admin/src/components/SetupWizard.tsx:317
+msgid "Pick any method to create your admin account."
+msgstr ""
+
#: packages/admin/src/components/auth/PasskeyLogin.tsx:313
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:310
msgid "Plain"
msgstr "普通"
-#: packages/admin/src/components/SetupWizard.tsx:206
+#: packages/admin/src/components/SetupWizard.tsx:208
msgid "Please enter a valid email"
msgstr "请输入有效的邮箱"
@@ -3637,7 +3643,7 @@ msgid "Preparing to download files from WordPress..."
msgstr "正在准备从 WordPress 下载文件..."
#: packages/admin/src/components/auth/PasskeyLogin.tsx:166
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Preparing..."
msgstr "正在准备..."
@@ -4264,7 +4270,7 @@ msgstr "区块"
msgid "secure context"
msgstr "安全上下文"
-#: packages/admin/src/components/SetupWizard.tsx:506
+#: packages/admin/src/components/SetupWizard.tsx:572
msgid "Secure your account"
msgstr "保护您的账户"
@@ -4388,7 +4394,7 @@ msgstr "向新团队成员发送邀请邮件。"
msgid "Send Invite"
msgstr "发送邀请"
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
msgid "Send magic link"
msgstr "发送免密登录链接"
@@ -4404,7 +4410,7 @@ msgstr "发送测试"
msgid "Send Test Email"
msgstr "发送测试邮件"
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
#: packages/admin/src/components/settings/EmailSettings.tsx:162
#: packages/admin/src/components/SignupPage.tsx:87
#: packages/admin/src/components/SignupPage.tsx:150
@@ -4440,15 +4446,11 @@ msgstr "SEO 标题"
msgid "Set a custom display size for this image instance."
msgstr "为此图片实例设置自定义显示尺寸。"
-#: packages/admin/src/components/SetupWizard.tsx:295
-msgid "Set up your passkey"
-msgstr "设置您的通行密钥"
-
-#: packages/admin/src/components/SetupWizard.tsx:504
+#: packages/admin/src/components/SetupWizard.tsx:570
msgid "Set up your site"
msgstr "设置您的站点"
-#: packages/admin/src/components/SetupWizard.tsx:175
+#: packages/admin/src/components/SetupWizard.tsx:177
msgid "Setting up..."
msgstr "正在设置..."
@@ -4496,24 +4498,35 @@ msgstr "悬停在图片上时显示。"
msgid "Sign in"
msgstr "登录"
+#: packages/admin/src/components/SetupWizard.tsx:382
+msgid "Sign In"
+msgstr ""
+
#: packages/admin/src/components/SignupPage.tsx:269
msgid "Sign in instead"
msgstr "改为登录"
-#: packages/admin/src/components/LoginPage.tsx:291
+#: packages/admin/src/components/LoginPage.tsx:240
msgid "Sign in to your site"
msgstr "登录到您的网站"
-#: packages/admin/src/components/LoginPage.tsx:292
+#. placeholder {0}: authProviderList.find((p) => p.id === activeProvider)?.label ?? activeProvider
+#. placeholder {0}: provider.label
+#: packages/admin/src/components/LoginPage.tsx:239
+#: packages/admin/src/components/SetupWizard.tsx:291
+msgid "Sign in with {0}"
+msgstr ""
+
+#: packages/admin/src/components/LoginPage.tsx:237
msgid "Sign in with email"
msgstr "使用邮箱登录"
-#: packages/admin/src/components/LoginPage.tsx:348
+#: packages/admin/src/components/LoginPage.tsx:298
msgid "Sign in with email link"
msgstr "使用邮箱链接登录"
#: packages/admin/src/components/auth/PasskeyLogin.tsx:136
-#: packages/admin/src/components/LoginPage.tsx:312
+#: packages/admin/src/components/LoginPage.tsx:260
msgid "Sign in with Passkey"
msgstr "使用通行密钥登录"
@@ -4530,7 +4543,7 @@ msgstr "从选项中选择一个"
msgid "Single line text input"
msgstr "单行文本输入"
-#: packages/admin/src/components/SetupWizard.tsx:331
+#: packages/admin/src/components/SetupWizard.tsx:380
msgid "Site"
msgstr "站点"
@@ -4542,12 +4555,12 @@ msgstr "站点标识"
msgid "Site identity, logo, favicon, and reading preferences"
msgstr "网站标识、徽标、网站图标和阅读偏好"
-#: packages/admin/src/components/SetupWizard.tsx:329
+#: packages/admin/src/components/SetupWizard.tsx:378
msgid "Site Settings"
msgstr "站点设置"
#: packages/admin/src/components/settings/GeneralSettings.tsx:156
-#: packages/admin/src/components/SetupWizard.tsx:141
+#: packages/admin/src/components/SetupWizard.tsx:143
msgid "Site Title"
msgstr "站点标题"
@@ -4555,7 +4568,7 @@ msgstr "站点标题"
msgid "Site title & tagline"
msgstr "站点标题和副标题"
-#: packages/admin/src/components/SetupWizard.tsx:125
+#: packages/admin/src/components/SetupWizard.tsx:127
msgid "Site title is required"
msgstr "站点标题为必填项"
@@ -4699,7 +4712,7 @@ msgid "System ({resolvedLabel})"
msgstr "跟随系统({resolvedLabel})"
#: packages/admin/src/components/settings/GeneralSettings.tsx:162
-#: packages/admin/src/components/SetupWizard.tsx:152
+#: packages/admin/src/components/SetupWizard.tsx:154
msgid "Tagline"
msgstr "副标题"
@@ -4737,7 +4750,7 @@ msgstr "分类已创建"
msgid "Taxonomy not found:"
msgstr "未找到分类:"
-#: packages/admin/src/components/SetupWizard.tsx:180
+#: packages/admin/src/components/SetupWizard.tsx:182
msgid "Template:"
msgstr "模板:"
@@ -4770,7 +4783,7 @@ msgstr "以下表格包含内容但未注册为合集。注册它们以便在管
msgid "The invited user will have this role once they complete registration."
msgstr "被邀请的用户完成注册后将拥有此角色。"
-#: packages/admin/src/components/LoginPage.tsx:170
+#: packages/admin/src/components/LoginPage.tsx:113
#: packages/admin/src/components/SignupPage.tsx:138
msgid "The link will expire in 15 minutes."
msgstr "链接将在 15 分钟后过期。"
@@ -4895,7 +4908,7 @@ msgstr "这将从您的站点移除插件及其包。"
msgid "This will revert to the published version. Your draft changes will be lost."
msgstr "这将恢复到已发布的版本。您的草稿更改将会丢失。"
-#: packages/admin/src/components/SetupWizard.tsx:156
+#: packages/admin/src/components/SetupWizard.tsx:158
msgid "Thoughts, tutorials, and more"
msgstr "想法、教程等"
@@ -5261,7 +5274,7 @@ msgstr "在路径中使用 [param] 或 [...rest] 进行模式匹配。"
msgid "Use your device's biometric authentication, security key, or PIN to sign in."
msgstr "使用设备的生物识别认证、安全密钥或 PIN 码登录。"
-#: packages/admin/src/components/LoginPage.tsx:359
+#: packages/admin/src/components/LoginPage.tsx:334
msgid "Use your registered passkey to sign in securely."
msgstr "使用您注册的通行密钥安全登录。"
@@ -5379,7 +5392,7 @@ msgstr "我们无法连接到位于 {0} 的 WordPress 站点。这可能意味
msgid "We'll check what import options are available for your site."
msgstr "我们将检查您的站点有哪些导入选项可用。"
-#: packages/admin/src/components/LoginPage.tsx:360
+#: packages/admin/src/components/LoginPage.tsx:331
msgid "We'll send you a link to sign in without a password."
msgstr "我们将向您发送一个无需密码即可登录的链接。"
@@ -5516,7 +5529,7 @@ msgstr "您将被重定向到 WordPress 以授权连接。"
msgid "You'll be signing up as"
msgstr "您将注册为"
-#: packages/admin/src/components/SetupWizard.tsx:509
+#: packages/admin/src/components/SetupWizard.tsx:575
msgid "You're signed in via Cloudflare Access"
msgstr "您已通过 Cloudflare Access 登录"
@@ -5525,7 +5538,7 @@ msgid "you@company.com"
msgstr "you@company.com"
#: packages/admin/src/components/auth/PasskeyLogin.tsx:336
-#: packages/admin/src/components/SetupWizard.tsx:226
+#: packages/admin/src/components/SetupWizard.tsx:228
msgid "you@example.com"
msgstr "you@example.com"
@@ -5543,7 +5556,7 @@ msgstr "您的浏览器不支持通行密钥。请使用现代浏览器,如 Ch
msgid "Your device doesn't support the required security features."
msgstr "您的设备不支持所需的安全功能。"
-#: packages/admin/src/components/SetupWizard.tsx:222
+#: packages/admin/src/components/SetupWizard.tsx:224
msgid "Your Email"
msgstr "您的邮箱"
@@ -5563,7 +5576,7 @@ msgstr "您的 Instagram 用户名"
msgid "Your LinkedIn profile username"
msgstr "您的 LinkedIn 个人资料用户名"
-#: packages/admin/src/components/SetupWizard.tsx:234
+#: packages/admin/src/components/SetupWizard.tsx:236
msgid "Your Name"
msgstr "您的姓名"
diff --git a/packages/admin/src/locales/zh-TW/messages.po b/packages/admin/src/locales/zh-TW/messages.po
index f67e5677d..ed5f4ad06 100644
--- a/packages/admin/src/locales/zh-TW/messages.po
+++ b/packages/admin/src/locales/zh-TW/messages.po
@@ -59,7 +59,7 @@ msgid "{0, plural, one {(# item)} other {(# items)}}"
msgstr "{0, plural, one {(# 個項目)} other {(# 個項目)}}"
#. placeholder {0}: seedInfo.collections
-#: packages/admin/src/components/SetupWizard.tsx:181
+#: packages/admin/src/components/SetupWizard.tsx:183
msgid "{0, plural, one {# collection} other {# collections}}"
msgstr "{0, plural, one {# 個合集} other {# 個合集}}"
@@ -315,8 +315,9 @@ msgstr "• 已上傳到您的 EmDash 媒體存儲"
msgid "• URLs in your content are updated automatically"
msgstr "• 您內容中的 URL 將自動更新"
-#: packages/admin/src/components/SetupWizard.tsx:250
-#: packages/admin/src/components/SetupWizard.tsx:310
+#: packages/admin/src/components/SetupWizard.tsx:252
+#: packages/admin/src/components/SetupWizard.tsx:304
+#: packages/admin/src/components/SetupWizard.tsx:359
#: packages/admin/src/components/WordPressImport.tsx:1431
msgid "← Back"
msgstr "← 返回"
@@ -409,7 +410,7 @@ msgstr "接受並安裝"
msgid "Accept & Update"
msgstr "接受並更新"
-#: packages/admin/src/components/SetupWizard.tsx:332
+#: packages/admin/src/components/SetupWizard.tsx:381
msgid "Account"
msgstr "帳戶"
@@ -694,7 +695,7 @@ msgstr "將 WordPress 作者分配給 EmDash 用戶。文章將歸屬於所選
msgid "Authentication error: {0}"
msgstr "認證錯誤:{0}"
-#: packages/admin/src/components/LoginPage.tsx:253
+#: packages/admin/src/components/LoginPage.tsx:195
msgid "Authentication error: {error}"
msgstr "認證錯誤:{error}"
@@ -764,8 +765,9 @@ msgstr "返回"
msgid "Back to {collectionLabel} list"
msgstr "返回到 {collectionLabel} 列表"
-#: packages/admin/src/components/LoginPage.tsx:174
-#: packages/admin/src/components/LoginPage.tsx:210
+#: packages/admin/src/components/LoginPage.tsx:117
+#: packages/admin/src/components/LoginPage.tsx:153
+#: packages/admin/src/components/LoginPage.tsx:319
#: packages/admin/src/components/SignupPage.tsx:280
msgid "Back to login"
msgstr "返回登錄"
@@ -957,7 +959,7 @@ msgstr "檢查更新"
msgid "Check Site"
msgstr "檢查站點"
-#: packages/admin/src/components/LoginPage.tsx:158
+#: packages/admin/src/components/LoginPage.tsx:101
#: packages/admin/src/components/SignupPage.tsx:129
#: packages/admin/src/components/SignupPage.tsx:401
msgid "Check your email"
@@ -971,6 +973,10 @@ msgstr "正在檢查 {urlInput}..."
msgid "Checking authentication..."
msgstr "正在檢查認證..."
+#: packages/admin/src/components/SetupWizard.tsx:315
+msgid "Choose how to sign in"
+msgstr ""
+
#: packages/admin/src/components/Settings.tsx:130
msgid "Choose your preferred admin language"
msgstr "選擇您的首選管理語言"
@@ -979,7 +985,7 @@ msgstr "選擇您的首選管理語言"
msgid "Click the link in the email to continue setting up your account."
msgstr "點擊郵件中的鏈接以繼續設置您的帳戶。"
-#: packages/admin/src/components/LoginPage.tsx:169
+#: packages/admin/src/components/LoginPage.tsx:112
msgid "Click the link in the email to sign in."
msgstr "點擊郵件中的鏈接即可登錄。"
@@ -1185,8 +1191,8 @@ msgstr "內容寫入"
msgid "Continue"
msgstr "繼續"
-#: packages/admin/src/components/SetupWizard.tsx:175
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:177
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Continue →"
msgstr "繼續 →"
@@ -1291,7 +1297,7 @@ msgstr "創建新令牌"
msgid "Create one in WordPress: Users → Profile → Application Passwords"
msgstr "在 WordPress 中創建:用戶 → 個人資料 → 應用程式密碼"
-#: packages/admin/src/components/SetupWizard.tsx:305
+#: packages/admin/src/components/SetupWizard.tsx:325
msgid "Create Passkey"
msgstr "創建通行密鑰"
@@ -1336,7 +1342,7 @@ msgstr "創建分類"
msgid "Create Token"
msgstr "創建令牌"
-#: packages/admin/src/components/SetupWizard.tsx:505
+#: packages/admin/src/components/SetupWizard.tsx:571
msgid "Create your account"
msgstr "創建您的帳戶"
@@ -1724,7 +1730,7 @@ msgstr "域名已移除"
msgid "Domain updated"
msgstr "域名已更新"
-#: packages/admin/src/components/LoginPage.tsx:366
+#: packages/admin/src/components/LoginPage.tsx:340
msgid "Don't have an account? <0>Sign up0>"
msgstr "還沒有帳號?<0>立即註冊0>"
@@ -1853,13 +1859,13 @@ msgstr "郵箱"
msgid "Email (optional)"
msgstr "郵箱(可選)"
-#: packages/admin/src/components/LoginPage.tsx:183
+#: packages/admin/src/components/LoginPage.tsx:126
#: packages/admin/src/components/SignupPage.tsx:65
#: packages/admin/src/components/users/InviteUserModal.tsx:154
msgid "Email address"
msgstr "郵箱地址"
-#: packages/admin/src/components/SetupWizard.tsx:204
+#: packages/admin/src/components/SetupWizard.tsx:206
#: packages/admin/src/components/SignupPage.tsx:48
msgid "Email is required"
msgstr "郵箱爲必填項"
@@ -1965,6 +1971,10 @@ msgstr "輸入名稱"
msgid "Enter the code from your terminal"
msgstr "輸入終端中的代碼"
+#: packages/admin/src/components/LoginPage.tsx:333
+msgid "Enter your handle to sign in."
+msgstr ""
+
#: packages/admin/src/components/WordPressImport.tsx:1289
msgid "Enter your WordPress credentials to import content directly."
msgstr "輸入您的 WordPress 憑據以直接導入內容。"
@@ -1975,7 +1985,7 @@ msgstr "輸入您的 WordPress 站點 URL"
#: packages/admin/src/components/MenuEditor.tsx:83
#: packages/admin/src/components/MenuEditor.tsx:122
-#: packages/admin/src/components/SetupWizard.tsx:484
+#: packages/admin/src/components/SetupWizard.tsx:550
msgid "Error"
msgstr "錯誤"
@@ -2089,7 +2099,7 @@ msgstr "加載插件失敗:{0}"
msgid "Failed to load revisions"
msgstr "加載修訂歷史失敗"
-#: packages/admin/src/components/SetupWizard.tsx:486
+#: packages/admin/src/components/SetupWizard.tsx:552
msgid "Failed to load setup"
msgstr "加載設置失敗"
@@ -2120,8 +2130,8 @@ msgstr "儲存失敗"
msgid "Failed to save settings"
msgstr "保存設置失敗"
-#: packages/admin/src/components/LoginPage.tsx:127
-#: packages/admin/src/components/LoginPage.tsx:132
+#: packages/admin/src/components/LoginPage.tsx:70
+#: packages/admin/src/components/LoginPage.tsx:75
msgid "Failed to send magic link"
msgstr "發送免密登錄鏈接失敗"
@@ -2328,7 +2338,7 @@ msgstr "由於圖像審計,圖標已模糊"
msgid "ID"
msgstr "ID"
-#: packages/admin/src/components/LoginPage.tsx:160
+#: packages/admin/src/components/LoginPage.tsx:103
msgid "If an account exists for <0>{email}0>, we've sent a sign-in link."
msgstr "如果存在與 <0>{email}0> 關聯的帳號,我們已發送登錄鏈接。"
@@ -2448,7 +2458,7 @@ msgstr "正在導入內容..."
msgid "Importing Media"
msgstr "正在導入媒體"
-#: packages/admin/src/components/SetupWizard.tsx:163
+#: packages/admin/src/components/SetupWizard.tsx:165
msgid "Include sample content (recommended for new sites)"
msgstr "包含示例內容(推薦新站點使用)"
@@ -2561,7 +2571,7 @@ msgstr "項目已刪除"
msgid "Item updated"
msgstr "項目已更新"
-#: packages/admin/src/components/SetupWizard.tsx:238
+#: packages/admin/src/components/SetupWizard.tsx:240
#: packages/admin/src/components/SignupPage.tsx:204
msgid "Jane Doe"
msgstr "張三"
@@ -2585,7 +2595,7 @@ msgstr "關鍵詞"
msgid "Label"
msgstr "標籤"
-#: packages/admin/src/components/LoginPage.tsx:379
+#: packages/admin/src/components/LoginPage.tsx:353
#: packages/admin/src/components/Settings.tsx:129
#: packages/admin/src/components/Settings.tsx:134
msgid "Language"
@@ -2725,7 +2735,7 @@ msgstr "正在加載區塊..."
msgid "Loading settings..."
msgstr "正在加載設置..."
-#: packages/admin/src/components/SetupWizard.tsx:473
+#: packages/admin/src/components/SetupWizard.tsx:539
msgid "Loading setup..."
msgstr "正在加載設置..."
@@ -3021,7 +3031,7 @@ msgstr "多行純文本"
msgid "Multiple choices from options"
msgstr "從選項中選擇多個"
-#: packages/admin/src/components/SetupWizard.tsx:145
+#: packages/admin/src/components/SetupWizard.tsx:147
msgid "My Awesome Blog"
msgstr "我的精彩博客"
@@ -3403,7 +3413,7 @@ msgstr "或從庫中選擇"
msgid "Or click to browse. Accepts .xml files exported from WordPress."
msgstr "或點擊瀏覽。接受從 WordPress 導出的 .xml 文件。"
-#: packages/admin/src/components/LoginPage.tsx:321
+#: packages/admin/src/components/LoginPage.tsx:269
msgid "Or continue with"
msgstr "或繼續使用"
@@ -3462,10 +3472,6 @@ msgstr "重定向循環的一部分"
msgid "Pass"
msgstr "通過"
-#: packages/admin/src/components/SetupWizard.tsx:333
-msgid "Passkey"
-msgstr "通行密鑰"
-
#: packages/admin/src/components/settings/SecuritySettings.tsx:98
msgid "Passkey added successfully"
msgstr "通行密鑰添加成功"
@@ -3507,10 +3513,6 @@ msgstr "通行密鑰是一種安全的無密碼登錄方式。您可以爲不同
msgid "Passkeys are a secure, passwordless way to sign in using your device's biometrics, PIN, or security key."
msgstr "通行密鑰是一種安全的無密碼登錄方式,使用設備的生物識別、PIN 碼或安全密鑰登錄。"
-#: packages/admin/src/components/SetupWizard.tsx:297
-msgid "Passkeys are more secure than passwords. You'll use your device's biometrics, PIN, or security key to sign in."
-msgstr "通行密鑰比密碼更安全。您將使用設備的生物識別、PIN 碼或安全密鑰登錄。"
-
#: packages/admin/src/components/auth/PasskeyLogin.tsx:303
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:300
msgid "Passkeys Not Available Here"
@@ -3558,12 +3560,16 @@ msgstr "永久刪除 {title}"
msgid "Permissions"
msgstr "權限"
+#: packages/admin/src/components/SetupWizard.tsx:317
+msgid "Pick any method to create your admin account."
+msgstr ""
+
#: packages/admin/src/components/auth/PasskeyLogin.tsx:313
#: packages/admin/src/components/auth/PasskeyRegistration.tsx:310
msgid "Plain"
msgstr "普通"
-#: packages/admin/src/components/SetupWizard.tsx:206
+#: packages/admin/src/components/SetupWizard.tsx:208
msgid "Please enter a valid email"
msgstr "請輸入有效的郵箱"
@@ -3637,7 +3643,7 @@ msgid "Preparing to download files from WordPress..."
msgstr "正在準備從 WordPress 下載文件..."
#: packages/admin/src/components/auth/PasskeyLogin.tsx:166
-#: packages/admin/src/components/SetupWizard.tsx:258
+#: packages/admin/src/components/SetupWizard.tsx:260
msgid "Preparing..."
msgstr "正在準備..."
@@ -4264,7 +4270,7 @@ msgstr "區塊"
msgid "secure context"
msgstr "安全上下文"
-#: packages/admin/src/components/SetupWizard.tsx:506
+#: packages/admin/src/components/SetupWizard.tsx:572
msgid "Secure your account"
msgstr "保護您的帳戶"
@@ -4388,7 +4394,7 @@ msgstr "向新團隊成員發送邀請郵件。"
msgid "Send Invite"
msgstr "發送邀請"
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
msgid "Send magic link"
msgstr "發送免密登錄鏈接"
@@ -4404,7 +4410,7 @@ msgstr "發送測試"
msgid "Send Test Email"
msgstr "發送測試郵件"
-#: packages/admin/src/components/LoginPage.tsx:206
+#: packages/admin/src/components/LoginPage.tsx:149
#: packages/admin/src/components/settings/EmailSettings.tsx:162
#: packages/admin/src/components/SignupPage.tsx:87
#: packages/admin/src/components/SignupPage.tsx:150
@@ -4440,15 +4446,11 @@ msgstr "SEO 標題"
msgid "Set a custom display size for this image instance."
msgstr "爲此圖片實例設置自定義顯示尺寸。"
-#: packages/admin/src/components/SetupWizard.tsx:295
-msgid "Set up your passkey"
-msgstr "設置您的通行密鑰"
-
-#: packages/admin/src/components/SetupWizard.tsx:504
+#: packages/admin/src/components/SetupWizard.tsx:570
msgid "Set up your site"
msgstr "設置您的站點"
-#: packages/admin/src/components/SetupWizard.tsx:175
+#: packages/admin/src/components/SetupWizard.tsx:177
msgid "Setting up..."
msgstr "正在設置..."
@@ -4496,24 +4498,35 @@ msgstr "懸停在圖片上時顯示。"
msgid "Sign in"
msgstr "登錄"
+#: packages/admin/src/components/SetupWizard.tsx:382
+msgid "Sign In"
+msgstr ""
+
#: packages/admin/src/components/SignupPage.tsx:269
msgid "Sign in instead"
msgstr "改爲登錄"
-#: packages/admin/src/components/LoginPage.tsx:291
+#: packages/admin/src/components/LoginPage.tsx:240
msgid "Sign in to your site"
msgstr "登錄到您的網站"
-#: packages/admin/src/components/LoginPage.tsx:292
+#. placeholder {0}: authProviderList.find((p) => p.id === activeProvider)?.label ?? activeProvider
+#. placeholder {0}: provider.label
+#: packages/admin/src/components/LoginPage.tsx:239
+#: packages/admin/src/components/SetupWizard.tsx:291
+msgid "Sign in with {0}"
+msgstr ""
+
+#: packages/admin/src/components/LoginPage.tsx:237
msgid "Sign in with email"
msgstr "使用郵箱登錄"
-#: packages/admin/src/components/LoginPage.tsx:348
+#: packages/admin/src/components/LoginPage.tsx:298
msgid "Sign in with email link"
msgstr "使用郵箱鏈接登錄"
#: packages/admin/src/components/auth/PasskeyLogin.tsx:136
-#: packages/admin/src/components/LoginPage.tsx:312
+#: packages/admin/src/components/LoginPage.tsx:260
msgid "Sign in with Passkey"
msgstr "使用通行密鑰登錄"
@@ -4530,7 +4543,7 @@ msgstr "從選項中選擇一個"
msgid "Single line text input"
msgstr "單行文本輸入"
-#: packages/admin/src/components/SetupWizard.tsx:331
+#: packages/admin/src/components/SetupWizard.tsx:380
msgid "Site"
msgstr "站點"
@@ -4542,12 +4555,12 @@ msgstr "站點標識"
msgid "Site identity, logo, favicon, and reading preferences"
msgstr "網站標識、徽標、網站圖標和閱讀偏好"
-#: packages/admin/src/components/SetupWizard.tsx:329
+#: packages/admin/src/components/SetupWizard.tsx:378
msgid "Site Settings"
msgstr "站點設置"
#: packages/admin/src/components/settings/GeneralSettings.tsx:156
-#: packages/admin/src/components/SetupWizard.tsx:141
+#: packages/admin/src/components/SetupWizard.tsx:143
msgid "Site Title"
msgstr "站點標題"
@@ -4555,7 +4568,7 @@ msgstr "站點標題"
msgid "Site title & tagline"
msgstr "站點標題和副標題"
-#: packages/admin/src/components/SetupWizard.tsx:125
+#: packages/admin/src/components/SetupWizard.tsx:127
msgid "Site title is required"
msgstr "站點標題爲必填項"
@@ -4699,7 +4712,7 @@ msgid "System ({resolvedLabel})"
msgstr "跟隨系統({resolvedLabel})"
#: packages/admin/src/components/settings/GeneralSettings.tsx:162
-#: packages/admin/src/components/SetupWizard.tsx:152
+#: packages/admin/src/components/SetupWizard.tsx:154
msgid "Tagline"
msgstr "副標題"
@@ -4737,7 +4750,7 @@ msgstr "分類已創建"
msgid "Taxonomy not found:"
msgstr "未找到分類:"
-#: packages/admin/src/components/SetupWizard.tsx:180
+#: packages/admin/src/components/SetupWizard.tsx:182
msgid "Template:"
msgstr "模板:"
@@ -4770,7 +4783,7 @@ msgstr "以下表格包含內容但未註冊爲合集。註冊它們以便在管
msgid "The invited user will have this role once they complete registration."
msgstr "被邀請的用戶完成註冊後將擁有此角色。"
-#: packages/admin/src/components/LoginPage.tsx:170
+#: packages/admin/src/components/LoginPage.tsx:113
#: packages/admin/src/components/SignupPage.tsx:138
msgid "The link will expire in 15 minutes."
msgstr "鏈接將在 15 分鐘後過期。"
@@ -4895,7 +4908,7 @@ msgstr "這將從您的站點移除插件及其包。"
msgid "This will revert to the published version. Your draft changes will be lost."
msgstr "這將恢復到已發佈的版本。您的草稿更改將會丟失。"
-#: packages/admin/src/components/SetupWizard.tsx:156
+#: packages/admin/src/components/SetupWizard.tsx:158
msgid "Thoughts, tutorials, and more"
msgstr "想法、教程等"
@@ -5261,7 +5274,7 @@ msgstr "在路徑中使用 [param] 或 [...rest] 進行模式匹配。"
msgid "Use your device's biometric authentication, security key, or PIN to sign in."
msgstr "使用設備的生物識別認證、安全密鑰或 PIN 碼登錄。"
-#: packages/admin/src/components/LoginPage.tsx:359
+#: packages/admin/src/components/LoginPage.tsx:334
msgid "Use your registered passkey to sign in securely."
msgstr "使用您註冊的通行密鑰安全登錄。"
@@ -5379,7 +5392,7 @@ msgstr "我們無法連接到位於 {0} 的 WordPress 站點。這可能意味
msgid "We'll check what import options are available for your site."
msgstr "我們將檢查您的站點有哪些導入選項可用。"
-#: packages/admin/src/components/LoginPage.tsx:360
+#: packages/admin/src/components/LoginPage.tsx:331
msgid "We'll send you a link to sign in without a password."
msgstr "我們將向您發送一個無需密碼即可登錄的鏈接。"
@@ -5516,7 +5529,7 @@ msgstr "您將被重定向到 WordPress 以授權連接。"
msgid "You'll be signing up as"
msgstr "您將註冊爲"
-#: packages/admin/src/components/SetupWizard.tsx:509
+#: packages/admin/src/components/SetupWizard.tsx:575
msgid "You're signed in via Cloudflare Access"
msgstr "您已通過 Cloudflare Access 登錄"
@@ -5525,7 +5538,7 @@ msgid "you@company.com"
msgstr "you@company.com"
#: packages/admin/src/components/auth/PasskeyLogin.tsx:336
-#: packages/admin/src/components/SetupWizard.tsx:226
+#: packages/admin/src/components/SetupWizard.tsx:228
msgid "you@example.com"
msgstr "you@example.com"
@@ -5543,7 +5556,7 @@ msgstr "您的瀏覽器不支持通行密鑰。請使用現代瀏覽器,如 Ch
msgid "Your device doesn't support the required security features."
msgstr "您的設備不支持所需的安全功能。"
-#: packages/admin/src/components/SetupWizard.tsx:222
+#: packages/admin/src/components/SetupWizard.tsx:224
msgid "Your Email"
msgstr "您的郵箱"
@@ -5563,7 +5576,7 @@ msgstr "您的 Instagram 用戶名"
msgid "Your LinkedIn profile username"
msgstr "您的 LinkedIn 個人資料用戶名"
-#: packages/admin/src/components/SetupWizard.tsx:234
+#: packages/admin/src/components/SetupWizard.tsx:236
msgid "Your Name"
msgstr "您的姓名"
diff --git a/packages/admin/tests/components/LoginPage.test.tsx b/packages/admin/tests/components/LoginPage.test.tsx
index bf609c654..6c6441ef1 100644
--- a/packages/admin/tests/components/LoginPage.test.tsx
+++ b/packages/admin/tests/components/LoginPage.test.tsx
@@ -18,20 +18,16 @@ vi.mock("@tanstack/react-router", async () => {
};
});
-// Mock API — keep a reference so tests can override fetchManifest
-const mockFetchManifest = vi.fn().mockResolvedValue({
+// Mock API — keep a reference so tests can override fetchAuthMode
+const mockFetchAuthMode = vi.fn().mockResolvedValue({
authMode: "passkey",
- collections: {},
- plugins: {},
- version: "1",
- hash: "",
});
vi.mock("../../src/lib/api", async () => {
const actual = await vi.importActual("../../src/lib/api");
return {
...actual,
- fetchManifest: (...args: unknown[]) => mockFetchManifest(...args),
+ fetchAuthMode: (...args: unknown[]) => mockFetchAuthMode(...args),
apiFetch: vi
.fn()
.mockResolvedValue(new Response(JSON.stringify({ success: true }), { status: 200 })),
@@ -132,12 +128,8 @@ describe("LoginPage", () => {
});
it("shows sign up link when signup is enabled", async () => {
- mockFetchManifest.mockResolvedValueOnce({
+ mockFetchAuthMode.mockResolvedValueOnce({
authMode: "passkey",
- collections: {},
- plugins: {},
- version: "1",
- hash: "",
signupEnabled: true,
});
diff --git a/packages/admin/tests/components/SetupWizard.test.tsx b/packages/admin/tests/components/SetupWizard.test.tsx
index 08e0150cf..72c02dea6 100644
--- a/packages/admin/tests/components/SetupWizard.test.tsx
+++ b/packages/admin/tests/components/SetupWizard.test.tsx
@@ -134,6 +134,6 @@ describe("SetupWizard", () => {
await expect.element(screen.getByText("Set up your site")).toBeInTheDocument();
// Step indicator labels - use exact matching via role
await expect.element(screen.getByText("Account")).toBeInTheDocument();
- await expect.element(screen.getByText("Passkey")).toBeInTheDocument();
+ await expect.element(screen.getByText("Sign In")).toBeInTheDocument();
});
});
diff --git a/packages/auth-atproto/package.json b/packages/auth-atproto/package.json
new file mode 100644
index 000000000..97188a06c
--- /dev/null
+++ b/packages/auth-atproto/package.json
@@ -0,0 +1,53 @@
+{
+ "name": "@emdash-cms/auth-atproto",
+ "version": "0.1.0",
+ "description": "AT Protocol / Atmosphere authentication provider for EmDash CMS",
+ "type": "module",
+ "main": "src/auth.ts",
+ "exports": {
+ ".": "./src/auth.ts",
+ "./admin": "./src/admin.tsx",
+ "./oauth-client": "./src/oauth-client.ts",
+ "./resolve-handle": "./src/resolve-handle.ts",
+ "./routes/*": "./src/routes/*"
+ },
+ "files": [
+ "src"
+ ],
+ "keywords": [
+ "emdash",
+ "cms",
+ "auth",
+ "atproto",
+ "bluesky",
+ "atmosphere"
+ ],
+ "author": "Matt Kane",
+ "license": "MIT",
+ "peerDependencies": {
+ "astro": ">=5",
+ "emdash": "workspace:*",
+ "react": ">=18"
+ },
+ "devDependencies": {
+ "@atcute/lexicons": "^1.2.10",
+ "@cloudflare/kumo": "^1.16.0",
+ "@types/react": "^19.0.0",
+ "vitest": "catalog:"
+ },
+ "scripts": {
+ "test": "vitest run",
+ "typecheck": "tsgo --noEmit"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/emdash-cms/emdash.git",
+ "directory": "packages/auth-atproto"
+ },
+ "dependencies": {
+ "@atcute/identity-resolver": "^1.2.2",
+ "@atcute/oauth-node-client": "^1.1.0",
+ "@emdash-cms/auth": "workspace:*",
+ "kysely": "^0.27.6"
+ }
+}
diff --git a/packages/auth-atproto/src/admin.tsx b/packages/auth-atproto/src/admin.tsx
new file mode 100644
index 000000000..b567d7138
--- /dev/null
+++ b/packages/auth-atproto/src/admin.tsx
@@ -0,0 +1,172 @@
+/**
+ * AT Protocol Auth Provider Admin Components
+ *
+ * Provides LoginForm and SetupStep components for the pluggable auth system.
+ * These are imported at build time via the virtual:emdash/auth-providers module.
+ */
+
+import { Button, Input } from "@cloudflare/kumo";
+import * as React from "react";
+
+// ============================================================================
+// Shared icon
+// ============================================================================
+
+function AtprotoIcon({ className }: { className?: string }) {
+ return (
+
+
+
+ );
+}
+
+// ============================================================================
+// LoginButton — compact button shown in the provider grid
+// ============================================================================
+
+export function LoginButton() {
+ return (
+
+
+ Atmosphere
+
+ );
+}
+
+// ============================================================================
+// LoginForm — expanded form shown when LoginButton is clicked
+// ============================================================================
+
+export function LoginForm() {
+ const [handle, setHandle] = React.useState("");
+ const [isLoading, setIsLoading] = React.useState(false);
+ const [error, setError] = React.useState(null);
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ if (!handle.trim()) return;
+
+ setIsLoading(true);
+ setError(null);
+
+ try {
+ const response = await fetch("/_emdash/api/auth/atproto/login", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "X-EmDash-Request": "1",
+ },
+ body: JSON.stringify({ handle: handle.trim() }),
+ });
+
+ if (!response.ok) {
+ const body: { error?: { message?: string } } = await response.json().catch(() => ({}));
+ throw new Error(body?.error?.message || "Failed to start AT Protocol login");
+ }
+
+ const result: { data: { url: string } } = await response.json();
+ window.location.href = result.data.url;
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "Failed to start AT Protocol login");
+ setIsLoading(false);
+ }
+ };
+
+ return (
+
+ );
+}
+
+// ============================================================================
+// SetupStep — shown in the setup wizard
+// ============================================================================
+
+export function SetupStep({ onComplete }: { onComplete: () => void }) {
+ const [handle, setHandle] = React.useState("");
+ const [isLoading, setIsLoading] = React.useState(false);
+ const [error, setError] = React.useState(null);
+
+ // Suppress unused variable warning — onComplete is called after redirect
+ void onComplete;
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ if (!handle.trim()) return;
+
+ setIsLoading(true);
+ setError(null);
+
+ try {
+ const response = await fetch("/_emdash/api/setup/atproto-admin", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "X-EmDash-Request": "1",
+ },
+ body: JSON.stringify({ handle: handle.trim() }),
+ });
+
+ if (!response.ok) {
+ const body: { error?: { message?: string } } = await response.json().catch(() => ({}));
+ throw new Error(body?.error?.message || "Failed to start AT Protocol login");
+ }
+
+ const result: { data: { url: string } } = await response.json();
+ // Redirect to PDS authorization page — onComplete will be called after redirect back
+ window.location.href = result.data.url;
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "Failed to start AT Protocol login");
+ setIsLoading(false);
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/packages/auth-atproto/src/auth.ts b/packages/auth-atproto/src/auth.ts
new file mode 100644
index 000000000..4b0ce77f4
--- /dev/null
+++ b/packages/auth-atproto/src/auth.ts
@@ -0,0 +1,104 @@
+/**
+ * AT Protocol PDS Authentication Provider
+ *
+ * Config-time function that returns an AuthProviderDescriptor for use in astro.config.ts.
+ * When configured, EmDash adds AT Protocol as a login option alongside passkey and
+ * any other configured auth providers.
+ *
+ * @example
+ * ```ts
+ * import { atproto } from "@emdash-cms/auth-atproto";
+ *
+ * export default defineConfig({
+ * integrations: [
+ * emdash({
+ * authProviders: [
+ * atproto({ allowedDIDs: ["did:plc:abc123"] }),
+ * ],
+ * }),
+ * ],
+ * });
+ * ```
+ */
+
+import type { AuthProviderDescriptor } from "emdash";
+
+/**
+ * Configuration for AT Protocol PDS authentication
+ */
+export interface AtprotoAuthConfig {
+ /**
+ * Restrict login to specific DIDs (optional allowlist).
+ * DIDs are permanent cryptographic identifiers that can't be spoofed.
+ *
+ * @example ["did:plc:abc123", "did:web:example.com"]
+ */
+ allowedDIDs?: string[];
+
+ /**
+ * Restrict login to handles matching these patterns (optional allowlist).
+ * Supports exact matches and wildcard domains (e.g., `"*.example.com"`).
+ *
+ * Handle ownership is independently verified via DNS TXT / HTTP resolution
+ * (not trusting the PDS's claim), so this is safe for org-level gating
+ * where the org controls the domain.
+ *
+ * If both `allowedDIDs` and `allowedHandles` are set, a user matching
+ * either list is allowed.
+ *
+ * @example ["*.mycompany.com", "alice.bsky.social"]
+ */
+ allowedHandles?: string[];
+
+ /**
+ * Default role level for users who are not the first user.
+ * First user always gets Admin (50).
+ * Valid values: 10 (Subscriber), 20 (Contributor), 30 (Author), 40 (Editor), 50 (Admin).
+ * @default 10 (Subscriber)
+ */
+ defaultRole?: number;
+}
+
+/**
+ * Configure AT Protocol PDS authentication as a pluggable auth provider.
+ *
+ * Users authenticate by signing in through their PDS's authorization page.
+ * No passkeys or app passwords required — the user authenticates however
+ * their PDS supports (password, passkey, etc.).
+ *
+ * @param config Optional configuration
+ * @returns AuthProviderDescriptor for use in `emdash({ authProviders: [...] })`
+ */
+export function atproto(config?: AtprotoAuthConfig): AuthProviderDescriptor {
+ return {
+ id: "atproto",
+ label: "Atmosphere",
+ config: config ?? {},
+ adminEntry: "@emdash-cms/auth-atproto/admin",
+ routes: [
+ {
+ pattern: "/_emdash/api/auth/atproto/login",
+ entrypoint: "@emdash-cms/auth-atproto/routes/login.ts",
+ },
+ {
+ pattern: "/_emdash/api/auth/atproto/callback",
+ entrypoint: "@emdash-cms/auth-atproto/routes/callback.ts",
+ },
+ {
+ pattern: "/_emdash/api/setup/atproto-admin",
+ entrypoint: "@emdash-cms/auth-atproto/routes/setup-admin.ts",
+ },
+ {
+ // Served at root /.well-known/ (not /_emdash/) so PDS authorization
+ // servers can fetch them quickly without hitting the EmDash middleware chain.
+ pattern: "/.well-known/atproto-client-metadata.json",
+ entrypoint: "@emdash-cms/auth-atproto/routes/client-metadata.ts",
+ },
+ ],
+ publicRoutes: ["/_emdash/api/auth/atproto/"],
+ storage: {
+ states: { indexes: [] },
+ sessions: { indexes: [] },
+ },
+ };
+}
diff --git a/packages/auth-atproto/src/db-store.ts b/packages/auth-atproto/src/db-store.ts
new file mode 100644
index 000000000..e9bfb0683
--- /dev/null
+++ b/packages/auth-atproto/src/db-store.ts
@@ -0,0 +1,68 @@
+/**
+ * Database-backed store for AT Protocol OAuth state and sessions.
+ *
+ * Wraps EmDash's plugin storage infrastructure to implement the `Store`
+ * interface required by @atcute/oauth-node-client. Data is stored in the
+ * shared `_plugin_storage` table under the `auth:atproto` namespace.
+ *
+ * Each store instance maps to a storage collection (e.g., "states" or
+ * "sessions") and handles JSON serialization and TTL expiry checks.
+ */
+
+import type { Store } from "@atcute/oauth-node-client";
+
+interface StorageCollection {
+ get(id: string): Promise;
+ put(id: string, data: T): Promise;
+ delete(id: string): Promise;
+ deleteMany(ids: string[]): Promise;
+ query(options?: { limit?: number }): Promise<{ items: Array<{ id: string; data: T }> }>;
+}
+
+interface StoredEntry {
+ value: V;
+ expiresAt: number | null;
+}
+
+/**
+ * Create a Store backed by a StorageCollection.
+ *
+ * @param getCollection - Function returning the StorageCollection instance.
+ * Using a getter because on Cloudflare Workers the db
+ * binding (and thus the collection) changes per request.
+ */
+export function createDbStore(
+ getCollection: () => StorageCollection>,
+): Store {
+ return {
+ async get(key: K): Promise {
+ const entry = await getCollection().get(key);
+ if (!entry) return undefined;
+
+ // Check TTL
+ if (entry.expiresAt && Date.now() > entry.expiresAt * 1000) {
+ await getCollection().delete(key);
+ return undefined;
+ }
+ return entry.value;
+ },
+
+ async set(key: K, value: V): Promise {
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- narrowing to check optional expiresAt on opaque Store value type
+ const expiresAt = (value as { expiresAt?: number }).expiresAt ?? null;
+ await getCollection().put(key, { value, expiresAt });
+ },
+
+ async delete(key: K): Promise {
+ await getCollection().delete(key);
+ },
+
+ async clear(): Promise {
+ // Query all items and delete them in batch
+ const result = await getCollection().query({ limit: 10000 });
+ if (result.items.length > 0) {
+ await getCollection().deleteMany(result.items.map((i) => i.id));
+ }
+ },
+ };
+}
diff --git a/packages/auth-atproto/src/env.d.ts b/packages/auth-atproto/src/env.d.ts
new file mode 100644
index 000000000..87c21e991
--- /dev/null
+++ b/packages/auth-atproto/src/env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/packages/auth-atproto/src/oauth-client.ts b/packages/auth-atproto/src/oauth-client.ts
new file mode 100644
index 000000000..cf73b1187
--- /dev/null
+++ b/packages/auth-atproto/src/oauth-client.ts
@@ -0,0 +1,213 @@
+/**
+ * AT Protocol OAuth Client
+ *
+ * Creates and manages the @atcute/oauth-node-client OAuthClient instance
+ * for AT Protocol PDS authentication.
+ *
+ * The OAuthClient handles all atproto-specific OAuth complexity:
+ * - DPoP (proof-of-possession tokens)
+ * - PAR (Pushed Authorization Requests)
+ * - PKCE (Proof Key for Code Exchange)
+ * - Session management with automatic token refresh
+ * - Actor resolution (handle → DID → PDS)
+ *
+ * Uses a public client with PKCE in all environments. Per the AT Protocol
+ * OAuth spec, public clients have a 2-week session lifetime cap (vs unlimited
+ * for confidential clients), which is acceptable for a CMS admin panel.
+ * This avoids the complexity of key management, JWKS endpoints, and
+ * client assertion signing that confidential clients require.
+ *
+ * In dev (http://localhost), uses a loopback client per RFC 8252 — no client
+ * metadata endpoint needed. In production (HTTPS), the PDS fetches the
+ * client metadata document to verify the client.
+ */
+
+import {
+ CompositeDidDocumentResolver,
+ CompositeHandleResolver,
+ DohJsonHandleResolver,
+ LocalActorResolver,
+ PlcDidDocumentResolver,
+ WebDidDocumentResolver,
+ WellKnownHandleResolver,
+} from "@atcute/identity-resolver";
+import {
+ MemoryStore,
+ OAuthClient,
+ type OAuthSession,
+ type StoredSession,
+ type StoredState,
+} from "@atcute/oauth-node-client";
+
+import { createDbStore } from "./db-store.js";
+
+type Did = `did:${string}:${string}`;
+
+interface StorageCollectionLike {
+ get(id: string): Promise;
+ put(id: string, data: T): Promise;
+ delete(id: string): Promise;
+ deleteMany(ids: string[]): Promise;
+ query(options?: { limit?: number }): Promise<{ items: Array<{ id: string; data: T }> }>;
+}
+
+type AuthProviderStorageMap = Record;
+
+function isLoopback(url: string): boolean {
+ try {
+ const parsed = new URL(url);
+ return parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
+ } catch {
+ return false;
+ }
+}
+
+/**
+ * Create an AT Protocol OAuth client for a single request.
+ *
+ * Constructed per-request to avoid leaking state between requests on Workers
+ * (where module-scope vars persist across isolate reuses) and between
+ * concurrent requests on Node.
+ *
+ * Uses a public client with PKCE in all environments:
+ * - Loopback (localhost/127.0.0.1): No client metadata needed — PDS derives
+ * metadata from client_id URL parameters per RFC 8252.
+ * - Production (HTTPS): PDS fetches the client metadata document to verify
+ * the client. No JWKS or key management needed.
+ *
+ * @param baseUrl - The site's public URL.
+ * @param storage - Auth provider storage collections from `getAuthProviderStorage()`.
+ * Pass `null` to use in-memory storage (dev only).
+ */
+export async function getAtprotoOAuthClient(
+ baseUrl: string,
+ storage?: AuthProviderStorageMap | null,
+): Promise {
+ // RFC 8252 §8.3: loopback redirect URIs MUST use an IP literal (127.0.0.1),
+ // not "localhost". The atcute library enforces this — see loopbackRedirectUriSchema.
+ // The admin UI normalizes the browser to 127.0.0.1 before initiating the flow
+ // (ensureLoopbackIP in admin.tsx) so cookies stay on one origin.
+ if (isLoopback(baseUrl)) {
+ baseUrl = baseUrl.replace("://localhost", "://127.0.0.1");
+ }
+
+ const actorResolver = new LocalActorResolver({
+ handleResolver: new CompositeHandleResolver({
+ methods: {
+ dns: new DohJsonHandleResolver({ dohUrl: "https://cloudflare-dns.com/dns-query" }),
+ http: new WellKnownHandleResolver(),
+ },
+ }),
+ didDocumentResolver: new CompositeDidDocumentResolver({
+ methods: {
+ plc: new PlcDidDocumentResolver(),
+ web: new WebDidDocumentResolver(),
+ },
+ }),
+ });
+
+ // Use plugin storage when available (required for multi-instance deployments
+ // like Cloudflare Workers where in-memory state doesn't survive across
+ // requests). Fall back to MemoryStore for local dev.
+ const stores = storage
+ ? {
+ sessions: createDbStore(
+ () =>
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- plugin storage collections match StorageCollectionLike shape
+ storage.sessions as StorageCollectionLike<{
+ value: StoredSession;
+ expiresAt: number | null;
+ }>,
+ ),
+ states: createDbStore(
+ () =>
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- plugin storage collections match StorageCollectionLike shape
+ storage.states as StorageCollectionLike<{
+ value: StoredState;
+ expiresAt: number | null;
+ }>,
+ ),
+ }
+ : {
+ sessions: new MemoryStore(),
+ states: new MemoryStore(),
+ };
+
+ if (isLoopback(baseUrl)) {
+ // Loopback public client for local development.
+ // AT Protocol spec allows loopback IPs with public clients.
+ // No client metadata endpoints needed — the PDS derives
+ // metadata from the client_id URL parameters per RFC 8252.
+ // baseUrl is already normalized to 127.0.0.1 above (RFC 8252).
+ return new OAuthClient({
+ metadata: {
+ redirect_uris: [`${baseUrl}/_emdash/api/auth/atproto/callback`],
+ scope: "atproto transition:generic",
+ },
+ stores,
+ actorResolver,
+ });
+ }
+
+ // Public client for production (HTTPS).
+ // Uses PKCE for security — no client secret or key management needed.
+ // The PDS fetches the client metadata document to verify redirect_uris.
+ return new OAuthClient({
+ metadata: {
+ client_id: `${baseUrl}/.well-known/atproto-client-metadata.json`,
+ redirect_uris: [`${baseUrl}/_emdash/api/auth/atproto/callback`],
+ scope: "atproto transition:generic",
+ },
+ stores,
+ actorResolver,
+ });
+}
+
+/**
+ * Resolve an AT Protocol user's display name and handle from their PDS.
+ *
+ * Uses the authenticated session to call com.atproto.repo.getRecord
+ * for the app.bsky.actor.profile record. Returns displayName and handle
+ * (falls back to DID if resolution fails).
+ */
+export async function resolveAtprotoProfile(
+ atprotoSession: OAuthSession,
+): Promise<{ displayName: string | null; handle: string }> {
+ const did = atprotoSession.did;
+
+ // Resolve handle and displayName as independent best-effort steps.
+ // Handle comes from getSession (authoritative PDS record).
+ // DisplayName comes from the profile record (optional, cosmetic).
+ let handle: string = did;
+ let displayName: string | null = null;
+
+ // 1. Handle via getSession (needed for allowlist checks — fetch independently)
+ try {
+ const sessionRes = await atprotoSession.handle("/xrpc/com.atproto.server.getSession");
+ if (sessionRes.ok) {
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- atproto XRPC getSession returns { handle?: string }
+ const sessionData = (await sessionRes.json()) as { handle?: string };
+ if (sessionData.handle) handle = sessionData.handle;
+ }
+ } catch (error) {
+ console.warn("[atproto-auth] Failed to resolve handle via getSession:", error);
+ }
+
+ // 2. DisplayName via profile record (cosmetic — failure is fine)
+ try {
+ const res = await atprotoSession.handle(
+ `/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent(did)}&collection=app.bsky.actor.profile&rkey=self`,
+ );
+ if (res.ok) {
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- atproto XRPC getRecord returns { value?: { displayName?: string } }
+ const data = (await res.json()) as {
+ value?: { displayName?: string };
+ };
+ displayName = data.value?.displayName || null;
+ }
+ } catch (error) {
+ console.warn("[atproto-auth] Failed to resolve profile record:", error);
+ }
+
+ return { displayName, handle };
+}
diff --git a/packages/auth-atproto/src/resolve-handle.ts b/packages/auth-atproto/src/resolve-handle.ts
new file mode 100644
index 000000000..c8a5296e1
--- /dev/null
+++ b/packages/auth-atproto/src/resolve-handle.ts
@@ -0,0 +1,53 @@
+/**
+ * Independent AT Protocol handle resolution.
+ *
+ * Verifies the handle→DID binding directly against the handle's domain,
+ * without trusting any PDS or relay. This is critical for security when
+ * using handle-based allowlists — a malicious PDS could claim any handle
+ * for its DIDs, so we must verify independently.
+ *
+ * Uses @atcute/identity-resolver which supports:
+ * - DNS over HTTPS (works on Cloudflare Workers, no node:dns needed)
+ * - HTTP well-known (`https://{handle}/.well-known/atproto-did`)
+ * - Composite strategies (race both methods for speed)
+ */
+
+import {
+ CompositeHandleResolver,
+ DohJsonHandleResolver,
+ WellKnownHandleResolver,
+} from "@atcute/identity-resolver";
+
+let resolver: CompositeHandleResolver | undefined;
+
+function getResolver(): CompositeHandleResolver {
+ if (!resolver) {
+ resolver = new CompositeHandleResolver({
+ strategy: "race",
+ methods: {
+ dns: new DohJsonHandleResolver({ dohUrl: "https://cloudflare-dns.com/dns-query" }),
+ http: new WellKnownHandleResolver(),
+ },
+ });
+ }
+ return resolver;
+}
+
+/**
+ * Resolve an AT Protocol handle to a DID by verifying the binding
+ * directly against the handle's domain (DNS-over-HTTPS + HTTP, raced).
+ *
+ * Returns the verified DID, or null if resolution fails.
+ */
+export async function verifyHandleDID(handle: string): Promise {
+ // Basic validation — must be at least `x.y` (atcute expects `${string}.${string}`)
+ if (!handle.includes(".")) return null;
+
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- validated above with includes("."), satisfies atcute's template literal type
+ const did = await getResolver().resolve(handle as `${string}.${string}`);
+ return did;
+ } catch {
+ return null;
+ }
+}
diff --git a/packages/auth-atproto/src/routes/callback.ts b/packages/auth-atproto/src/routes/callback.ts
new file mode 100644
index 000000000..895efa41b
--- /dev/null
+++ b/packages/auth-atproto/src/routes/callback.ts
@@ -0,0 +1,218 @@
+/**
+ * GET /_emdash/api/auth/atproto/callback
+ *
+ * Handles the OAuth callback from the user's PDS after authentication.
+ * Exchanges the authorization code for tokens, resolves the user's identity,
+ * finds or creates an EmDash user, and establishes a session.
+ *
+ * User lookup uses oauth_accounts (provider="atproto", provider_account_id=DID)
+ * rather than email, since AT Protocol doesn't guarantee email access.
+ *
+ * For the first user (setup flow), the real email from the setup wizard is used.
+ * For subsequent users, a synthetic email is generated from the DID.
+ */
+
+import type { APIRoute } from "astro";
+
+export const prerender = false;
+
+import {
+ Role,
+ toRoleLevel,
+ findOrCreateOAuthUser,
+ OAuthError,
+ type RoleLevel,
+ type OAuthProfile,
+} from "@emdash-cms/auth";
+import { createKyselyAdapter } from "@emdash-cms/auth/adapters/kysely";
+import type { AuthProviderDescriptor } from "emdash";
+import { finalizeSetup, getPublicOrigin, OptionsRepository } from "emdash/api/route-utils";
+
+export const GET: APIRoute = async ({ request, locals, session, redirect }) => {
+ const { emdash } = locals;
+
+ if (!emdash?.db) {
+ return redirect(
+ `/_emdash/admin/login?error=server_error&message=${encodeURIComponent("Database not configured")}`,
+ );
+ }
+
+ try {
+ const url = new URL(request.url);
+ const baseUrl = getPublicOrigin(url, emdash?.config);
+
+ // Handle OAuth errors from PDS
+ const error = url.searchParams.get("error");
+ const errorDescription = url.searchParams.get("error_description");
+ if (error) {
+ const message = errorDescription || error;
+ return redirect(
+ `/_emdash/admin/login?error=atproto_denied&message=${encodeURIComponent(message)}`,
+ );
+ }
+
+ // Exchange code for session via atcute
+ const { getAtprotoOAuthClient, resolveAtprotoProfile } =
+ await import("@emdash-cms/auth-atproto/oauth-client");
+ const { getAtprotoStorage } = await import("../storage.js");
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- emdash locals satisfy EmdashLocals shape required by getAtprotoStorage
+ const storage = await getAtprotoStorage(emdash as Parameters[0]);
+ const client = await getAtprotoOAuthClient(baseUrl, storage);
+ const { session: atprotoSession } = await client.callback(url.searchParams);
+
+ const did = atprotoSession.did;
+
+ // Resolve profile for display name and handle
+ const { displayName, handle } = await resolveAtprotoProfile(atprotoSession);
+
+ // Get auth config from authProviders
+ const providers =
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- emdash.config has authProviders but Astro locals type is opaque
+ (emdash.config as { authProviders?: AuthProviderDescriptor[] } | null | undefined)
+ ?.authProviders;
+ const atprotoProvider = providers?.find((p) => p.id === "atproto");
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- provider config is an opaque Record, narrowing to known atproto config shape
+ const config = (atprotoProvider?.config ?? {}) as {
+ allowedDIDs?: string[];
+ allowedHandles?: string[];
+ defaultRole?: number;
+ };
+
+ // Check allowlists if configured (DID or handle match = allowed)
+ const hasAllowedDIDs = config.allowedDIDs && config.allowedDIDs.length > 0;
+ const hasAllowedHandles = config.allowedHandles && config.allowedHandles.length > 0;
+
+ if (hasAllowedDIDs || hasAllowedHandles) {
+ const didAllowed = hasAllowedDIDs && config.allowedDIDs!.includes(did);
+
+ let handleAllowed = false;
+ if (!didAllowed && hasAllowedHandles) {
+ // Independently verify the handle→DID binding before trusting it.
+ // A malicious PDS could claim any handle — we verify via DNS/HTTP.
+ const { verifyHandleDID } = await import("@emdash-cms/auth-atproto/resolve-handle");
+ const verifiedDid = await verifyHandleDID(handle);
+
+ if (verifiedDid === did) {
+ const normalizedHandle = handle.toLowerCase();
+ handleAllowed = config.allowedHandles!.some((pattern) => {
+ const p = pattern.toLowerCase();
+ return (
+ normalizedHandle === p ||
+ (p.startsWith("*.") && normalizedHandle.endsWith(p.slice(1)))
+ );
+ });
+ } else {
+ console.warn(
+ `[atproto-auth] Handle verification failed for ${handle}: expected DID ${did}, got ${verifiedDid}`,
+ );
+ }
+ }
+
+ if (!didAllowed && !handleAllowed) {
+ return redirect(
+ `/_emdash/admin/login?error=not_allowed&message=${encodeURIComponent("Your account is not in the allowlist")}`,
+ );
+ }
+ }
+
+ // Resolve default role from config
+ let defaultRole: RoleLevel = Role.SUBSCRIBER;
+ try {
+ if (config.defaultRole != null) defaultRole = toRoleLevel(config.defaultRole);
+ } catch {
+ console.warn(
+ `[atproto-auth] Invalid defaultRole ${config.defaultRole}, using SUBSCRIBER (${Role.SUBSCRIBER})`,
+ );
+ }
+
+ // Check setup_complete as the authoritative first-user gate.
+ // Using an option flag instead of countUsers() avoids a TOCTOU race
+ // where two concurrent callbacks both see 0 users and both create admins.
+ const adapter = createKyselyAdapter(emdash.db);
+ const options = new OptionsRepository(emdash.db);
+ const setupComplete = await options.get("emdash:setup_complete");
+ const isFirstUser = setupComplete !== true && setupComplete !== "true";
+
+ // Build synthetic email — AT Protocol doesn't guarantee email access.
+ // For the first user, read the real email from the setup wizard state.
+ let email: string;
+ if (isFirstUser) {
+ const setupState = await options.get>("emdash:setup_state");
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- setup_state is a Record with optional email string
+ email = (setupState?.email as string) || `${did.replaceAll(":", "-")}@atproto.invalid`;
+ } else {
+ email = `${did.replaceAll(":", "-")}@atproto.invalid`;
+ }
+
+ const profile: OAuthProfile = {
+ id: did,
+ email,
+ name: displayName || handle,
+ avatarUrl: null,
+ emailVerified: isFirstUser,
+ };
+
+ // Use shared find-or-create with canSelfSignup policy.
+ // When no allowlists are configured, forbid self-signup — only the
+ // initial admin (first user during setup) is allowed through.
+ const user = await findOrCreateOAuthUser(adapter, "atproto", profile, async () => {
+ if (isFirstUser) {
+ return { allowed: true, role: Role.ADMIN };
+ }
+ if (!hasAllowedDIDs && !hasAllowedHandles) {
+ return null;
+ }
+ return { allowed: true, role: defaultRole };
+ });
+
+ if (isFirstUser) {
+ // finalizeSetup is idempotent — safe if two callbacks race past the check
+ await finalizeSetup(emdash.db);
+ console.log(`[atproto-auth] Setup complete: created admin user via atproto (${did})`);
+ }
+
+ // Update display name on each login in case it changed
+ const newName = displayName || handle;
+ if (user.name !== newName) {
+ await adapter.updateUser(user.id, { name: newName });
+ }
+
+ // Check if user is disabled
+ if (user.disabled) {
+ return redirect(
+ `/_emdash/admin/login?error=account_disabled&message=${encodeURIComponent("Account disabled")}`,
+ );
+ }
+
+ // Create Astro session
+ if (session) {
+ session.set("user", { id: user.id });
+ }
+
+ // Redirect to admin dashboard
+ return redirect("/_emdash/admin");
+ } catch (callbackError) {
+ console.error("[atproto-auth] Callback error:", callbackError);
+
+ let message = "AT Protocol authentication failed. Please try again.";
+ let errorCode = "atproto_error";
+
+ if (callbackError instanceof OAuthError) {
+ errorCode = callbackError.code;
+ switch (callbackError.code) {
+ case "signup_not_allowed":
+ message = "Self-signup is not allowed. Please contact an administrator.";
+ break;
+ case "user_not_found":
+ message = "Your account was not found. It may have been deleted.";
+ break;
+ default:
+ break;
+ }
+ }
+
+ return redirect(
+ `/_emdash/admin/login?error=${errorCode}&message=${encodeURIComponent(message)}`,
+ );
+ }
+};
diff --git a/packages/auth-atproto/src/routes/client-metadata.ts b/packages/auth-atproto/src/routes/client-metadata.ts
new file mode 100644
index 000000000..602bd0c20
--- /dev/null
+++ b/packages/auth-atproto/src/routes/client-metadata.ts
@@ -0,0 +1,37 @@
+/**
+ * GET /.well-known/atproto-client-metadata.json
+ *
+ * Serves the OAuth client metadata document required by the AT Protocol OAuth spec.
+ * The user's PDS fetches this URL during authorization to verify the client.
+ */
+
+import type { APIRoute } from "astro";
+
+export const prerender = false;
+
+export const GET: APIRoute = async ({ request }) => {
+ const baseUrl = new URL(request.url).origin;
+
+ // Build metadata statically — no keyset or OAuthClient needed.
+ // This must be fast because PDS authorization servers fetch it
+ // during PAR with short timeouts (~1-2s).
+ const metadata = {
+ client_id: `${baseUrl}/.well-known/atproto-client-metadata.json`,
+ redirect_uris: [`${baseUrl}/_emdash/api/auth/atproto/callback`],
+ scope: "atproto transition:generic",
+ application_type: "web",
+ subject_type: "public",
+ response_types: ["code"],
+ grant_types: ["authorization_code", "refresh_token"],
+ token_endpoint_auth_method: "none",
+ dpop_bound_access_tokens: true,
+ };
+
+ return new Response(JSON.stringify(metadata), {
+ headers: {
+ "Content-Type": "application/json",
+ "Cache-Control": "public, max-age=3600",
+ "Access-Control-Allow-Origin": "*",
+ },
+ });
+};
diff --git a/packages/auth-atproto/src/routes/login.ts b/packages/auth-atproto/src/routes/login.ts
new file mode 100644
index 000000000..c709b6418
--- /dev/null
+++ b/packages/auth-atproto/src/routes/login.ts
@@ -0,0 +1,52 @@
+/**
+ * POST /_emdash/api/auth/atproto/login
+ *
+ * Initiates the AT Protocol OAuth flow by generating an authorization URL.
+ * The client should redirect the browser to the returned URL.
+ */
+
+import type { APIRoute } from "astro";
+
+export const prerender = false;
+
+import type { ActorIdentifier } from "@atcute/lexicons";
+import {
+ apiError,
+ apiSuccess,
+ getPublicOrigin,
+ handleError,
+ isParseError,
+ parseBody,
+} from "emdash/api/route-utils";
+import { atprotoLoginBody } from "emdash/api/schemas";
+
+export const POST: APIRoute = async ({ request, locals }) => {
+ const { emdash } = locals;
+
+ if (!emdash?.db) {
+ return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
+ }
+
+ try {
+ const body = await parseBody(request, atprotoLoginBody);
+ if (isParseError(body)) return body;
+
+ const url = new URL(request.url);
+ const baseUrl = getPublicOrigin(url, emdash?.config);
+
+ const { getAtprotoOAuthClient } = await import("@emdash-cms/auth-atproto/oauth-client");
+ const { getAtprotoStorage } = await import("../storage.js");
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- emdash locals satisfy EmdashLocals shape required by getAtprotoStorage
+ const storage = await getAtprotoStorage(emdash as Parameters[0]);
+ const client = await getAtprotoOAuthClient(baseUrl, storage);
+
+ const { url: authUrl } = await client.authorize({
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- body.handle is a validated string, ActorIdentifier is atcute's branded type
+ target: { type: "account", identifier: body.handle as ActorIdentifier },
+ });
+
+ return apiSuccess({ url: authUrl.toString() });
+ } catch (error) {
+ return handleError(error, "Failed to start AT Protocol login", "ATPROTO_LOGIN_ERROR");
+ }
+};
diff --git a/packages/auth-atproto/src/routes/setup-admin.ts b/packages/auth-atproto/src/routes/setup-admin.ts
new file mode 100644
index 000000000..4341a9f18
--- /dev/null
+++ b/packages/auth-atproto/src/routes/setup-admin.ts
@@ -0,0 +1,75 @@
+/**
+ * POST /_emdash/api/setup/atproto-admin
+ *
+ * Step 2 of setup for atproto auth: initiate OAuth flow with user's PDS.
+ * Returns the authorization URL for the client to redirect to.
+ *
+ * The actual admin creation happens in the OAuth callback
+ * (routes/callback.ts) when the PDS redirects back.
+ */
+
+import type { APIRoute } from "astro";
+
+export const prerender = false;
+
+import type { ActorIdentifier } from "@atcute/lexicons";
+import {
+ apiError,
+ apiSuccess,
+ getPublicOrigin,
+ handleError,
+ isParseError,
+ OptionsRepository,
+ parseBody,
+} from "emdash/api/route-utils";
+import { setupAtprotoAdminBody } from "emdash/api/schemas";
+
+export const POST: APIRoute = async ({ request, locals }) => {
+ const { emdash } = locals;
+
+ if (!emdash?.db) {
+ return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
+ }
+
+ try {
+ // Check if setup is already complete
+ const options = new OptionsRepository(emdash.db);
+ const setupComplete = await options.get("emdash:setup_complete");
+
+ if (setupComplete === true || setupComplete === "true") {
+ return apiError("SETUP_COMPLETE", "Setup already complete", 400);
+ }
+
+ // Parse request body
+ const body = await parseBody(request, setupAtprotoAdminBody);
+ if (isParseError(body)) return body;
+
+ // Merge into existing setup state (preserves title/tagline from step 1)
+ const existing = (await options.get>("emdash:setup_state")) ?? {};
+ await options.set("emdash:setup_state", {
+ ...existing,
+ step: "atproto_admin",
+ handle: body.handle,
+ });
+
+ // Get OAuth client and generate authorization URL
+ const url = new URL(request.url);
+ const baseUrl = getPublicOrigin(url, emdash?.config);
+ const { getAtprotoOAuthClient } = await import("@emdash-cms/auth-atproto/oauth-client");
+ const { getAtprotoStorage } = await import("../storage.js");
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- emdash locals satisfy EmdashLocals shape required by getAtprotoStorage
+ const storage = await getAtprotoStorage(emdash as Parameters[0]);
+ const client = await getAtprotoOAuthClient(baseUrl, storage);
+
+ const { url: authUrl } = await client.authorize({
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- body.handle is a validated string, ActorIdentifier is atcute's branded type
+ target: { type: "account", identifier: body.handle as ActorIdentifier },
+ });
+
+ return apiSuccess({
+ url: authUrl.toString(),
+ });
+ } catch (error) {
+ return handleError(error, "Failed to start AT Protocol setup", "SETUP_ATPROTO_ERROR");
+ }
+};
diff --git a/packages/auth-atproto/src/storage.ts b/packages/auth-atproto/src/storage.ts
new file mode 100644
index 000000000..5d3db36e1
--- /dev/null
+++ b/packages/auth-atproto/src/storage.ts
@@ -0,0 +1,40 @@
+/**
+ * Auth provider storage accessor.
+ *
+ * Resolves the atproto auth provider's storage collections from the
+ * EmDash runtime config. Used by route handlers to get storage for
+ * the OAuth client.
+ */
+
+import type { Kysely } from "kysely";
+
+interface AuthProviderDescriptorLike {
+ id: string;
+ storage?: Record }>;
+}
+
+interface EmdashLocals {
+ db: Kysely;
+ config: { authProviders?: AuthProviderDescriptorLike[] };
+}
+
+/**
+ * Get the auth provider storage collections for the atproto provider.
+ * Returns null if the provider has no storage declared or is not found.
+ */
+export async function getAtprotoStorage(
+ emdash: EmdashLocals,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+): Promise {
+ const { getAuthProviderStorage } = await import("emdash/api/route-utils");
+ const provider = emdash.config.authProviders?.find((p) => p.id === "atproto");
+ if (!provider?.storage) return null;
+
+ return getAuthProviderStorage(
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Kysely satisfies getAuthProviderStorage's Database parameter
+ emdash.db as Parameters[0],
+ "atproto",
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- provider.storage shape matches getAuthProviderStorage's expected Record type
+ provider.storage as Parameters[2],
+ );
+}
diff --git a/packages/auth-atproto/tests/auth.test.ts b/packages/auth-atproto/tests/auth.test.ts
new file mode 100644
index 000000000..f683bb864
--- /dev/null
+++ b/packages/auth-atproto/tests/auth.test.ts
@@ -0,0 +1,129 @@
+import { describe, it, expect } from "vitest";
+
+import { atproto, type AtprotoAuthConfig } from "../src/auth.js";
+
+const AUTH_ROUTES_RE = /^@emdash-cms\/auth-atproto\/routes\//;
+
+describe("atproto auth config", () => {
+ describe("AuthProviderDescriptor contract", () => {
+ it("returns id 'atproto'", () => {
+ const descriptor = atproto();
+ expect(descriptor.id).toBe("atproto");
+ });
+
+ it("has label 'Atmosphere'", () => {
+ const descriptor = atproto();
+ expect(descriptor.label).toBe("Atmosphere");
+ });
+
+ it("points adminEntry to the admin module", () => {
+ const descriptor = atproto();
+ expect(descriptor.adminEntry).toBe("@emdash-cms/auth-atproto/admin");
+ });
+
+ it("defaults config to empty object when no options provided", () => {
+ const descriptor = atproto();
+ expect(descriptor.config).toEqual({});
+ });
+
+ it("defaults config to empty object when undefined is passed", () => {
+ const descriptor = atproto(undefined);
+ expect(descriptor.config).toEqual({});
+ });
+
+ it("declares routes pointing to auth package", () => {
+ const descriptor = atproto();
+ expect(descriptor.routes).toBeDefined();
+ expect(descriptor.routes!.length).toBe(4);
+ for (const route of descriptor.routes!) {
+ expect(route.entrypoint).toMatch(AUTH_ROUTES_RE);
+ }
+ });
+
+ it("declares storage collections for OAuth state", () => {
+ const descriptor = atproto();
+ expect(descriptor.storage).toBeDefined();
+ expect(descriptor.storage).toHaveProperty("states");
+ expect(descriptor.storage).toHaveProperty("sessions");
+ });
+
+ it("declares publicRoutes with specific paths", () => {
+ const descriptor = atproto();
+ expect(descriptor.publicRoutes).toBeDefined();
+ expect(descriptor.publicRoutes).toContain("/_emdash/api/auth/atproto/");
+ // Should not have overly broad prefixes
+ expect(descriptor.publicRoutes).not.toContain("/_emdash/.well-known/");
+ });
+ });
+
+ describe("config passthrough", () => {
+ it("passes allowedDIDs through", () => {
+ const config: AtprotoAuthConfig = {
+ allowedDIDs: ["did:plc:abc123", "did:web:example.com"],
+ };
+ const descriptor = atproto(config);
+ const result = descriptor.config as AtprotoAuthConfig;
+ expect(result.allowedDIDs).toEqual(["did:plc:abc123", "did:web:example.com"]);
+ });
+
+ it("passes defaultRole through", () => {
+ const descriptor = atproto({ defaultRole: 20 });
+ const result = descriptor.config as AtprotoAuthConfig;
+ expect(result.defaultRole).toBe(20);
+ });
+
+ it("passes allowedHandles through", () => {
+ const config: AtprotoAuthConfig = {
+ allowedHandles: ["*.example.com", "alice.bsky.social"],
+ };
+ const descriptor = atproto(config);
+ const result = descriptor.config as AtprotoAuthConfig;
+ expect(result.allowedHandles).toEqual(["*.example.com", "alice.bsky.social"]);
+ });
+
+ it("passes full config through unchanged", () => {
+ const config: AtprotoAuthConfig = {
+ allowedDIDs: ["did:plc:me123"],
+ allowedHandles: ["*.example.com"],
+ defaultRole: 40,
+ };
+ const descriptor = atproto(config);
+ expect(descriptor.config).toEqual(config);
+ });
+
+ it("does not mutate the input config", () => {
+ const config: AtprotoAuthConfig = {
+ allowedDIDs: ["did:plc:alice123"],
+ allowedHandles: ["*.example.com"],
+ defaultRole: 30,
+ };
+ const original = {
+ ...config,
+ allowedDIDs: [...config.allowedDIDs!],
+ allowedHandles: [...config.allowedHandles!],
+ };
+ atproto(config);
+ expect(config).toEqual(original);
+ });
+ });
+
+ describe("descriptor shape invariants", () => {
+ it("id is always a non-empty string", () => {
+ const descriptor = atproto();
+ expect(typeof descriptor.id).toBe("string");
+ expect(descriptor.id.length).toBeGreaterThan(0);
+ });
+
+ it("label is always a non-empty string", () => {
+ const descriptor = atproto();
+ expect(typeof descriptor.label).toBe("string");
+ expect(descriptor.label.length).toBeGreaterThan(0);
+ });
+
+ it("adminEntry is always a non-empty string", () => {
+ const descriptor = atproto();
+ expect(typeof descriptor.adminEntry).toBe("string");
+ expect(descriptor.adminEntry!.length).toBeGreaterThan(0);
+ });
+ });
+});
diff --git a/packages/auth-atproto/tests/oauth-client.test.ts b/packages/auth-atproto/tests/oauth-client.test.ts
new file mode 100644
index 000000000..f057ad24f
--- /dev/null
+++ b/packages/auth-atproto/tests/oauth-client.test.ts
@@ -0,0 +1,98 @@
+import { describe, it, expect, beforeEach, vi } from "vitest";
+
+const LOCALHOST_RE = /^http:\/\/localhost/;
+
+// Reset the module singleton between tests by re-importing fresh copies
+async function freshImport() {
+ // Clear the module cache so the singleton resets
+ vi.resetModules();
+ return import("../src/oauth-client.js");
+}
+
+describe("getAtprotoOAuthClient (HTTPS - public client)", () => {
+ beforeEach(() => {
+ vi.resetModules();
+ });
+
+ it("returns an OAuthClient instance", async () => {
+ const { getAtprotoOAuthClient } = await freshImport();
+ const client = await getAtprotoOAuthClient("https://example.com");
+ expect(client).toBeDefined();
+ expect(client.metadata).toBeDefined();
+ });
+
+ it("sets client_id to the well-known metadata URL", async () => {
+ const { getAtprotoOAuthClient } = await freshImport();
+ const client = await getAtprotoOAuthClient("https://example.com");
+ expect(client.metadata.client_id).toBe(
+ "https://example.com/.well-known/atproto-client-metadata.json",
+ );
+ });
+
+ it("sets redirect_uri to the callback endpoint", async () => {
+ const { getAtprotoOAuthClient } = await freshImport();
+ const client = await getAtprotoOAuthClient("https://example.com");
+ expect(client.metadata.redirect_uris).toEqual([
+ "https://example.com/_emdash/api/auth/atproto/callback",
+ ]);
+ });
+
+ it("does not set jwks_uri (public client)", async () => {
+ const { getAtprotoOAuthClient } = await freshImport();
+ const client = await getAtprotoOAuthClient("https://example.com");
+ expect(client.metadata.jwks_uri).toBeUndefined();
+ });
+
+ it("requests atproto scope", async () => {
+ const { getAtprotoOAuthClient } = await freshImport();
+ const client = await getAtprotoOAuthClient("https://example.com");
+ expect(client.metadata.scope).toBe("atproto transition:generic");
+ });
+
+ it("creates a fresh instance per call (no shared state between requests)", async () => {
+ const { getAtprotoOAuthClient } = await freshImport();
+ const client1 = await getAtprotoOAuthClient("https://example.com");
+ const client2 = await getAtprotoOAuthClient("https://example.com");
+ expect(client1).not.toBe(client2);
+ });
+
+ it("creates distinct instances for different baseUrls", async () => {
+ const { getAtprotoOAuthClient } = await freshImport();
+ const client1 = await getAtprotoOAuthClient("https://example.com");
+ const client2 = await getAtprotoOAuthClient("https://other.com");
+ expect(client1).not.toBe(client2);
+ expect(client2.metadata.client_id).toContain("other.com");
+ });
+});
+
+describe("getAtprotoOAuthClient (localhost - loopback public client)", () => {
+ beforeEach(() => {
+ vi.resetModules();
+ });
+
+ it("creates a loopback client for http://localhost", async () => {
+ const { getAtprotoOAuthClient } = await freshImport();
+ const client = await getAtprotoOAuthClient("http://localhost:4321");
+ expect(client).toBeDefined();
+ expect(client.metadata).toBeDefined();
+ });
+
+ it("uses http://localhost client_id (loopback format)", async () => {
+ const { getAtprotoOAuthClient } = await freshImport();
+ const client = await getAtprotoOAuthClient("http://localhost:4321");
+ // Loopback clients have client_id starting with http://localhost
+ expect(client.metadata.client_id).toMatch(LOCALHOST_RE);
+ });
+
+ it("does not set jwks_uri for loopback clients", async () => {
+ const { getAtprotoOAuthClient } = await freshImport();
+ const client = await getAtprotoOAuthClient("http://localhost:4321");
+ expect(client.metadata.jwks_uri).toBeUndefined();
+ });
+
+ it("also treats 127.0.0.1 as loopback", async () => {
+ const { getAtprotoOAuthClient } = await freshImport();
+ const client = await getAtprotoOAuthClient("http://127.0.0.1:4321");
+ expect(client.metadata.client_id).toMatch(LOCALHOST_RE);
+ });
+});
diff --git a/packages/auth-atproto/tests/resolve-handle.test.ts b/packages/auth-atproto/tests/resolve-handle.test.ts
new file mode 100644
index 000000000..41196a955
--- /dev/null
+++ b/packages/auth-atproto/tests/resolve-handle.test.ts
@@ -0,0 +1,27 @@
+import { describe, it, expect, vi, afterEach } from "vitest";
+
+import { verifyHandleDID } from "../src/resolve-handle.js";
+
+describe("verifyHandleDID", () => {
+ const originalFetch = globalThis.fetch;
+
+ afterEach(() => {
+ globalThis.fetch = originalFetch;
+ vi.restoreAllMocks();
+ });
+
+ it("returns null for handles without a dot", async () => {
+ expect(await verifyHandleDID("localhost")).toBeNull();
+ expect(await verifyHandleDID("")).toBeNull();
+ });
+
+ it("returns null when resolution fails", async () => {
+ globalThis.fetch = vi.fn().mockRejectedValue(new Error("network error"));
+ expect(await verifyHandleDID("nobody.example.com")).toBeNull();
+ });
+
+ it("returns null when HTTP returns non-ok", async () => {
+ globalThis.fetch = vi.fn().mockResolvedValue(new Response("", { status: 404 }));
+ expect(await verifyHandleDID("nobody.example.com")).toBeNull();
+ });
+});
diff --git a/packages/auth-atproto/tsconfig.json b/packages/auth-atproto/tsconfig.json
new file mode 100644
index 000000000..8e16995f0
--- /dev/null
+++ b/packages/auth-atproto/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../plugins/tsconfig.base.json",
+ "compilerOptions": {
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "noUncheckedIndexedAccess": false,
+ "lib": ["es2023", "DOM", "DOM.Iterable", "esnext.typedarrays"]
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/auth/src/index.ts b/packages/auth/src/index.ts
index 9c3981482..32be9e695 100644
--- a/packages/auth/src/index.ts
+++ b/packages/auth/src/index.ts
@@ -109,9 +109,11 @@ export {
export {
createAuthorizationUrl,
handleOAuthCallback,
+ findOrCreateOAuthUser,
OAuthError,
github,
google,
+ type CanSelfSignup,
type StateStore,
type OAuthConsumerConfig,
} from "./oauth/consumer.js";
diff --git a/packages/auth/src/oauth/consumer.ts b/packages/auth/src/oauth/consumer.ts
index ac3f4a340..524f7c94f 100644
--- a/packages/auth/src/oauth/consumer.ts
+++ b/packages/auth/src/oauth/consumer.ts
@@ -111,7 +111,7 @@ export async function handleOAuthCallback(
const profile = await fetchProfile(provider, tokens.accessToken, providerName);
// Find or create user
- return findOrCreateUser(config, adapter, providerName, profile);
+ return findOrCreateOAuthUser(adapter, providerName, profile, config.canSelfSignup);
}
/**
@@ -200,13 +200,25 @@ async function fetchProfile(
}
/**
- * Find existing user or create new one (with auto-linking)
+ * Signup policy callback.
+ * Return `{ allowed: true, role }` to permit signup, or `null` to deny.
*/
-async function findOrCreateUser(
- config: OAuthConsumerConfig,
+export type CanSelfSignup = (
+ email: string,
+) => Promise<{ allowed: boolean; role: RoleLevel } | null>;
+
+/**
+ * Find existing user or create new one (with auto-linking).
+ *
+ * Shared across all OAuth providers (GitHub, Google, AT Protocol, etc.).
+ * The provider-specific token exchange happens before this function is called;
+ * this function only deals with the EmDash user record.
+ */
+export async function findOrCreateOAuthUser(
adapter: AuthAdapter,
providerName: string,
profile: OAuthProfile,
+ canSelfSignup?: CanSelfSignup,
): Promise {
// Check if OAuth account already linked
const existingAccount = await adapter.getOAuthAccount(providerName, profile.id);
@@ -238,8 +250,8 @@ async function findOrCreateUser(
}
// Check if self-signup is allowed
- if (config.canSelfSignup) {
- const signup = await config.canSelfSignup(profile.email);
+ if (canSelfSignup) {
+ const signup = await canSelfSignup(profile.email);
if (signup?.allowed) {
// Create new user
const user = await adapter.createUser({
diff --git a/packages/core/package.json b/packages/core/package.json
index 9492a9d73..2e4be47c6 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -45,6 +45,12 @@
"default": "./dist/cli/index.mjs"
},
"./routes/*": "./src/astro/routes/*",
+ "./api/route-utils": "./src/api/route-utils.ts",
+ "./api/schemas": "./src/api/schemas/index.ts",
+ "./auth/providers/github": "./src/auth/providers/github.ts",
+ "./auth/providers/github-admin": "./src/auth/providers/github-admin.tsx",
+ "./auth/providers/google": "./src/auth/providers/google.ts",
+ "./auth/providers/google-admin": "./src/auth/providers/google-admin.tsx",
"./db": {
"types": "./dist/db/index.d.mts",
"default": "./dist/db/index.mjs"
@@ -205,17 +211,24 @@
},
"peerDependencies": {
"@astrojs/react": ">=5.0.0-beta.0",
+ "@emdash-cms/auth-atproto": "workspace:*",
"@tanstack/react-query": ">=5.0.0",
"@tanstack/react-router": ">=1.100.0",
"astro": ">=6.0.0-beta.0",
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
},
+ "peerDependenciesMeta": {
+ "@emdash-cms/auth-atproto": {
+ "optional": true
+ }
+ },
"devDependencies": {
"@apidevtools/swagger-parser": "^12.1.0",
"@arethetypeswrong/cli": "catalog:",
"@emdash-cms/blocks": "workspace:*",
"@types/better-sqlite3": "^7.6.12",
+ "@types/react": "catalog:",
"@types/pg": "^8.16.0",
"@types/sanitize-html": "^2.16.0",
"@types/sax": "^1.2.7",
diff --git a/packages/core/src/api/auth-storage.ts b/packages/core/src/api/auth-storage.ts
new file mode 100644
index 000000000..c145a8556
--- /dev/null
+++ b/packages/core/src/api/auth-storage.ts
@@ -0,0 +1,37 @@
+/**
+ * Auth provider storage helper.
+ *
+ * Gives auth provider routes access to plugin-style storage collections
+ * namespaced under `auth:`. Reuses the existing `_plugin_storage`
+ * table and `PluginStorageRepository` infrastructure.
+ */
+
+import type { Kysely } from "kysely";
+
+import type { Database } from "../database/types.js";
+import { createStorageAccess } from "../plugins/context.js";
+import type { StorageCollection, StorageCollectionConfig } from "../plugins/types.js";
+
+/**
+ * Get storage collections for an auth provider.
+ *
+ * Returns a record of `StorageCollection` instances, one per declared
+ * collection in the provider's `storage` config. Data is stored in the
+ * shared `_plugin_storage` table under the namespace `auth:`.
+ *
+ * @example
+ * ```ts
+ * const storage = getAuthProviderStorage(emdash.db, "atproto", {
+ * states: { indexes: [] },
+ * sessions: { indexes: [] },
+ * });
+ * const session = await storage.sessions.get(sessionId);
+ * ```
+ */
+export function getAuthProviderStorage(
+ db: Kysely,
+ providerId: string,
+ storageConfig: Record,
+): Record {
+ return createStorageAccess(db, `auth:${providerId}`, storageConfig);
+}
diff --git a/packages/core/src/api/public-url.ts b/packages/core/src/api/public-url.ts
index cb5afb9b7..4e96de5d3 100644
--- a/packages/core/src/api/public-url.ts
+++ b/packages/core/src/api/public-url.ts
@@ -9,7 +9,10 @@
* Workers-safe: no Node.js imports.
*/
-import type { EmDashConfig } from "../astro/integration/runtime.js";
+/** Minimal config shape — avoids importing the full EmDashConfig type tree. */
+interface SiteUrlConfig {
+ siteUrl?: string;
+}
/**
* Resolve siteUrl from runtime environment variables.
@@ -67,7 +70,7 @@ function getEnvSiteUrl(): string | undefined {
* @param config The EmDash config (from `locals.emdash?.config`)
* @returns Origin string, e.g. `"https://mysite.example.com"`
*/
-export function getPublicOrigin(url: URL, config?: EmDashConfig): string {
+export function getPublicOrigin(url: URL, config?: SiteUrlConfig): string {
return config?.siteUrl || getEnvSiteUrl() || url.origin;
}
@@ -79,6 +82,6 @@ export function getPublicOrigin(url: URL, config?: EmDashConfig): string {
* @param path Path to append (must start with `/`)
* @returns Full URL string, e.g. `"https://mysite.example.com/_emdash/admin/login"`
*/
-export function getPublicUrl(url: URL, config: EmDashConfig | undefined, path: string): string {
+export function getPublicUrl(url: URL, config: SiteUrlConfig | undefined, path: string): string {
return `${getPublicOrigin(url, config)}${path}`;
}
diff --git a/packages/core/src/api/route-utils.ts b/packages/core/src/api/route-utils.ts
new file mode 100644
index 000000000..2a013d199
--- /dev/null
+++ b/packages/core/src/api/route-utils.ts
@@ -0,0 +1,14 @@
+/**
+ * Public API route utilities for auth provider routes.
+ *
+ * This module re-exports the utilities that auth provider route handlers
+ * need from core. Auth providers (plugins) import these via `emdash/api/route-utils`.
+ */
+
+export { apiError, apiSuccess, handleError } from "./error.js";
+export { parseBody, parseQuery, isParseError } from "./parse.js";
+export type { ParseResult } from "./parse.js";
+export { finalizeSetup } from "./setup-complete.js";
+export { OptionsRepository } from "../database/repositories/options.js";
+export { getAuthProviderStorage } from "./auth-storage.js";
+export { getPublicOrigin } from "./public-url.js";
diff --git a/packages/core/src/api/schemas/setup.ts b/packages/core/src/api/schemas/setup.ts
index 6b7581e14..70d16c82e 100644
--- a/packages/core/src/api/schemas/setup.ts
+++ b/packages/core/src/api/schemas/setup.ts
@@ -35,3 +35,11 @@ export const setupAdminBody = z.object({
export const setupAdminVerifyBody = z.object({
credential: registrationCredential,
});
+
+export const atprotoLoginBody = z.object({
+ handle: z.string().trim().min(1),
+});
+
+export const setupAtprotoAdminBody = z.object({
+ handle: z.string().trim().min(1),
+});
diff --git a/packages/core/src/api/setup-complete.ts b/packages/core/src/api/setup-complete.ts
new file mode 100644
index 000000000..2e13ca8e4
--- /dev/null
+++ b/packages/core/src/api/setup-complete.ts
@@ -0,0 +1,40 @@
+/**
+ * Shared setup completion logic.
+ *
+ * Called by OAuth callbacks and the passkey verify step when the first user
+ * is created during setup. Persists site title/tagline from setup state
+ * and marks setup as complete.
+ */
+
+import type { Kysely } from "kysely";
+
+import { OptionsRepository } from "../database/repositories/options.js";
+import type { Database } from "../database/types.js";
+
+/**
+ * Finalize setup after the first admin user is created.
+ *
+ * Reads the setup_state option (written by the setup wizard's step 1),
+ * persists site_title and site_tagline, then marks setup complete.
+ *
+ * Safe to call multiple times — checks setup_complete first and no-ops
+ * if already done.
+ */
+export async function finalizeSetup(db: Kysely): Promise {
+ const options = new OptionsRepository(db);
+
+ const setupComplete = await options.get("emdash:setup_complete");
+ if (setupComplete === true || setupComplete === "true") return;
+
+ // Persist site title/tagline from setup state (stored in step 1)
+ const setupState = await options.get>("emdash:setup_state");
+ if (setupState?.title && typeof setupState.title === "string") {
+ await options.set("emdash:site_title", setupState.title);
+ }
+ if (setupState?.tagline && typeof setupState.tagline === "string") {
+ await options.set("emdash:site_tagline", setupState.tagline);
+ }
+
+ await options.set("emdash:setup_complete", true);
+ await options.delete("emdash:setup_state");
+}
diff --git a/packages/core/src/astro/integration/index.ts b/packages/core/src/astro/integration/index.ts
index 18702e1df..e9248ab5f 100644
--- a/packages/core/src/astro/integration/index.ts
+++ b/packages/core/src/astro/integration/index.ts
@@ -15,7 +15,12 @@ import type { AstroIntegration, AstroIntegrationLogger } from "astro";
import type { ResolvedPlugin } from "../../plugins/types.js";
import { local } from "../storage/adapters.js";
import { notoSans } from "./font-provider.js";
-import { injectCoreRoutes, injectBuiltinAuthRoutes, injectMcpRoute } from "./routes.js";
+import {
+ injectCoreRoutes,
+ injectBuiltinAuthRoutes,
+ injectAuthProviderRoutes,
+ injectMcpRoute,
+} from "./routes.js";
import type { EmDashConfig, PluginDescriptor } from "./runtime.js";
import { createViteConfig } from "./vite-config.js";
@@ -157,6 +162,7 @@ export function emdash(config: EmDashConfig = {}): AstroIntegration {
database: resolvedConfig.database,
storage: resolvedConfig.storage,
auth: resolvedConfig.auth,
+ authProviders: resolvedConfig.authProviders,
marketplace: resolvedConfig.marketplace,
siteUrl: resolvedConfig.siteUrl,
trustedProxyHeaders: resolvedConfig.trustedProxyHeaders,
@@ -267,7 +273,12 @@ export function emdash(config: EmDashConfig = {}): AstroIntegration {
// Inject all core routes
injectCoreRoutes(injectRoute);
- // Only inject passkey/oauth/magic-link routes when NOT using external auth
+ // Inject routes from pluggable auth providers (authProviders config)
+ if (resolvedConfig.authProviders?.length) {
+ injectAuthProviderRoutes(injectRoute, resolvedConfig.authProviders);
+ }
+
+ // Inject passkey/oauth/magic-link routes unless transparent external auth is active
if (!useExternalAuth) {
injectBuiltinAuthRoutes(injectRoute);
}
diff --git a/packages/core/src/astro/integration/routes.ts b/packages/core/src/astro/integration/routes.ts
index cef83d2e3..5f2f002f8 100644
--- a/packages/core/src/astro/integration/routes.ts
+++ b/packages/core/src/astro/integration/routes.ts
@@ -46,6 +46,12 @@ export function injectCoreRoutes(injectRoute: InjectRoute): void {
entrypoint: resolveRoute("api/manifest.ts"),
});
+ // Auth mode endpoint (public — used by the login page to pick the right UI)
+ injectRoute({
+ pattern: "/_emdash/api/auth/mode",
+ entrypoint: resolveRoute("api/auth/mode.ts"),
+ });
+
injectRoute({
pattern: "/_emdash/api/dashboard",
entrypoint: resolveRoute("api/dashboard.ts"),
@@ -747,6 +753,28 @@ export function injectMcpRoute(injectRoute: InjectRoute): void {
});
}
+/**
+ * Injects routes from pluggable auth providers.
+ *
+ * Each provider declares the routes it needs in its `AuthProviderDescriptor.routes` array.
+ * Routes are injected at build time so Vite can bundle them.
+ */
+export function injectAuthProviderRoutes(
+ injectRoute: InjectRoute,
+ providers: Array<{ routes?: Array<{ pattern: string; entrypoint: string }> }>,
+): void {
+ for (const provider of providers) {
+ if (provider.routes) {
+ for (const route of provider.routes) {
+ injectRoute({
+ pattern: route.pattern,
+ entrypoint: route.entrypoint,
+ });
+ }
+ }
+ }
+}
+
/**
* Injects passkey/oauth/magic-link auth routes.
* Only used when NOT using external auth.
diff --git a/packages/core/src/astro/integration/runtime.ts b/packages/core/src/astro/integration/runtime.ts
index a62cb6b23..f5ee99a3e 100644
--- a/packages/core/src/astro/integration/runtime.ts
+++ b/packages/core/src/astro/integration/runtime.ts
@@ -7,7 +7,7 @@
* DO NOT import Node.js-only modules here (fs, path, module, etc.)
*/
-import type { AuthDescriptor } from "../../auth/types.js";
+import type { AuthDescriptor, AuthProviderDescriptor } from "../../auth/types.js";
import type { DatabaseDescriptor } from "../../db/adapters.js";
import type { MediaProviderDescriptor } from "../../media/types.js";
import type { ResolvedPlugin } from "../../plugins/types.js";
@@ -222,6 +222,24 @@ export interface EmDashConfig {
*/
auth?: AuthDescriptor;
+ /**
+ * Pluggable auth providers (login methods on the login page).
+ *
+ * Auth providers appear as options alongside passkey on the login page
+ * and setup wizard. Any provider can be used to create the initial
+ * admin account. Passkey is built-in; providers listed here are additive.
+ *
+ * @example
+ * ```ts
+ * import { atproto } from "@emdash-cms/auth-atproto";
+ *
+ * emdash({
+ * authProviders: [atproto()],
+ * })
+ * ```
+ */
+ authProviders?: AuthProviderDescriptor[];
+
/**
* MCP (Model Context Protocol) server endpoint.
*
diff --git a/packages/core/src/astro/integration/virtual-modules.ts b/packages/core/src/astro/integration/virtual-modules.ts
index e8bb0f278..33b16849c 100644
--- a/packages/core/src/astro/integration/virtual-modules.ts
+++ b/packages/core/src/astro/integration/virtual-modules.ts
@@ -10,6 +10,7 @@ import { readFileSync } from "node:fs";
import { createRequire } from "node:module";
import { resolve } from "node:path";
+import type { AuthProviderDescriptor } from "../../auth/types.js";
import type { MediaProviderDescriptor } from "../../media/types.js";
import { defaultSeed } from "../../seed/default.js";
import type { PluginDescriptor } from "./runtime.js";
@@ -47,6 +48,9 @@ export const RESOLVED_VIRTUAL_SANDBOXED_PLUGINS_ID = "\0" + VIRTUAL_SANDBOXED_PL
export const VIRTUAL_AUTH_ID = "virtual:emdash/auth";
export const RESOLVED_VIRTUAL_AUTH_ID = "\0" + VIRTUAL_AUTH_ID;
+export const VIRTUAL_AUTH_PROVIDERS_ID = "virtual:emdash/auth-providers";
+export const RESOLVED_VIRTUAL_AUTH_PROVIDERS_ID = "\0" + VIRTUAL_AUTH_PROVIDERS_ID;
+
export const VIRTUAL_MEDIA_PROVIDERS_ID = "virtual:emdash/media-providers";
export const RESOLVED_VIRTUAL_MEDIA_PROVIDERS_ID = "\0" + VIRTUAL_MEDIA_PROVIDERS_ID;
@@ -135,6 +139,43 @@ export const authenticate = _authenticate;
`;
}
+/**
+ * Generates the auth providers module.
+ *
+ * Statically imports each auth provider's `adminEntry` module and exports
+ * a registry keyed by provider ID. The admin UI uses this to render
+ * provider-specific login buttons/forms and setup steps.
+ *
+ * Follows the same pattern as `generateAdminRegistryModule()` for plugins.
+ */
+export function generateAuthProvidersModule(descriptors: AuthProviderDescriptor[]): string {
+ const withAdmin = descriptors.filter((d) => d.adminEntry);
+
+ if (withAdmin.length === 0) {
+ return `export const authProviders = {};`;
+ }
+
+ const imports: string[] = [];
+ const entries: string[] = [];
+
+ withAdmin.forEach((descriptor, index) => {
+ const varName = `authProvider${index}`;
+ imports.push(`import * as ${varName} from ${JSON.stringify(descriptor.adminEntry)};`);
+ entries.push(
+ ` ${JSON.stringify(descriptor.id)}: { ...${varName}, id: ${JSON.stringify(descriptor.id)}, label: ${JSON.stringify(descriptor.label)} },`,
+ );
+ });
+
+ return `
+// Auto-generated auth provider registry
+${imports.join("\n")}
+
+export const authProviders = {
+${entries.join("\n")}
+};
+`;
+}
+
/**
* Generates the plugins module.
* Imports and instantiates all plugins at runtime.
diff --git a/packages/core/src/astro/integration/vite-config.ts b/packages/core/src/astro/integration/vite-config.ts
index 7575f60fa..c8a62c8df 100644
--- a/packages/core/src/astro/integration/vite-config.ts
+++ b/packages/core/src/astro/integration/vite-config.ts
@@ -32,6 +32,8 @@ import {
RESOLVED_VIRTUAL_SANDBOXED_PLUGINS_ID,
VIRTUAL_AUTH_ID,
RESOLVED_VIRTUAL_AUTH_ID,
+ VIRTUAL_AUTH_PROVIDERS_ID,
+ RESOLVED_VIRTUAL_AUTH_PROVIDERS_ID,
VIRTUAL_MEDIA_PROVIDERS_ID,
RESOLVED_VIRTUAL_MEDIA_PROVIDERS_ID,
VIRTUAL_BLOCK_COMPONENTS_ID,
@@ -46,6 +48,7 @@ import {
generateDialectModule,
generateStorageModule,
generateAuthModule,
+ generateAuthProvidersModule,
generatePluginsModule,
generateAdminRegistryModule,
generateSandboxRunnerModule,
@@ -170,6 +173,9 @@ export function createVirtualModulesPlugin(options: VitePluginOptions): Plugin {
if (id === VIRTUAL_AUTH_ID) {
return RESOLVED_VIRTUAL_AUTH_ID;
}
+ if (id === VIRTUAL_AUTH_PROVIDERS_ID) {
+ return RESOLVED_VIRTUAL_AUTH_PROVIDERS_ID;
+ }
if (id === VIRTUAL_MEDIA_PROVIDERS_ID) {
return RESOLVED_VIRTUAL_MEDIA_PROVIDERS_ID;
}
@@ -228,6 +234,10 @@ export function createVirtualModulesPlugin(options: VitePluginOptions): Plugin {
}
return generateAuthModule(authDescriptor.entrypoint);
}
+ // Generate auth providers module (pluggable login methods)
+ if (id === RESOLVED_VIRTUAL_AUTH_PROVIDERS_ID) {
+ return generateAuthProvidersModule(resolvedConfig.authProviders ?? []);
+ }
// Generate media providers module
if (id === RESOLVED_VIRTUAL_MEDIA_PROVIDERS_ID) {
return generateMediaProvidersModule(resolvedConfig.mediaProviders ?? []);
diff --git a/packages/core/src/astro/middleware.ts b/packages/core/src/astro/middleware.ts
index 315e9c469..7eff90cdf 100644
--- a/packages/core/src/astro/middleware.ts
+++ b/packages/core/src/astro/middleware.ts
@@ -232,6 +232,20 @@ export const onRequest = defineMiddleware(async (context, next) => {
const { request, locals, cookies } = context;
const url = context.url;
+ // Fast path: routes outside /_emdash/ that plugins inject (e.g.,
+ // /.well-known/atproto-client-metadata.json) skip the entire runtime
+ // init + middleware chain. External servers fetch these with tight
+ // timeouts (~1-2s) so they must respond quickly even on cold starts.
+ if (!url.pathname.startsWith("/_emdash") && virtualConfig?.authProviders) {
+ const isPluginFastRoute = virtualConfig.authProviders.some(
+ (p: { routes?: { pattern?: string }[] }) =>
+ p.routes?.some((r: { pattern?: string }) => r.pattern && url.pathname === r.pattern),
+ );
+ if (isPluginFastRoute) {
+ return finalizeResponse(await next());
+ }
+ }
+
const queryRecorder = isInstrumentationEnabled()
? createRecorder(url.pathname, request.method, request.headers.get("x-perf-phase") ?? "default")
: undefined;
diff --git a/packages/core/src/astro/middleware/auth.ts b/packages/core/src/astro/middleware/auth.ts
index ffae48444..644e223d8 100644
--- a/packages/core/src/astro/middleware/auth.ts
+++ b/packages/core/src/astro/middleware/auth.ts
@@ -17,6 +17,8 @@ import { ulid } from "ulidx";
// Import auth provider via virtual module (statically bundled)
// This avoids dynamic import issues in Cloudflare Workers
import { authenticate as virtualAuthenticate } from "virtual:emdash/auth";
+// @ts-ignore - virtual module
+import virtualConfig from "virtual:emdash/config";
import { checkPublicCsrf } from "../../api/csrf.js";
import { apiError } from "../../api/error.js";
@@ -111,6 +113,7 @@ const PUBLIC_API_PREFIXES = [
const PUBLIC_API_EXACT = new Set([
"/_emdash/api/auth/passkey/options",
"/_emdash/api/auth/passkey/verify",
+ "/_emdash/api/auth/mode",
"/_emdash/api/oauth/token",
"/_emdash/api/snapshot",
// Public site search — read-only. The query layer hardcodes status='published'
@@ -119,6 +122,22 @@ const PUBLIC_API_EXACT = new Set([
"/_emdash/api/search",
]);
+// Build merged public routes at module load from auth provider descriptors.
+// Routes ending with "/" are treated as prefixes; all others are exact matches.
+const { exact: _providerExactRoutes, prefixes: _providerPrefixRoutes } = (() => {
+ const exact = new Set();
+ const prefixes: string[] = [];
+ if (!virtualConfig?.authProviders) return { exact, prefixes };
+ for (const route of virtualConfig.authProviders.flatMap((p) => p.publicRoutes ?? [])) {
+ if (route.endsWith("/")) {
+ prefixes.push(route);
+ } else {
+ exact.add(route);
+ }
+ }
+ return { exact, prefixes };
+})();
+
/**
* OAuth protocol endpoints that are CSRF-exempt by design.
*
@@ -146,6 +165,8 @@ const CSRF_EXEMPT_PUBLIC_ROUTES = new Set([
function isPublicEmDashRoute(pathname: string): boolean {
if (PUBLIC_API_EXACT.has(pathname)) return true;
if (PUBLIC_API_PREFIXES.some((p) => pathname.startsWith(p))) return true;
+ if (_providerExactRoutes.has(pathname)) return true;
+ if (_providerPrefixRoutes.some((p) => pathname.startsWith(p))) return true;
if (import.meta.env.DEV && pathname === "/_emdash/api/typegen") return true;
return false;
}
diff --git a/packages/core/src/astro/routes/PluginRegistry.tsx b/packages/core/src/astro/routes/PluginRegistry.tsx
index a131ec1f9..50d526e41 100644
--- a/packages/core/src/astro/routes/PluginRegistry.tsx
+++ b/packages/core/src/astro/routes/PluginRegistry.tsx
@@ -10,6 +10,8 @@ import { AdminApp } from "@emdash-cms/admin";
import type { Messages } from "@lingui/core";
// @ts-ignore - virtual module generated by integration
import { pluginAdmins } from "virtual:emdash/admin-registry";
+// @ts-ignore - virtual module generated by integration
+import { authProviders } from "virtual:emdash/auth-providers";
interface AdminWrapperProps {
locale: string;
@@ -17,5 +19,12 @@ interface AdminWrapperProps {
}
export default function AdminWrapper({ locale, messages }: AdminWrapperProps) {
- return ;
+ return (
+
+ );
}
diff --git a/packages/core/src/astro/routes/api/auth/mode.ts b/packages/core/src/astro/routes/api/auth/mode.ts
new file mode 100644
index 000000000..97fb735ac
--- /dev/null
+++ b/packages/core/src/astro/routes/api/auth/mode.ts
@@ -0,0 +1,57 @@
+/**
+ * GET /_emdash/api/auth/mode
+ *
+ * Public endpoint that returns the active authentication mode.
+ * Used by the login page to determine which login UI to render.
+ *
+ * Unlike the full manifest endpoint, this is intentionally public
+ * and returns only the auth mode — no collection schemas, plugin
+ * info, or other internal details.
+ */
+
+import type { APIRoute } from "astro";
+
+import { getAuthMode } from "#auth/mode.js";
+
+export const prerender = false;
+
+export const GET: APIRoute = async ({ locals }) => {
+ const { emdash } = locals;
+
+ const authMode = getAuthMode(emdash?.config);
+
+ // Only check signup for passkey auth (external providers handle their own)
+ let signupEnabled = false;
+ if (emdash?.db && authMode.type === "passkey") {
+ try {
+ const { sql } = await import("kysely");
+ const result = await sql<{ cnt: unknown }>`
+ SELECT COUNT(*) as cnt FROM allowed_domains WHERE enabled = 1
+ `.execute(emdash.db);
+ signupEnabled = Number(result.rows[0]?.cnt ?? 0) > 0;
+ } catch {
+ // Table may not exist yet
+ }
+ }
+
+ // Collect pluggable auth providers (from authProviders config)
+ const providers = (emdash?.config?.authProviders ?? []).map((p) => ({
+ id: p.id,
+ label: p.label,
+ }));
+
+ return Response.json(
+ {
+ data: {
+ authMode: authMode.type === "external" ? authMode.providerType : "passkey",
+ signupEnabled,
+ providers,
+ },
+ },
+ {
+ headers: {
+ "Cache-Control": "private, no-store",
+ },
+ },
+ );
+};
diff --git a/packages/core/src/astro/routes/api/auth/oauth/[provider].ts b/packages/core/src/astro/routes/api/auth/oauth/[provider].ts
index 58e6ce174..0d7d8fa63 100644
--- a/packages/core/src/astro/routes/api/auth/oauth/[provider].ts
+++ b/packages/core/src/astro/routes/api/auth/oauth/[provider].ts
@@ -71,16 +71,22 @@ export const GET: APIRoute = async ({ params, request, locals, redirect }) => {
const { emdash } = locals;
const provider = params.provider;
+ // Determine where to redirect errors (setup wizard or login page)
+ const referer = request.headers.get("referer") ?? "";
+ const errorRedirectBase = referer.includes("/setup")
+ ? "/_emdash/admin/setup"
+ : "/_emdash/admin/login";
+
// Validate provider
if (!provider || !isValidProvider(provider)) {
return redirect(
- `/_emdash/admin/login?error=invalid_provider&message=${encodeURIComponent("Invalid OAuth provider")}`,
+ `${errorRedirectBase}?error=invalid_provider&message=${encodeURIComponent("Invalid OAuth provider")}`,
);
}
if (!emdash?.db) {
return redirect(
- `/_emdash/admin/login?error=server_error&message=${encodeURIComponent("Database not configured")}`,
+ `${errorRedirectBase}?error=server_error&message=${encodeURIComponent("Database not configured")}`,
);
}
@@ -97,7 +103,7 @@ export const GET: APIRoute = async ({ params, request, locals, redirect }) => {
if (!providers[provider]) {
return redirect(
- `/_emdash/admin/login?error=provider_not_configured&message=${encodeURIComponent(`OAuth provider ${provider} is not configured`)}`,
+ `${errorRedirectBase}?error=provider_not_configured&message=${encodeURIComponent(`OAuth provider ${provider} is not configured. Set either EMDASH_OAUTH_${provider.toUpperCase()}_CLIENT_ID and EMDASH_OAUTH_${provider.toUpperCase()}_CLIENT_SECRET, or ${provider.toUpperCase()}_CLIENT_ID and ${provider.toUpperCase()}_CLIENT_SECRET.`)}`,
);
}
@@ -114,7 +120,7 @@ export const GET: APIRoute = async ({ params, request, locals, redirect }) => {
} catch (error) {
console.error("OAuth initiation error:", error);
return redirect(
- `/_emdash/admin/login?error=oauth_error&message=${encodeURIComponent("Failed to start OAuth flow. Please try again.")}`,
+ `${errorRedirectBase}?error=oauth_error&message=${encodeURIComponent("Failed to start OAuth flow. Please try again.")}`,
);
}
};
diff --git a/packages/core/src/astro/routes/api/auth/oauth/[provider]/callback.ts b/packages/core/src/astro/routes/api/auth/oauth/[provider]/callback.ts
index c74994cd3..f7e7cc311 100644
--- a/packages/core/src/astro/routes/api/auth/oauth/[provider]/callback.ts
+++ b/packages/core/src/astro/routes/api/auth/oauth/[provider]/callback.ts
@@ -18,7 +18,9 @@ import {
import { createKyselyAdapter } from "@emdash-cms/auth/adapters/kysely";
import { getPublicOrigin } from "#api/public-url.js";
+import { finalizeSetup } from "#api/setup-complete.js";
import { createOAuthStateStore } from "#auth/oauth-state-store.js";
+import { OptionsRepository } from "#db/repositories/options.js";
type ProviderName = "github" | "google";
@@ -126,10 +128,22 @@ export const GET: APIRoute = async ({ params, request, locals, session, redirect
);
}
+ const adapter = createKyselyAdapter(emdash.db);
+ const stateStore = createOAuthStateStore(emdash.db);
+
const config: OAuthConsumerConfig = {
baseUrl: `${getPublicOrigin(url, emdash?.config)}/_emdash`,
providers,
canSelfSignup: async (email: string) => {
+ // During setup: first user becomes admin.
+ // Check setup_complete flag instead of countUsers() to avoid
+ // a TOCTOU race where concurrent callbacks both see 0 users.
+ const options = new OptionsRepository(emdash.db);
+ const setupComplete = await options.get("emdash:setup_complete");
+ if (setupComplete !== true && setupComplete !== "true") {
+ return { allowed: true, role: Role.ADMIN };
+ }
+
// Extract domain from email
const domain = email.split("@")[1]?.toLowerCase();
if (!domain) {
@@ -168,10 +182,16 @@ export const GET: APIRoute = async ({ params, request, locals, session, redirect
},
};
- const adapter = createKyselyAdapter(emdash.db);
- const stateStore = createOAuthStateStore(emdash.db);
-
+ const options = new OptionsRepository(emdash.db);
+ const setupCompleteBefore = await options.get("emdash:setup_complete");
const user = await handleOAuthCallback(config, adapter, provider, code, state, stateStore);
+ const isFirstUser = setupCompleteBefore !== true && setupCompleteBefore !== "true";
+
+ // Finalize setup outside the transaction (idempotent, safe if two callbacks race).
+ if (isFirstUser) {
+ await finalizeSetup(emdash.db);
+ console.log(`[oauth] Setup complete: created admin user via ${provider} (${user.email})`);
+ }
// Create session
if (session) {
diff --git a/packages/core/src/astro/routes/api/setup/admin.ts b/packages/core/src/astro/routes/api/setup/admin.ts
index 9d697cfdb..487ff02af 100644
--- a/packages/core/src/astro/routes/api/setup/admin.ts
+++ b/packages/core/src/astro/routes/api/setup/admin.ts
@@ -49,6 +49,10 @@ export const POST: APIRoute = async ({ cookies, request, locals }) => {
const body = await parseBody(request, setupAdminBody);
if (isParseError(body)) return body;
+ // Preserve title/tagline from step 1 by reading existing setup state
+ // before we overwrite it below.
+ const existingState = await options.get>("emdash:setup_state");
+
// Mint a fresh session nonce. This binds the follow-up
// /setup/admin/verify call to the same browser that made this
// request, so an unauthenticated attacker on another host cannot
@@ -81,9 +85,11 @@ export const POST: APIRoute = async ({ cookies, request, locals }) => {
challengeStore,
);
- // Store the nonce alongside the rest of the setup state. The verify
- // endpoint will constant-time compare this with the incoming cookie.
+ // Store the nonce alongside the rest of the setup state, preserving
+ // title/tagline from step 1. The verify endpoint will constant-time
+ // compare the nonce with the incoming cookie.
await options.set("emdash:setup_state", {
+ ...existingState,
step: "admin",
email: body.email.toLowerCase(),
name: body.name || null,
diff --git a/packages/core/src/astro/routes/api/setup/index.ts b/packages/core/src/astro/routes/api/setup/index.ts
index c4709ee99..bbf59ce62 100644
--- a/packages/core/src/astro/routes/api/setup/index.ts
+++ b/packages/core/src/astro/routes/api/setup/index.ts
@@ -81,7 +81,7 @@ export const POST: APIRoute = async ({ request, url, locals }) => {
// 5. Store setup state
// In external auth mode, mark setup complete immediately (first user to login becomes admin)
- // In passkey mode, setup_complete is set after admin user is created
+ // Otherwise, setup_complete is set after admin user is created (passkey or auth provider)
const authMode = getAuthMode(emdash.config);
const useExternalAuth = authMode.type === "external";
@@ -105,7 +105,7 @@ export const POST: APIRoute = async ({ request, url, locals }) => {
await options.set("emdash:site_tagline", body.tagline);
}
} else {
- // Passkey mode: store state for next step (admin creation)
+ // Passkey/provider mode: store state for next step (admin creation)
await options.set("emdash:setup_state", {
step: "site_complete",
title: body.title,
diff --git a/packages/core/src/astro/routes/api/setup/status.ts b/packages/core/src/astro/routes/api/setup/status.ts
index 4f9c068b8..99c71a741 100644
--- a/packages/core/src/astro/routes/api/setup/status.ts
+++ b/packages/core/src/astro/routes/api/setup/status.ts
@@ -91,7 +91,7 @@ export const GET: APIRoute = async ({ locals }) => {
const authMode = getAuthMode(emdash.config);
const useExternalAuth = authMode.type === "external";
- // In external auth mode, setup is complete if flag is set (no users required initially)
+ // In external auth mode (not atproto), setup is complete if flag is set (no users required initially)
if (useExternalAuth && isComplete) {
return apiSuccess({
needsSetup: false,
diff --git a/packages/core/src/auth/mode.ts b/packages/core/src/auth/mode.ts
index e59998ae8..45a46dbb2 100644
--- a/packages/core/src/auth/mode.ts
+++ b/packages/core/src/auth/mode.ts
@@ -6,9 +6,21 @@
*/
import type { EmDashConfig } from "../astro/integration/runtime.js";
-import type { AuthDescriptor, AuthResult, ExternalAuthConfig } from "./types.js";
+import type {
+ AuthDescriptor,
+ AuthProviderDescriptor,
+ AuthRouteDescriptor,
+ AuthResult,
+ ExternalAuthConfig,
+} from "./types.js";
-export type { AuthDescriptor, AuthResult, ExternalAuthConfig };
+export type {
+ AuthDescriptor,
+ AuthProviderDescriptor,
+ AuthRouteDescriptor,
+ AuthResult,
+ ExternalAuthConfig,
+};
/**
* Passkey auth mode (default)
@@ -59,7 +71,7 @@ export function getAuthMode(
): AuthMode {
const auth = config?.auth;
- // Check for AuthDescriptor (new style)
+ // Check for AuthDescriptor (transparent external auth like Cloudflare Access)
if (auth && "entrypoint" in auth && auth.entrypoint) {
return {
type: "external",
diff --git a/packages/core/src/auth/providers/github-admin.tsx b/packages/core/src/auth/providers/github-admin.tsx
new file mode 100644
index 000000000..79283bd69
--- /dev/null
+++ b/packages/core/src/auth/providers/github-admin.tsx
@@ -0,0 +1,29 @@
+/**
+ * GitHub OAuth Admin Components
+ *
+ * LoginButton for the login page, rendered via the auth provider virtual module.
+ */
+
+import { LinkButton } from "@cloudflare/kumo";
+import * as React from "react";
+
+function GitHubIcon({ className }: { className?: string }) {
+ return (
+
+
+
+ );
+}
+
+export function LoginButton() {
+ return (
+
+
+ GitHub
+
+ );
+}
diff --git a/packages/core/src/auth/providers/github.ts b/packages/core/src/auth/providers/github.ts
new file mode 100644
index 000000000..199d56d51
--- /dev/null
+++ b/packages/core/src/auth/providers/github.ts
@@ -0,0 +1,31 @@
+/**
+ * GitHub OAuth Auth Provider
+ *
+ * Returns an AuthProviderDescriptor for GitHub OAuth login.
+ * Credentials are read from environment variables at runtime.
+ *
+ * @example
+ * ```ts
+ * import { github } from "emdash/auth/providers/github";
+ *
+ * emdash({
+ * authProviders: [github()],
+ * })
+ * ```
+ */
+
+import type { AuthProviderDescriptor } from "../types.js";
+
+/**
+ * Configure GitHub OAuth as an auth provider.
+ *
+ * Requires `EMDASH_OAUTH_GITHUB_CLIENT_ID` and `EMDASH_OAUTH_GITHUB_CLIENT_SECRET`
+ * (or `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET`) environment variables.
+ */
+export function github(): AuthProviderDescriptor {
+ return {
+ id: "github",
+ label: "GitHub",
+ adminEntry: "emdash/auth/providers/github-admin",
+ };
+}
diff --git a/packages/core/src/auth/providers/google-admin.tsx b/packages/core/src/auth/providers/google-admin.tsx
new file mode 100644
index 000000000..5284f0999
--- /dev/null
+++ b/packages/core/src/auth/providers/google-admin.tsx
@@ -0,0 +1,44 @@
+/**
+ * Google OAuth Admin Components
+ *
+ * LoginButton for the login page, rendered via the auth provider virtual module.
+ */
+
+import { LinkButton } from "@cloudflare/kumo";
+import * as React from "react";
+
+function GoogleIcon({ className }: { className?: string }) {
+ return (
+
+
+
+
+
+
+ );
+}
+
+export function LoginButton() {
+ return (
+
+
+ Google
+
+ );
+}
diff --git a/packages/core/src/auth/providers/google.ts b/packages/core/src/auth/providers/google.ts
new file mode 100644
index 000000000..924ab9ca5
--- /dev/null
+++ b/packages/core/src/auth/providers/google.ts
@@ -0,0 +1,31 @@
+/**
+ * Google OAuth Auth Provider
+ *
+ * Returns an AuthProviderDescriptor for Google OAuth login.
+ * Credentials are read from environment variables at runtime.
+ *
+ * @example
+ * ```ts
+ * import { google } from "emdash/auth/providers/google";
+ *
+ * emdash({
+ * authProviders: [google()],
+ * })
+ * ```
+ */
+
+import type { AuthProviderDescriptor } from "../types.js";
+
+/**
+ * Configure Google OAuth as an auth provider.
+ *
+ * Requires `EMDASH_OAUTH_GOOGLE_CLIENT_ID` and `EMDASH_OAUTH_GOOGLE_CLIENT_SECRET`
+ * (or `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET`) environment variables.
+ */
+export function google(): AuthProviderDescriptor {
+ return {
+ id: "google",
+ label: "Google",
+ adminEntry: "emdash/auth/providers/google-admin",
+ };
+}
diff --git a/packages/core/src/auth/types.ts b/packages/core/src/auth/types.ts
index 23fc2af14..521850144 100644
--- a/packages/core/src/auth/types.ts
+++ b/packages/core/src/auth/types.ts
@@ -2,7 +2,13 @@
* Auth Provider Types
*
* Defines the interfaces for pluggable authentication providers.
- * Providers like Cloudflare Access implement these interfaces.
+ *
+ * Two systems coexist:
+ * - `AuthDescriptor` — transparent auth (Cloudflare Access) that authenticates
+ * every request via headers/cookies. No login UI needed.
+ * - `AuthProviderDescriptor` — pluggable login methods (GitHub, Google,
+ * AT Protocol, etc.) that appear as options on the login page and setup
+ * wizard. Passkey is built-in; providers are additive.
*/
/**
@@ -22,10 +28,10 @@ export interface AuthResult {
}
/**
- * Auth descriptor - returned by auth adapter functions (e.g., access())
+ * Auth descriptor — transparent auth providers (e.g., Cloudflare Access).
*
- * Similar to DatabaseDescriptor and StorageDescriptor, this allows
- * auth providers to be configured at build time and loaded at runtime.
+ * These authenticate every request via headers/cookies. No login UI needed.
+ * The module's `authenticate()` function is called by middleware on each request.
*/
export interface AuthDescriptor {
/**
@@ -64,6 +70,110 @@ export interface AuthProviderModule {
authenticate(request: Request, config: unknown): Promise;
}
+// ---------------------------------------------------------------------------
+// Pluggable Auth Providers (additive login methods)
+// ---------------------------------------------------------------------------
+
+/**
+ * Descriptor for a pluggable auth provider.
+ *
+ * Auth providers appear as login options on the login page and setup wizard.
+ * They coexist with passkey (which is built-in) and with each other.
+ * Any provider can be used to create the initial admin account.
+ *
+ * @example
+ * ```ts
+ * // astro.config.ts
+ * import { atproto } from "@emdash-cms/auth-atproto";
+ *
+ * emdash({
+ * authProviders: [atproto(), github(), google()],
+ * })
+ * ```
+ */
+export interface AuthProviderDescriptor {
+ /** Unique provider ID (e.g., "github", "atproto") */
+ id: string;
+
+ /** Human-readable label for UI (e.g., "GitHub", "AT Protocol") */
+ label: string;
+
+ /** Provider-specific config (JSON-serializable) */
+ config?: unknown;
+
+ /**
+ * Module exporting React components for the admin UI.
+ * Statically imported at build time via virtual module.
+ *
+ * The module should export components matching `AuthProviderAdminExports`.
+ */
+ adminEntry?: string;
+
+ /**
+ * Astro route handlers this provider needs injected at build time.
+ * Used for login initiation, OAuth callbacks, well-known endpoints, etc.
+ */
+ routes?: AuthRouteDescriptor[];
+
+ /**
+ * URL prefixes/paths that should bypass auth middleware.
+ * Added to the public routes set so login/callback endpoints work
+ * for unauthenticated users.
+ */
+ publicRoutes?: string[];
+
+ /**
+ * Storage collections for persistent auth state (e.g., OAuth sessions).
+ * Same format as plugin storage — collections are stored in the shared
+ * `_plugin_storage` table namespaced under `auth:`.
+ *
+ * Access via `getAuthProviderStorage()` from `emdash/api/route-utils`.
+ */
+ storage?: Record<
+ string,
+ { indexes?: Array; uniqueIndexes?: Array }
+ >;
+}
+
+/**
+ * A route that an auth provider needs injected into the Astro app.
+ */
+export interface AuthRouteDescriptor {
+ /** URL pattern (e.g., "/_emdash/api/auth/atproto/login") */
+ pattern: string;
+ /** Module specifier for the Astro route handler */
+ entrypoint: string;
+}
+
+/**
+ * Expected exports from an auth provider's `adminEntry` module.
+ *
+ * All exports are optional. Providers export whichever components
+ * make sense for their auth flow.
+ */
+export interface AuthProviderAdminExports {
+ /**
+ * Compact button for the login page (icon + label).
+ * Used for providers with a simple redirect flow (GitHub, Google).
+ * Rendered in the "Or continue with" section.
+ */
+ LoginButton?: import("react").ComponentType;
+
+ /**
+ * Full login form for providers that need custom input.
+ * Used for providers like AT Protocol that need a handle field.
+ * Rendered as an expandable section on the login page.
+ */
+ LoginForm?: import("react").ComponentType;
+
+ /**
+ * Setup wizard step for creating the admin account via this provider.
+ * When present, this provider appears as an option in the setup wizard's
+ * "Create admin account" step.
+ */
+ SetupStep?: import("react").ComponentType<{ onComplete: () => void }>;
+}
+
/**
* Configuration options common to external auth providers
*/
diff --git a/packages/core/src/cli/commands/bundle.ts b/packages/core/src/cli/commands/bundle.ts
index a250e5653..b1d6f88b9 100644
--- a/packages/core/src/cli/commands/bundle.ts
+++ b/packages/core/src/cli/commands/bundle.ts
@@ -38,7 +38,7 @@ import {
ICON_SIZE,
} from "./bundle-utils.js";
-const TS_EXT_RE = /\.tsx?$/;
+const TS_EXT_RE = /\.(tsx?|[mc]?js)$/;
const SLASH_RE = /\//g;
const LEADING_AT_RE = /^@/;
const emdash_SCOPE_RE = /^@emdash-cms\//;
@@ -163,6 +163,8 @@ export const bundleCommand = defineCommand({
const tmpDir = join(pluginDir, ".emdash-bundle-tmp");
try {
+ // Clean up any stale temp directory from a previous failed run
+ await rm(tmpDir, { recursive: true, force: true });
await mkdir(tmpDir, { recursive: true });
// Build main entry to extract manifest.
diff --git a/packages/core/src/components/InlinePortableTextEditor.tsx b/packages/core/src/components/InlinePortableTextEditor.tsx
index 04721c4c9..661a985a6 100644
--- a/packages/core/src/components/InlinePortableTextEditor.tsx
+++ b/packages/core/src/components/InlinePortableTextEditor.tsx
@@ -1795,7 +1795,7 @@ export function InlinePortableTextEditor({
// Don't save if focus moved to the slash menu (portalled to body)
if (related?.closest(".emdash-slash-menu")) return;
if (related?.closest(".emdash-media-picker")) return;
- save();
+ void save();
},
[save, mediaPickerOpen],
);
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 8d0c52806..9dc38757f 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -481,11 +481,14 @@ export type {
SearchStats,
} from "./search/index.js";
-// Auth types (for platform-specific auth providers)
+// Auth types (for platform-specific auth providers and pluggable login methods)
export type {
AuthDescriptor,
+ AuthProviderDescriptor,
+ AuthProviderAdminExports,
AuthProviderModule,
AuthResult,
+ AuthRouteDescriptor,
ExternalAuthConfig,
} from "./auth/types.js";
diff --git a/packages/core/src/virtual-modules.d.ts b/packages/core/src/virtual-modules.d.ts
index 3124fa1e9..fc1429668 100644
--- a/packages/core/src/virtual-modules.d.ts
+++ b/packages/core/src/virtual-modules.d.ts
@@ -7,12 +7,18 @@
declare module "virtual:emdash/config" {
import type { I18nConfig } from "./i18n/config.js";
- import type { DatabaseDescriptor, StorageDescriptor, AuthDescriptor } from "./index.js";
+ import type {
+ AuthDescriptor,
+ AuthProviderDescriptor,
+ DatabaseDescriptor,
+ StorageDescriptor,
+ } from "./index.js";
interface VirtualConfig {
database?: DatabaseDescriptor;
storage?: StorageDescriptor;
auth?: AuthDescriptor;
+ authProviders?: AuthProviderDescriptor[];
i18n?: I18nConfig | null;
}
@@ -103,6 +109,20 @@ declare module "virtual:emdash/block-components" {
export const pluginBlockComponents: Record;
}
+declare module "virtual:emdash/auth-providers" {
+ import type { ComponentType } from "react";
+
+ interface AuthProviderEntry {
+ id: string;
+ label: string;
+ LoginButton?: ComponentType;
+ LoginForm?: ComponentType;
+ SetupStep?: ComponentType<{ onComplete: () => void }>;
+ }
+
+ export const authProviders: Record;
+}
+
declare module "virtual:emdash/wait-until" {
/**
* Optional host-provided lifetime extender for work deferred past the
diff --git a/packages/core/tests/unit/auth/mcp-discovery-post.test.ts b/packages/core/tests/unit/auth/mcp-discovery-post.test.ts
index 5cb800214..2e2a65522 100644
--- a/packages/core/tests/unit/auth/mcp-discovery-post.test.ts
+++ b/packages/core/tests/unit/auth/mcp-discovery-post.test.ts
@@ -1,6 +1,7 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("virtual:emdash/auth", () => ({ authenticate: vi.fn() }));
+vi.mock("virtual:emdash/config", () => ({ default: {} }));
vi.mock("astro:middleware", () => ({
defineMiddleware: (handler: unknown) => handler,
}));
diff --git a/packages/core/tests/unit/middleware/oauth-csrf.test.ts b/packages/core/tests/unit/middleware/oauth-csrf.test.ts
index 6da5005d3..cd298344a 100644
--- a/packages/core/tests/unit/middleware/oauth-csrf.test.ts
+++ b/packages/core/tests/unit/middleware/oauth-csrf.test.ts
@@ -1,6 +1,7 @@
import { beforeAll, describe, expect, it, vi } from "vitest";
vi.mock("virtual:emdash/auth", () => ({ authenticate: vi.fn() }));
+vi.mock("virtual:emdash/config", () => ({ default: {} }));
vi.mock("astro:middleware", () => ({
defineMiddleware: (handler: unknown) => handler,
}));
diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json
index 1899f205c..2f23235f1 100644
--- a/packages/core/tsconfig.json
+++ b/packages/core/tsconfig.json
@@ -16,6 +16,7 @@
"src/astro/**",
"src/components/**",
"src/preview/**",
- "src/ui.ts"
+ "src/ui.ts",
+ "src/auth/providers/*-admin.tsx"
]
}
diff --git a/packages/plugins/atproto/package.json b/packages/plugins/atproto/package.json
index 6f0470696..5ec480c2f 100644
--- a/packages/plugins/atproto/package.json
+++ b/packages/plugins/atproto/package.json
@@ -12,7 +12,8 @@
"./sandbox": "./dist/sandbox-entry.mjs"
},
"files": [
- "dist"
+ "dist",
+ "src"
],
"keywords": [
"emdash",
@@ -31,12 +32,10 @@
},
"devDependencies": {
"tsdown": "catalog:",
- "typescript": "catalog:",
"vitest": "catalog:"
},
"scripts": {
"build": "tsdown src/index.ts src/sandbox-entry.ts --format esm --dts --clean",
- "dev": "tsdown src/index.ts src/sandbox-entry.ts --format esm --dts --watch",
"test": "vitest run",
"typecheck": "tsgo --noEmit"
},
@@ -44,5 +43,7 @@
"type": "git",
"url": "git+https://github.com/emdash-cms/emdash.git",
"directory": "packages/plugins/atproto"
- }
+ },
+ "dependencies": {}
+
}
diff --git a/packages/plugins/atproto/tests/plugin.test.ts b/packages/plugins/atproto/tests/plugin.test.ts
index 1a536bf79..d234258b9 100644
--- a/packages/plugins/atproto/tests/plugin.test.ts
+++ b/packages/plugins/atproto/tests/plugin.test.ts
@@ -12,12 +12,9 @@ describe("atprotoPlugin descriptor", () => {
expect(descriptor.adminWidgets).toHaveLength(1);
});
- it("declares the storage used by the sandbox implementation", () => {
+ it("uses standard format", () => {
const descriptor = atprotoPlugin();
- expect(descriptor.storage).toHaveProperty("records");
- expect(descriptor.storage!.records!.indexes).toContain("contentId");
- expect(descriptor.storage!.records!.indexes).toContain("status");
- expect(descriptor.storage!.records!.indexes).toContain("lastSyncedAt");
+ expect(descriptor.format).toBe("standard");
});
it("declares required capabilities", () => {
@@ -26,6 +23,14 @@ describe("atprotoPlugin descriptor", () => {
expect(descriptor.capabilities).toContain("network:fetch:any");
});
+ it("declares the storage used by the sandbox implementation", () => {
+ const descriptor = atprotoPlugin();
+ expect(descriptor.storage).toHaveProperty("records");
+ expect(descriptor.storage!.records!.indexes).toContain("contentId");
+ expect(descriptor.storage!.records!.indexes).toContain("status");
+ expect(descriptor.storage!.records!.indexes).toContain("lastSyncedAt");
+ });
+
it("exposes an admin status page and widget", () => {
const descriptor = atprotoPlugin();
expect(descriptor.adminPages).toEqual([
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index cbe382e35..d6a46a072 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -26,19 +26,19 @@ catalogs:
version: 4.20260305.1
'@lingui/babel-plugin-lingui-macro':
specifier: ^5.9.4
- version: 5.9.4
+ version: 5.9.5
'@lingui/cli':
specifier: ^5.9.4
- version: 5.9.4
+ version: 5.9.5
'@lingui/core':
specifier: ^5.9.4
- version: 5.9.4
+ version: 5.9.5
'@lingui/macro':
specifier: ^5.9.4
- version: 5.9.4
+ version: 5.9.5
'@lingui/react':
specifier: ^5.9.4
- version: 5.9.4
+ version: 5.9.5
'@phosphor-icons/react':
specifier: ^2.1.10
version: 2.1.10
@@ -238,6 +238,43 @@ importers:
specifier: 'catalog:'
version: 4.80.0(@cloudflare/workers-types@4.20260305.1)
+ demos/my-site:
+ dependencies:
+ '@astrojs/cloudflare':
+ specifier: 'catalog:'
+ version: 13.1.7(@types/node@24.10.13)(astro@6.0.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.55.2)(tsx@4.21.0)(typescript@6.0.0-beta)(yaml@2.8.2))(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(workerd@1.20260401.1)(wrangler@4.80.0(@cloudflare/workers-types@4.20260305.1))(yaml@2.8.2)
+ '@astrojs/react':
+ specifier: 'catalog:'
+ version: 5.0.0(@types/node@24.10.13)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(jiti@2.6.1)(lightningcss@1.31.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tsx@4.21.0)(yaml@2.8.2)
+ '@emdash-cms/auth-atproto':
+ specifier: workspace:*
+ version: link:../../packages/auth-atproto
+ '@emdash-cms/cloudflare':
+ specifier: workspace:*
+ version: link:../../packages/cloudflare
+ astro:
+ specifier: 'catalog:'
+ version: 6.0.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.55.2)(tsx@4.21.0)(typescript@6.0.0-beta)(yaml@2.8.2)
+ emdash:
+ specifier: workspace:*
+ version: link:../../packages/core
+ react:
+ specifier: 'catalog:'
+ version: 19.2.4
+ react-dom:
+ specifier: 'catalog:'
+ version: 19.2.4(react@19.2.4)
+ devDependencies:
+ '@astrojs/check':
+ specifier: 'catalog:'
+ version: 0.9.7(prettier-plugin-astro@0.14.1)(prettier@3.8.1)(typescript@6.0.0-beta)
+ '@cloudflare/workers-types':
+ specifier: 'catalog:'
+ version: 4.20260305.1
+ wrangler:
+ specifier: 'catalog:'
+ version: 4.80.0(@cloudflare/workers-types@4.20260305.1)
+
demos/playground:
dependencies:
'@astrojs/cloudflare':
@@ -403,6 +440,12 @@ importers:
'@astrojs/react':
specifier: 'catalog:'
version: 5.0.0(@types/node@24.10.13)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(jiti@2.6.1)(lightningcss@1.31.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tsx@4.21.0)(yaml@2.8.2)
+ '@emdash-cms/auth-atproto':
+ specifier: workspace:*
+ version: link:../../packages/auth-atproto
+ '@emdash-cms/plugin-atproto':
+ specifier: workspace:*
+ version: link:../../packages/plugins/atproto
'@emdash-cms/plugin-audit-log':
specifier: workspace:*
version: link:../../packages/plugins/audit-log
@@ -655,10 +698,10 @@ importers:
version: 0.27.16(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@lingui/core':
specifier: 'catalog:'
- version: 5.9.4(@lingui/babel-plugin-lingui-macro@5.9.4(typescript@5.9.3))
+ version: 5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(typescript@5.9.3))
'@lingui/react':
specifier: 'catalog:'
- version: 5.9.4(@lingui/babel-plugin-lingui-macro@5.9.4(typescript@5.9.3))(react@19.2.4)
+ version: 5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(typescript@5.9.3))(react@19.2.4)
'@phosphor-icons/react':
specifier: 'catalog:'
version: 2.1.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -746,13 +789,13 @@ importers:
version: 7.29.0
'@lingui/babel-plugin-lingui-macro':
specifier: 'catalog:'
- version: 5.9.4(typescript@5.9.3)
+ version: 5.9.5(typescript@5.9.3)
'@lingui/cli':
specifier: 'catalog:'
- version: 5.9.4(typescript@5.9.3)
+ version: 5.9.5(typescript@5.9.3)
'@lingui/macro':
specifier: 'catalog:'
- version: 5.9.4(@lingui/babel-plugin-lingui-macro@5.9.4(typescript@5.9.3))(react@19.2.4)
+ version: 5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(typescript@5.9.3))(react@19.2.4)
'@tailwindcss/cli':
specifier: ^4.1.10
version: 4.1.18
@@ -848,6 +891,43 @@ importers:
specifier: 'catalog:'
version: 4.0.18(@types/node@24.10.13)(@vitest/browser-playwright@4.0.18)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)
+ packages/auth-atproto:
+ dependencies:
+ '@atcute/identity-resolver':
+ specifier: ^1.2.2
+ version: 1.2.2(@atcute/identity@1.1.4)
+ '@atcute/oauth-node-client':
+ specifier: ^1.1.0
+ version: 1.1.0
+ '@emdash-cms/auth':
+ specifier: workspace:*
+ version: link:../auth
+ astro:
+ specifier: '>=5'
+ version: 6.1.3(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.55.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
+ emdash:
+ specifier: workspace:*
+ version: link:../core
+ kysely:
+ specifier: ^0.27.6
+ version: 0.27.6
+ react:
+ specifier: '>=18'
+ version: 19.2.4
+ devDependencies:
+ '@atcute/lexicons':
+ specifier: ^1.2.10
+ version: 1.2.10
+ '@cloudflare/kumo':
+ specifier: ^1.16.0
+ version: 1.16.0(@phosphor-icons/react@2.1.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/react@19.2.14)(echarts@6.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6)
+ '@types/react':
+ specifier: ^19.0.0
+ version: 19.2.14
+ vitest:
+ specifier: 'catalog:'
+ version: 4.0.18(@types/node@24.10.13)(@vitest/browser-playwright@4.0.18)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)
+
packages/blocks:
dependencies:
'@cloudflare/kumo':
@@ -997,6 +1077,9 @@ importers:
'@emdash-cms/auth':
specifier: workspace:*
version: link:../auth
+ '@emdash-cms/auth-atproto':
+ specifier: workspace:*
+ version: link:../auth-atproto
'@emdash-cms/gutenberg-to-portable-text':
specifier: workspace:*
version: link:../gutenberg-to-portable-text
@@ -1056,10 +1139,10 @@ importers:
version: 3.7.0
astro:
specifier: '>=6.0.0-beta.0'
- version: 6.1.3(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.55.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
+ version: 6.0.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.55.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
astro-portabletext:
specifier: ^0.11.0
- version: 0.11.4(astro@6.1.3(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.55.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))
+ version: 0.11.4(astro@6.0.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.55.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))
better-sqlite3:
specifier: 'catalog:'
version: 12.8.0
@@ -1133,6 +1216,9 @@ importers:
'@types/pg':
specifier: ^8.16.0
version: 8.16.0
+ '@types/react':
+ specifier: 'catalog:'
+ version: 19.2.14
'@types/sanitize-html':
specifier: ^2.16.0
version: 2.16.0
@@ -1295,9 +1381,6 @@ importers:
tsdown:
specifier: 'catalog:'
version: 0.20.3(@arethetypeswrong/core@0.18.2)(@typescript/native-preview@7.0.0-dev.20260213.1)(oxc-resolver@11.16.4)(publint@0.3.17)(typescript@5.9.3)
- typescript:
- specifier: 'catalog:'
- version: 5.9.3
vitest:
specifier: 'catalog:'
version: 4.0.18(@types/node@24.10.13)(@vitest/browser-playwright@4.0.18)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)
@@ -1913,6 +1996,44 @@ packages:
'@astrojs/yaml2ts@0.2.2':
resolution: {integrity: sha512-GOfvSr5Nqy2z5XiwqTouBBpy5FyI6DEe+/g/Mk5am9SjILN1S5fOEvYK0GuWHg98yS/dobP4m8qyqw/URW35fQ==}
+ '@atcute/client@4.2.1':
+ resolution: {integrity: sha512-ZBFM2pW075JtgGFu5g7HHZBecrClhlcNH8GVP9Zz1aViWR+cjjBsTpeE63rJs+FCOHFYlirUyo5L8SGZ4kMINw==}
+
+ '@atcute/identity-resolver@1.2.2':
+ resolution: {integrity: sha512-eUh/UH4bFvuXS0X7epYCeJC/kj4rbBXfSRumLEH4smMVwNOgTo7cL/0Srty+P/qVPoZEyXdfEbS0PHJyzoXmHw==}
+ peerDependencies:
+ '@atcute/identity': ^1.0.0
+
+ '@atcute/identity@1.1.4':
+ resolution: {integrity: sha512-RCw1IqflfuSYCxK5m0lZCm0UnvIzcUnuhngiBhJEJb9a9Mc2SEf1xP3H8N5r8pvEH1LoAYd6/zrvCNU+uy9esw==}
+
+ '@atcute/lexicons@1.2.10':
+ resolution: {integrity: sha512-0EfRDQQjOgb06VSFOUWXLnqKY11ljWB2bXS3cJVPYJp0jTWudgRp6OTW4vReNAeVZaY4kVr2ud/I/Zn9mjix3g==}
+
+ '@atcute/multibase@1.2.0':
+ resolution: {integrity: sha512-ZK2GRra+qIYq9nNuQB52m2ul0hOmCQEtPobGfTSUxm7pF0OGEkWGkWHugFhNEDVzHzTwPxHp6VGotdZFue4lYQ==}
+
+ '@atcute/oauth-crypto@0.1.0':
+ resolution: {integrity: sha512-qZYDCNLF/4B6AndYT1rsQelN8621AC5u/sL5PHvlr/qqAbmmUwCBGjEgRSyZtHE1AqD60VNiSMlOgAuEQTSl3w==}
+
+ '@atcute/oauth-keyset@0.1.0':
+ resolution: {integrity: sha512-+wqT/+I5Lg9VzKnKY3g88+N45xbq+wsdT6bHDGqCVa2u57gRvolFF4dY+weMfc/OX641BIZO6/o+zFtKBsMQnQ==}
+
+ '@atcute/oauth-node-client@1.1.0':
+ resolution: {integrity: sha512-xCp/VfjtvTeKscKR/oI2hdMTp1/DaF/7ll8b6yZOCgbKlVDDfhCn5mmKNVARGTNaoywxrXG3XffbWCIx3/E87w==}
+
+ '@atcute/oauth-types@0.1.1':
+ resolution: {integrity: sha512-u+3KMjse3Uc/9hDyilu1QVN7IpcnjVXgRzhddzBB8Uh6wePHNVBDdi9wQvFTVVA3zmxtMJVptXRyLLg6Ou9bqg==}
+
+ '@atcute/uint8array@1.1.1':
+ resolution: {integrity: sha512-3LsC8XB8TKe9q/5hOA5sFuzGaIFdJZJNewC5OKa3o/eU6+K7JR6see9Zy2JbQERNVnRl11EzbNov1efgLMAs4g==}
+
+ '@atcute/util-fetch@1.0.5':
+ resolution: {integrity: sha512-qjHj01BGxjSjIFdPiAjSARnodJIIyKxnCMMEcXMESo9TAyND6XZQqrie5fia+LlYWVXdpsTds8uFQwc9jdKTig==}
+
+ '@atcute/util-text@1.2.0':
+ resolution: {integrity: sha512-b8WSh+Z7K601eUFFmTFj8QPKDO8Ic0VDDj63sdKzpkm+ySQKsYT5nXekViGqFVKbyKj1V5FyvZvgXad6/aI4QQ==}
+
'@atproto/api@0.13.35':
resolution: {integrity: sha512-vsEfBj0C333TLjDppvTdTE0IdKlXuljKSveAeI4PPx/l6eUKNnDTsYxvILtXUVzwUlTDmSRqy5O4Ryh78n1b7g==}
@@ -2050,6 +2171,10 @@ packages:
resolution: {integrity: sha512-ubmJ6TShyaD69VE9DQrlXcdkvJbmwWPB8qYj0H2kaJi29O7vJT9ajSdBd2W8CG34pwL9pYA74fi7RHC1qbLoVQ==}
engines: {node: ^20.19.0 || >=22.12.0}
+ '@badrap/valita@0.4.6':
+ resolution: {integrity: sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg==}
+ engines: {node: '>= 18'}
+
'@base-ui/react@1.2.0':
resolution: {integrity: sha512-O6aEQHcm+QyGTFY28xuwRD3SEJGZOBDpyjN2WvpfWYFVhg+3zfXPysAILqtM0C1kWC82MccOE/v1j+GHXE4qIw==}
engines: {node: '>=14.0.0'}
@@ -3000,12 +3125,12 @@ packages:
cpu: [x64]
os: [win32]
- '@lingui/babel-plugin-extract-messages@5.9.4':
- resolution: {integrity: sha512-sFH5lufIBCOLwjM2hyByMIi7gaGjAPhU7md8XMQYgcEjUVtzjBQvZ9APGDdDQ5BB8xRDyqF2kvaJpJvWZu19zA==}
+ '@lingui/babel-plugin-extract-messages@5.9.5':
+ resolution: {integrity: sha512-XOAXMPOkpy45784q5bCNN5PizoAecxkBm8kv8CEusI/f9kR3vMCcpH4kvSchU05JkKAVE8eIsdxb2zM6eDJTeA==}
engines: {node: '>=20.0.0'}
- '@lingui/babel-plugin-lingui-macro@5.9.4':
- resolution: {integrity: sha512-Gj+H48MQWY6rV40TBVG7U91/KETznbXOJpJsf8U4merBRPZgOMCy6VuWZGy1i+YJZJF/LiberlsCCEiiPbBRqg==}
+ '@lingui/babel-plugin-lingui-macro@5.9.5':
+ resolution: {integrity: sha512-TDIrOa2hAz8kXrZ0JfMGaIiFIE4TEdqI2he4OpkTSCfBh3ec/gSCn1kNW5HdviO7x46Gvy567YOgHNOI9/e4Fg==}
engines: {node: '>=20.0.0'}
peerDependencies:
babel-plugin-macros: 2 || 3
@@ -3013,20 +3138,20 @@ packages:
babel-plugin-macros:
optional: true
- '@lingui/cli@5.9.4':
- resolution: {integrity: sha512-0QAsZCWu6PZhxYmeQfoa6cJbNRRsTkeNQ1jTow/GzBYpFlO9iXw8dCG5cBh5pHHjzjoX3koxiKyUTFyLBmKNiQ==}
+ '@lingui/cli@5.9.5':
+ resolution: {integrity: sha512-gonY7U75nzKic8GvEciy1/otQv1WpfwGW5wGMjmBXUMaMnIsycm/wo3t0+2hzqFp+RNfEKZcScoM7aViK3XuLQ==}
engines: {node: '>=20.0.0'}
hasBin: true
- '@lingui/conf@5.9.4':
- resolution: {integrity: sha512-crF3AQgYXg52Caz4ffJKSTXWUU/4iOGOBRnSeOkw8lsOtOYlPTaWxeSGyDTEwaGCFl6P/1aew+pvHOSCxOAyrg==}
+ '@lingui/conf@5.9.5':
+ resolution: {integrity: sha512-k5r9ssOZirhS5BlqdsK5L0rzlqnHeryoJHAQIpUpeh8g5ymgpbUN7L4+4C4hAX/tddAFiCFN8boHTiu6Wbt83Q==}
engines: {node: '>=20.0.0'}
- '@lingui/core@5.9.4':
- resolution: {integrity: sha512-MsYYc8ue/w1C8bgAbC3h4cNik64bqZ6xGxMjsVdoGQBUe+b/ij+rOEiuJXbwvlo4GXBsvsan7EzeH7sx11IsYQ==}
+ '@lingui/core@5.9.5':
+ resolution: {integrity: sha512-Y+iZq9NqnqZOqHNgPomUFP21KH/zs4oTTizWoz0AKAkBbq9T9yb1DSz/ugtBRjF1YLtKMF9tq28v3thMHANSiQ==}
engines: {node: '>=20.0.0'}
peerDependencies:
- '@lingui/babel-plugin-lingui-macro': 5.9.4
+ '@lingui/babel-plugin-lingui-macro': 5.9.5
babel-plugin-macros: 2 || 3
peerDependenciesMeta:
'@lingui/babel-plugin-lingui-macro':
@@ -3034,15 +3159,15 @@ packages:
babel-plugin-macros:
optional: true
- '@lingui/format-po@5.9.4':
- resolution: {integrity: sha512-B+e8YF6S5EOUPF6i3gaSX69pPs/QkP6MIE97vYA48W9Lty7KFOHuYBk/YzCY9CSQaF7gW3GAI5ZsXX2+ZLVyZw==}
+ '@lingui/format-po@5.9.5':
+ resolution: {integrity: sha512-abawxkaEMhAUCqxrnim2NTTeu2gd55X9tkFN8jfRM0B1LE2KjZLWCA8gSD90J/DblDwej8jK8A2BynXlcQdluQ==}
engines: {node: '>=20.0.0'}
- '@lingui/macro@5.9.4':
- resolution: {integrity: sha512-p1/uPc8sQTMLdv0EJqjaFUvuFKBiwNVThdJp80GX7FayPzF570EO6wsS8U81g1p8NoCS/UY6cglK9YJeNVwKLw==}
+ '@lingui/macro@5.9.5':
+ resolution: {integrity: sha512-WZsF93jKwk0IW4xmvAGd+6S3dT+9ZzhXAasKIiJL9qB4viO9oj+oecGhXuPYTYV74mcoL7L1494hca0CsB/BLQ==}
engines: {node: '>=20.0.0'}
peerDependencies:
- '@lingui/babel-plugin-lingui-macro': 5.9.4
+ '@lingui/babel-plugin-lingui-macro': 5.9.5
babel-plugin-macros: 2 || 3
peerDependenciesMeta:
'@lingui/babel-plugin-lingui-macro':
@@ -3050,15 +3175,15 @@ packages:
babel-plugin-macros:
optional: true
- '@lingui/message-utils@5.9.4':
- resolution: {integrity: sha512-YzAVzILdsqdUqwHmryl7rfwZXRHYs6QY2wPLH5gxrV7wlieiCaskaKPeSk2SuN/gmC8im1GDrQHcwgKapFU3Sg==}
+ '@lingui/message-utils@5.9.5':
+ resolution: {integrity: sha512-t3dNbjb1dWkvcpXGMXIEyBDO3l4B8J2ColZXi0NTG1ioAj+sDfFxFB8fepVgd3JAk+AwARlOLvF14oS0mAdgpw==}
engines: {node: '>=20.0.0'}
- '@lingui/react@5.9.4':
- resolution: {integrity: sha512-ev/PvJd0WNy6OqeyghQV1QCGAFYku5xHyaattN2kg0wy6RPVfGsCaM8treRUK9TLiETra79GLVY8sTjfeH/M5Q==}
+ '@lingui/react@5.9.5':
+ resolution: {integrity: sha512-jzYoA/f4jrTfpOB+jrMhlC835UwqSXJdepr7cfWsmg+Rpp3HBSREtfrogaz1LqLI/AVnkmfp10Mo6VOp/8qeOQ==}
engines: {node: '>=20.0.0'}
peerDependencies:
- '@lingui/babel-plugin-lingui-macro': 5.9.4
+ '@lingui/babel-plugin-lingui-macro': 5.9.5
babel-plugin-macros: 2 || 3
react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
@@ -3071,7 +3196,7 @@ packages:
resolution: {integrity: sha512-yTCCjuQapvRz6S30B8DyqHu1WYsbYRCww6uNsmbQU4GQVf5gJzJSB60qUHj+qBSxReLtRL/mhmhYhrIc9jVFTw==}
'@lunariajs/core@https://pkg.pr.new/lunariajs/lunaria/@lunariajs/core@83617cc':
- resolution: {tarball: https://pkg.pr.new/lunariajs/lunaria/@lunariajs/core@83617cc}
+ resolution: {integrity: sha512-k8sHBM7S10HBa39fxsJcOGYMGrbru5UZ9vMS4kmCa9o6dJTUP6rt3zKVEs7uEsHAYasoXyiC6wre2Jiqs3X+zQ==, tarball: https://pkg.pr.new/lunariajs/lunaria/@lunariajs/core@83617cc}
version: 0.1.1
engines: {node: '>=18.17.0'}
@@ -5960,6 +6085,9 @@ packages:
eslint-plugin-depend@1.4.0:
resolution: {integrity: sha512-MQs+m4nHSfgAO9bJDsBzqw0ofK/AOA0vfeY/6ahofqcUMLeM6/D1sTYs21fOhc17kNU/gn58YCtj20XaAssh2A==}
+ esm-env@1.2.2:
+ resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==}
+
esm@3.2.25:
resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==}
engines: {node: '>=6'}
@@ -7159,6 +7287,11 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
+ nanoid@5.1.7:
+ resolution: {integrity: sha512-ua3NDgISf6jdwezAheMOk4mbE1LXjm1DfMUDMuJf4AqxLFK3ccGpgWizwa5YV7Yz9EpXwEaWoRXSb/BnV0t5dQ==}
+ engines: {node: ^18 || >=20}
+ hasBin: true
+
napi-build-utils@2.0.0:
resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==}
@@ -9555,6 +9688,75 @@ snapshots:
dependencies:
yaml: 2.8.2
+ '@atcute/client@4.2.1':
+ dependencies:
+ '@atcute/identity': 1.1.4
+ '@atcute/lexicons': 1.2.10
+
+ '@atcute/identity-resolver@1.2.2(@atcute/identity@1.1.4)':
+ dependencies:
+ '@atcute/identity': 1.1.4
+ '@atcute/lexicons': 1.2.10
+ '@atcute/util-fetch': 1.0.5
+ '@badrap/valita': 0.4.6
+
+ '@atcute/identity@1.1.4':
+ dependencies:
+ '@atcute/lexicons': 1.2.10
+ '@badrap/valita': 0.4.6
+
+ '@atcute/lexicons@1.2.10':
+ dependencies:
+ '@atcute/uint8array': 1.1.1
+ '@atcute/util-text': 1.2.0
+ '@standard-schema/spec': 1.1.0
+ esm-env: 1.2.2
+
+ '@atcute/multibase@1.2.0':
+ dependencies:
+ '@atcute/uint8array': 1.1.1
+
+ '@atcute/oauth-crypto@0.1.0':
+ dependencies:
+ '@atcute/multibase': 1.2.0
+ '@atcute/uint8array': 1.1.1
+ '@badrap/valita': 0.4.6
+ nanoid: 5.1.7
+
+ '@atcute/oauth-keyset@0.1.0':
+ dependencies:
+ '@atcute/oauth-crypto': 0.1.0
+
+ '@atcute/oauth-node-client@1.1.0':
+ dependencies:
+ '@atcute/client': 4.2.1
+ '@atcute/identity': 1.1.4
+ '@atcute/identity-resolver': 1.2.2(@atcute/identity@1.1.4)
+ '@atcute/lexicons': 1.2.10
+ '@atcute/oauth-crypto': 0.1.0
+ '@atcute/oauth-keyset': 0.1.0
+ '@atcute/oauth-types': 0.1.1
+ '@atcute/util-fetch': 1.0.5
+ '@badrap/valita': 0.4.6
+ nanoid: 5.1.7
+
+ '@atcute/oauth-types@0.1.1':
+ dependencies:
+ '@atcute/identity': 1.1.4
+ '@atcute/lexicons': 1.2.10
+ '@atcute/oauth-keyset': 0.1.0
+ '@badrap/valita': 0.4.6
+
+ '@atcute/uint8array@1.1.1': {}
+
+ '@atcute/util-fetch@1.0.5':
+ dependencies:
+ '@badrap/valita': 0.4.6
+
+ '@atcute/util-text@1.2.0':
+ dependencies:
+ unicode-segmenter: 0.14.5
+
'@atproto/api@0.13.35':
dependencies:
'@atproto/common-web': 0.4.12
@@ -9743,6 +9945,8 @@ snapshots:
'@babel/helper-string-parser': 8.0.0-rc.2
'@babel/helper-validator-identifier': 8.0.0-rc.1
+ '@badrap/valita@0.4.6': {}
+
'@base-ui/react@1.2.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
'@babel/runtime': 7.28.6
@@ -10665,33 +10869,33 @@ snapshots:
'@libsql/win32-x64-msvc@0.3.19':
optional: true
- '@lingui/babel-plugin-extract-messages@5.9.4': {}
+ '@lingui/babel-plugin-extract-messages@5.9.5': {}
- '@lingui/babel-plugin-lingui-macro@5.9.4(typescript@5.9.3)':
+ '@lingui/babel-plugin-lingui-macro@5.9.5(typescript@5.9.3)':
dependencies:
'@babel/core': 7.29.0
'@babel/runtime': 7.28.6
'@babel/types': 7.29.0
- '@lingui/conf': 5.9.4(typescript@5.9.3)
- '@lingui/core': 5.9.4(@lingui/babel-plugin-lingui-macro@5.9.4(typescript@5.9.3))
- '@lingui/message-utils': 5.9.4
+ '@lingui/conf': 5.9.5(typescript@5.9.3)
+ '@lingui/core': 5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(typescript@5.9.3))
+ '@lingui/message-utils': 5.9.5
transitivePeerDependencies:
- supports-color
- typescript
- '@lingui/cli@5.9.4(typescript@5.9.3)':
+ '@lingui/cli@5.9.5(typescript@5.9.3)':
dependencies:
'@babel/core': 7.29.0
'@babel/generator': 7.29.1
'@babel/parser': 7.29.0
'@babel/runtime': 7.28.6
'@babel/types': 7.29.0
- '@lingui/babel-plugin-extract-messages': 5.9.4
- '@lingui/babel-plugin-lingui-macro': 5.9.4(typescript@5.9.3)
- '@lingui/conf': 5.9.4(typescript@5.9.3)
- '@lingui/core': 5.9.4(@lingui/babel-plugin-lingui-macro@5.9.4(typescript@5.9.3))
- '@lingui/format-po': 5.9.4(typescript@5.9.3)
- '@lingui/message-utils': 5.9.4
+ '@lingui/babel-plugin-extract-messages': 5.9.5
+ '@lingui/babel-plugin-lingui-macro': 5.9.5(typescript@5.9.3)
+ '@lingui/conf': 5.9.5(typescript@5.9.3)
+ '@lingui/core': 5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(typescript@5.9.3))
+ '@lingui/format-po': 5.9.5(typescript@5.9.3)
+ '@lingui/message-utils': 5.9.5
chokidar: 3.5.1
cli-table: 0.3.11
commander: 10.0.1
@@ -10713,7 +10917,7 @@ snapshots:
- supports-color
- typescript
- '@lingui/conf@5.9.4(typescript@5.9.3)':
+ '@lingui/conf@5.9.5(typescript@5.9.3)':
dependencies:
'@babel/runtime': 7.28.6
cosmiconfig: 8.3.6(typescript@5.9.3)
@@ -10723,43 +10927,43 @@ snapshots:
transitivePeerDependencies:
- typescript
- '@lingui/core@5.9.4(@lingui/babel-plugin-lingui-macro@5.9.4(typescript@5.9.3))':
+ '@lingui/core@5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(typescript@5.9.3))':
dependencies:
'@babel/runtime': 7.28.6
- '@lingui/message-utils': 5.9.4
+ '@lingui/message-utils': 5.9.5
optionalDependencies:
- '@lingui/babel-plugin-lingui-macro': 5.9.4(typescript@5.9.3)
+ '@lingui/babel-plugin-lingui-macro': 5.9.5(typescript@5.9.3)
- '@lingui/format-po@5.9.4(typescript@5.9.3)':
+ '@lingui/format-po@5.9.5(typescript@5.9.3)':
dependencies:
- '@lingui/conf': 5.9.4(typescript@5.9.3)
- '@lingui/message-utils': 5.9.4
+ '@lingui/conf': 5.9.5(typescript@5.9.3)
+ '@lingui/message-utils': 5.9.5
date-fns: 3.6.0
pofile: 1.1.4
transitivePeerDependencies:
- typescript
- '@lingui/macro@5.9.4(@lingui/babel-plugin-lingui-macro@5.9.4(typescript@5.9.3))(react@19.2.4)':
+ '@lingui/macro@5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(typescript@5.9.3))(react@19.2.4)':
dependencies:
- '@lingui/core': 5.9.4(@lingui/babel-plugin-lingui-macro@5.9.4(typescript@5.9.3))
- '@lingui/react': 5.9.4(@lingui/babel-plugin-lingui-macro@5.9.4(typescript@5.9.3))(react@19.2.4)
+ '@lingui/core': 5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(typescript@5.9.3))
+ '@lingui/react': 5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(typescript@5.9.3))(react@19.2.4)
optionalDependencies:
- '@lingui/babel-plugin-lingui-macro': 5.9.4(typescript@5.9.3)
+ '@lingui/babel-plugin-lingui-macro': 5.9.5(typescript@5.9.3)
transitivePeerDependencies:
- react
- '@lingui/message-utils@5.9.4':
+ '@lingui/message-utils@5.9.5':
dependencies:
'@messageformat/parser': 5.1.1
js-sha256: 0.10.1
- '@lingui/react@5.9.4(@lingui/babel-plugin-lingui-macro@5.9.4(typescript@5.9.3))(react@19.2.4)':
+ '@lingui/react@5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(typescript@5.9.3))(react@19.2.4)':
dependencies:
'@babel/runtime': 7.28.6
- '@lingui/core': 5.9.4(@lingui/babel-plugin-lingui-macro@5.9.4(typescript@5.9.3))
+ '@lingui/core': 5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(typescript@5.9.3))
react: 19.2.4
optionalDependencies:
- '@lingui/babel-plugin-lingui-macro': 5.9.4(typescript@5.9.3)
+ '@lingui/babel-plugin-lingui-macro': 5.9.5(typescript@5.9.3)
'@loaderkit/resolve@1.0.2':
dependencies:
@@ -13014,11 +13218,11 @@ snapshots:
astro: 6.1.3(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.55.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
rehype-expressive-code: 0.41.6
- astro-portabletext@0.11.4(astro@6.1.3(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.55.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)):
+ astro-portabletext@0.11.4(astro@6.0.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.55.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)):
dependencies:
'@portabletext/toolkit': 3.0.3
'@portabletext/types': 2.0.15
- astro: 6.1.3(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.55.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
+ astro: 6.0.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.55.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
astro@6.0.0-beta.20(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.55.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2):
dependencies:
@@ -14060,6 +14264,8 @@ snapshots:
module-replacements: 2.11.0
semver: 7.7.4
+ esm-env@1.2.2: {}
+
esm@3.2.25:
optional: true
@@ -15624,6 +15830,8 @@ snapshots:
nanoid@3.3.11: {}
+ nanoid@5.1.7: {}
+
napi-build-utils@2.0.0: {}
negotiator@1.0.0: {}