@byline/host-tanstack-start 4.11.2 → 4.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/admin-shell/admin-activity/list.js +2 -2
  2. package/dist/admin-shell/chrome/dashboard.js +2 -2
  3. package/dist/admin-shell/chrome/route-error.js +3 -3
  4. package/dist/admin-shell/chrome/route-progress-bar.js +1 -1
  5. package/dist/admin-shell/chrome/sign-in-page.d.ts +5 -5
  6. package/dist/admin-shell/chrome/sign-in-page.js +8 -8
  7. package/dist/i18n/locale-cookie.d.ts +1 -1
  8. package/dist/i18n/locale-definitions.d.ts +1 -1
  9. package/dist/i18n/resolve-locale.js +5 -5
  10. package/dist/i18n/server-translator.js +2 -2
  11. package/dist/integrations/byline-admin-services.js +2 -2
  12. package/dist/routes/admin-path.js +2 -2
  13. package/dist/routes/create-admin-dashboard-route.js +2 -2
  14. package/dist/routes/create-admin-layout-route.d.ts +1 -6
  15. package/dist/routes/create-admin-layout-route.js +9 -10
  16. package/dist/routes/create-sign-in-route.d.ts +9 -1
  17. package/dist/routes/create-sign-in-route.js +5 -2
  18. package/dist/routes/get-content-locale-route-config.js +2 -2
  19. package/dist/routes/index.d.ts +2 -2
  20. package/dist/routes/index.js +1 -1
  21. package/dist/routes/sign-in-home-url.d.ts +9 -0
  22. package/dist/routes/sign-in-home-url.js +4 -0
  23. package/dist/routes/sign-in-path.d.ts +0 -4
  24. package/dist/routes/sign-in-path.js +3 -17
  25. package/dist/server-fns/auth/sign-in.js +1 -1
  26. package/dist/server-fns/i18n/index.d.ts +2 -2
  27. package/dist/server-fns/i18n/index.js +1 -1
  28. package/dist/server-fns/i18n/set-locale.d.ts +3 -3
  29. package/dist/server-fns/i18n/set-locale.js +4 -4
  30. package/package.json +14 -14
  31. package/src/admin-shell/admin-activity/list.tsx +2 -2
  32. package/src/admin-shell/chrome/dashboard.tsx +2 -2
  33. package/src/admin-shell/chrome/route-error.tsx +4 -4
  34. package/src/admin-shell/chrome/route-progress-bar.tsx +7 -1
  35. package/src/admin-shell/chrome/sign-in-page.tsx +12 -15
  36. package/src/i18n/locale-cookie.ts +1 -1
  37. package/src/i18n/locale-definitions.ts +1 -1
  38. package/src/i18n/resolve-locale.ts +6 -6
  39. package/src/i18n/server-translator.ts +2 -2
  40. package/src/integrations/byline-admin-services.ts +2 -2
  41. package/src/routes/admin-path.test.node.ts +3 -4
  42. package/src/routes/admin-path.ts +2 -2
  43. package/src/routes/auth-routes.test.node.ts +4 -10
  44. package/src/routes/create-admin-dashboard-route.tsx +2 -2
  45. package/src/routes/create-admin-layout-route.tsx +11 -17
  46. package/src/routes/create-sign-in-route.tsx +13 -2
  47. package/src/routes/get-content-locale-route-config.ts +2 -2
  48. package/src/routes/index.ts +5 -2
  49. package/src/routes/sign-in-home-url.test.node.ts +21 -0
  50. package/src/routes/sign-in-home-url.ts +12 -0
  51. package/src/routes/sign-in-path.test.node.ts +4 -61
  52. package/src/routes/sign-in-path.ts +2 -24
  53. package/src/server-fns/auth/sign-in.ts +2 -2
  54. package/src/server-fns/i18n/index.ts +2 -2
  55. package/src/server-fns/i18n/set-locale.ts +8 -8
@@ -38,8 +38,14 @@ const FINISH_EXIT_MS = 350
38
38
  type Phase = 'idle' | 'visible' | 'finishing'
39
39
 
40
40
  export function RouteProgressBar() {
41
+ // `isLoading` is the whole navigation-in-flight signal: router-core derives
42
+ // it as `status === 'pending'`, set when a navigation starts and cleared in
43
+ // the same batch that commits the new matches. TanStack Router 1.170.30
44
+ // (router-core 1.171.25) removed `isTransitioning` from `RouterState` —
45
+ // Transitioner now uses the standalone `React.startTransition`, which has no
46
+ // pending flag — so there is nothing further to OR in here.
41
47
  const isNavigating = useRouterState({
42
- select: (s) => s.isLoading || s.isTransitioning,
48
+ select: (s) => s.isLoading,
43
49
  })
44
50
  const [phase, setPhase] = useState<Phase>('idle')
45
51
  const showTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
@@ -8,19 +8,20 @@
8
8
 
9
9
  import { SignInForm } from '@byline/admin/auth/components/sign-in-form'
10
10
  import { BylineAdminServicesProvider } from '@byline/admin/services'
11
- import { getClientConfig } from '@byline/core'
11
+ import { getAdminConfig } from '@byline/core'
12
12
  import type { LocaleCode } from '@byline/i18n'
13
13
  import { I18nProvider, LanguageMenu } from '@byline/i18n/react'
14
14
  import cx from 'clsx'
15
15
 
16
16
  import { buildLocaleDefinitions } from '../../i18n/locale-definitions.js'
17
17
  import { bylineAdminServices } from '../../integrations/byline-admin-services.js'
18
- import { setInterfaceLocaleFn } from '../../server-fns/i18n/index.js'
18
+ import { setAdminLocaleFn } from '../../server-fns/i18n/index.js'
19
19
  import styles from './sign-in-page.module.css'
20
20
 
21
21
  interface SignInPageProps {
22
22
  redirectTo: string
23
23
  activeLocale: LocaleCode
24
+ homeUrl?: string
24
25
  }
25
26
 
26
27
  /**
@@ -33,32 +34,28 @@ interface SignInPageProps {
33
34
  * (the one that wraps the authenticated admin) doesn't apply here.
34
35
  * `<LanguageMenu>` lights up automatically when two or more interface
35
36
  * locales are configured. On change, the menu calls
36
- * `setInterfaceLocaleFn` which writes the cookie unconditionally and
37
+ * `setAdminLocaleFn` which writes the cookie unconditionally and
37
38
  * skips the DB write on the pre-auth path (the user has no admin
38
39
  * session yet). After sign-in succeeds, the `adminSignIn` server fn
39
40
  * reconciles the cookie locale into the user's
40
41
  * `admin_users.preferred_locale` so the pre-auth choice becomes
41
42
  * sticky across devices from day one.
42
43
  *
43
- * Threads the configured `serverURL` into the `SignInForm` as `homeUrl` so
44
- * the form's action row can render a plain "Home" link beside the submit
45
- * button.
44
+ * Threads an optional host-owned `homeUrl` into `SignInForm` so the form's
45
+ * action row can render a plain "Home" link beside the submit button.
46
46
  */
47
- export function SignInPage({ redirectTo, activeLocale }: SignInPageProps) {
48
- const { i18n, serverURL } = getClientConfig()
49
- const localeDefinitions = buildLocaleDefinitions(
50
- i18n.interface.locales,
51
- i18n.interface.localeDefinitions
52
- )
47
+ export function SignInPage({ redirectTo, activeLocale, homeUrl }: SignInPageProps) {
48
+ const { i18n } = getAdminConfig()
49
+ const localeDefinitions = buildLocaleDefinitions(i18n.admin.locales, i18n.admin.localeDefinitions)
53
50
  const handleSetLocale = async (next: LocaleCode) => {
54
- await setInterfaceLocaleFn({ data: { locale: next } })
51
+ await setAdminLocaleFn({ data: { locale: next } })
55
52
  window.location.reload()
56
53
  }
57
54
  return (
58
55
  <I18nProvider
59
56
  bundle={i18n.translations ?? {}}
60
57
  activeLocale={activeLocale}
61
- defaultLocale={i18n.interface.defaultLocale}
58
+ defaultLocale={i18n.admin.defaultLocale}
62
59
  localeDefinitions={localeDefinitions}
63
60
  setLocale={handleSetLocale}
64
61
  >
@@ -68,7 +65,7 @@ export function SignInPage({ redirectTo, activeLocale }: SignInPageProps) {
68
65
  <LanguageMenu />
69
66
  </div>
70
67
  <div className={cx('byline-sign-in-page-inner', styles.inner)}>
71
- <SignInForm redirectTo={redirectTo} homeUrl={serverURL} />
68
+ <SignInForm redirectTo={redirectTo} homeUrl={homeUrl} />
72
69
  </div>
73
70
  </main>
74
71
  </BylineAdminServicesProvider>
@@ -44,7 +44,7 @@ export function readAdminLocaleCookie(): string | null {
44
44
 
45
45
  /**
46
46
  * Write the admin locale cookie. Caller is expected to have validated
47
- * `locale` against the permitted `i18n.interface.locales` set first.
47
+ * `locale` against the permitted `i18n.admin.locales` set first.
48
48
  */
49
49
  export function setAdminLocaleCookie(locale: string): void {
50
50
  setCookie(ADMIN_LOCALE_COOKIE, locale, {
@@ -13,7 +13,7 @@ import type { LocaleDefinition } from '@byline/i18n'
13
13
  * resolution order:
14
14
  *
15
15
  * 1. An entry from the host's configured `localeDefinitions` —
16
- * `i18n.interface.localeDefinitions` for the admin switcher, or
16
+ * `i18n.admin.localeDefinitions` for the admin switcher, or
17
17
  * `i18n.content.localeDefinitions` when a host frontend resolves
18
18
  * content-locale labels (matched by code). Wins outright — this is
19
19
  * the path that lets a host author write `Français` instead of the
@@ -9,7 +9,7 @@
9
9
  /**
10
10
  * Server-side locale resolution for the admin interface.
11
11
  *
12
- * Combines `@byline/i18n`'s pure `resolveInterfaceLocale` cascade with
12
+ * Combines `@byline/i18n`'s pure `resolveAdminLocale` cascade with
13
13
  * the host's request-scoped signals — the `byline_admin_lng` cookie, the
14
14
  * `Accept-Language` header, and (when an admin session is present)
15
15
  * `admin_users.preferred_locale` on the authenticated actor.
@@ -25,7 +25,7 @@ import { getRequestHeader } from '@tanstack/react-start/server'
25
25
  import { AuthError, AuthErrorCodes } from '@byline/auth'
26
26
  import { getAdminRequestContext } from '@byline/client/server'
27
27
  import type { LocaleCode } from '@byline/i18n'
28
- import { resolveInterfaceLocale } from '@byline/i18n'
28
+ import { resolveAdminLocale } from '@byline/i18n'
29
29
 
30
30
  import { bylineCore } from '../integrations/byline-core.js'
31
31
  import { readAdminLocaleCookie } from './locale-cookie.js'
@@ -43,16 +43,16 @@ export async function resolveRequestLocale(options?: {
43
43
  skipActorLookup?: boolean
44
44
  }): Promise<LocaleCode> {
45
45
  const core = bylineCore()
46
- const { interface: ifaceConfig } = core.config.i18n
46
+ const { admin: adminConfig } = core.config.i18n
47
47
 
48
48
  let preferred: string | null = null
49
49
  if (!options?.skipActorLookup) {
50
50
  preferred = await readPreferredLocaleFromActor()
51
51
  }
52
52
 
53
- return resolveInterfaceLocale({
54
- locales: ifaceConfig.locales,
55
- defaultLocale: ifaceConfig.defaultLocale,
53
+ return resolveAdminLocale({
54
+ locales: adminConfig.locales,
55
+ defaultLocale: adminConfig.defaultLocale,
56
56
  preferred,
57
57
  cookie: readAdminLocaleCookie(),
58
58
  acceptLanguage: readAcceptLanguage(),
@@ -39,7 +39,7 @@ export interface ServerTranslator {
39
39
 
40
40
  export async function resolveServerTranslator(namespace: string): Promise<ServerTranslator> {
41
41
  const core = bylineCore()
42
- const { interface: ifaceConfig, translations: bundle } = core.config.i18n
42
+ const { admin: adminConfig, translations: bundle } = core.config.i18n
43
43
  if (bundle == null) {
44
44
  throw new Error(
45
45
  '[resolveServerTranslator] no translation bundle is registered. ' +
@@ -51,7 +51,7 @@ export async function resolveServerTranslator(namespace: string): Promise<Server
51
51
  const formatter = createFormatter({
52
52
  bundle,
53
53
  activeLocale,
54
- defaultLocale: ifaceConfig.defaultLocale,
54
+ defaultLocale: adminConfig.defaultLocale,
55
55
  })
56
56
  return {
57
57
  t: (key, values) => formatter.t(namespace, key, values),
@@ -35,7 +35,7 @@ import {
35
35
  } from '../server-fns/admin-users/index.js'
36
36
  import { adminSignIn } from '../server-fns/auth/index.js'
37
37
  import { getCollectionDocumentVersion as serverGetCollectionDocumentVersion } from '../server-fns/collections/get.js'
38
- import { setInterfaceLocaleFn } from '../server-fns/i18n/index.js'
38
+ import { setAdminLocaleFn } from '../server-fns/i18n/index.js'
39
39
 
40
40
  /**
41
41
  * Diff helper adapter — the contract uses positional args; the underlying
@@ -67,7 +67,7 @@ export const bylineAdminServices: BylineAdminServices = {
67
67
  // Account self-service
68
68
  updateAccount,
69
69
  changeAccountPassword,
70
- setInterfaceLocale: setInterfaceLocaleFn,
70
+ setAdminLocale: setAdminLocaleFn,
71
71
 
72
72
  // Admin user writes
73
73
  createAdminUser,
@@ -1,4 +1,4 @@
1
- import { defineClientConfig } from '@byline/core'
1
+ import { defineAdminConfig } from '@byline/core'
2
2
  import { beforeEach, describe, expect, it } from 'vitest'
3
3
 
4
4
  import {
@@ -11,13 +11,12 @@ import {
11
11
  } from './admin-path.js'
12
12
 
13
13
  function registerRoutes(admin?: string): void {
14
- defineClientConfig({
15
- serverURL: 'http://localhost:3000',
14
+ defineAdminConfig({
16
15
  routes: admin ? { admin } : undefined,
17
16
  collections: [],
18
17
  admin: [],
19
18
  i18n: {
20
- interface: { defaultLocale: 'en', locales: [] },
19
+ admin: { defaultLocale: 'en', locales: [] },
21
20
  content: { defaultLocale: 'en', locales: [] },
22
21
  },
23
22
  })
@@ -6,11 +6,11 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
 
9
- import { getClientConfig, normalizeRootRelativeRedirect } from '@byline/core'
9
+ import { getAdminConfig, normalizeRootRelativeRedirect } from '@byline/core'
10
10
 
11
11
  /** Build an admin URL or parameterized TanStack route from client-safe config. */
12
12
  export function getAdminRoutePath(...segments: ReadonlyArray<string | number>): string {
13
- const configuredBase = getClientConfig().routes.admin
13
+ const configuredBase = getAdminConfig().routes.admin
14
14
  const normalizedBase = configuredBase.replace(/^\/+|\/+$/g, '')
15
15
  const base = normalizedBase ? `/${normalizedBase}` : '/'
16
16
  const suffix = segments
@@ -1,18 +1,17 @@
1
- import { defineClientConfig } from '@byline/core'
1
+ import { defineAdminConfig } from '@byline/core'
2
2
  import { beforeEach, describe, expect, it } from 'vitest'
3
3
 
4
4
  import { resolveAdminCallbackPath, resolveAdminSignInRedirect } from './admin-path.js'
5
- import { configureSignInRoutePath, getSignInRoutePath } from './sign-in-path.js'
5
+ import { getSignInRoutePath } from './sign-in-path.js'
6
6
 
7
7
  describe('admin authentication routes', () => {
8
8
  beforeEach(() => {
9
- defineClientConfig({
10
- serverURL: 'http://localhost:3000',
9
+ defineAdminConfig({
11
10
  routes: { admin: '/cms/', signIn: '/staff/login/' },
12
11
  collections: [],
13
12
  admin: [],
14
13
  i18n: {
15
- interface: { defaultLocale: 'en', locales: [] },
14
+ admin: { defaultLocale: 'en', locales: [] },
16
15
  content: { defaultLocale: 'en', locales: [] },
17
16
  },
18
17
  })
@@ -30,10 +29,5 @@ describe('admin authentication routes', () => {
30
29
 
31
30
  it('shares routes.signIn between guard and sign-out consumers', () => {
32
31
  expect(getSignInRoutePath()).toBe('/staff/login')
33
- expect(configureSignInRoutePath('/staff/login/')).toBe('/staff/login')
34
- })
35
-
36
- it('does not allow the deprecated layout override to silently diverge', () => {
37
- expect(() => configureSignInRoutePath('/auth/login')).toThrow(/must match routes\.signIn/)
38
32
  })
39
33
  })
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { createFileRoute } from '@tanstack/react-router'
10
10
 
11
- import { getClientConfig } from '@byline/core'
11
+ import { getAdminConfig } from '@byline/core'
12
12
  import { useTranslation } from '@byline/i18n/react'
13
13
 
14
14
  import { BreadcrumbsClient } from '../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js'
@@ -20,7 +20,7 @@ export function createAdminDashboardRoute(path: string) {
20
20
  // biome-ignore lint/suspicious/noExplicitAny: dynamic path bypasses route-tree typing
21
21
  const Route: any = createFileRoute(path as never)({
22
22
  loader: async () => {
23
- const { collections } = getClientConfig()
23
+ const { collections } = getAdminConfig()
24
24
  const statsMap: Record<string, CollectionStatusCount[]> = {}
25
25
 
26
26
  await Promise.all(
@@ -25,7 +25,7 @@ import { createFileRoute, Outlet, redirect } from '@tanstack/react-router'
25
25
  import { applyStoredTheme } from '@byline/admin/admin-account/components/theme'
26
26
  import { BylineFieldServicesProvider } from '@byline/admin/react'
27
27
  import { BylineAdminServicesProvider } from '@byline/admin/services'
28
- import { getClientConfig } from '@byline/core'
28
+ import { getAdminConfig } from '@byline/core'
29
29
  import type { LocaleCode } from '@byline/i18n'
30
30
  import { I18nProvider } from '@byline/i18n/react'
31
31
  import cx from 'clsx'
@@ -42,19 +42,13 @@ import { bylineAdminServices } from '../integrations/byline-admin-services.js'
42
42
  import { BylineAiAdminProvider } from '../integrations/byline-ai.js'
43
43
  import { bylineFieldServices } from '../integrations/byline-field-services.js'
44
44
  import { getCurrentAdminUser } from '../server-fns/auth/index.js'
45
- import { getActiveLocaleFn, setInterfaceLocaleFn } from '../server-fns/i18n/index.js'
46
- import { createSignInRoutePathResolver } from './sign-in-path.js'
45
+ import { getActiveLocaleFn, setAdminLocaleFn } from '../server-fns/i18n/index.js'
46
+ import { getSignInRoutePath } from './sign-in-path.js'
47
47
 
48
- interface AdminLayoutOpts {
49
- /** @deprecated Configure `routes.signIn`; an override must resolve to the same path. */
50
- signInPath?: string
51
- }
52
-
53
- export function createAdminLayoutRoute(path: string, opts: AdminLayoutOpts = {}) {
54
- const resolveSignInPath = createSignInRoutePathResolver(opts.signInPath)
48
+ export function createAdminLayoutRoute(path: string) {
55
49
  const Route: any = createFileRoute(path as never)({
56
50
  beforeLoad: async ({ location }: { location: { href: string } }) => {
57
- const signInPath = resolveSignInPath()
51
+ const signInPath = getSignInRoutePath()
58
52
  try {
59
53
  const user = await getCurrentAdminUser()
60
54
  // Resolve the active interface locale once on the server so SSR
@@ -81,10 +75,10 @@ export function createAdminLayoutRoute(path: string, opts: AdminLayoutOpts = {})
81
75
  user: Awaited<ReturnType<typeof getCurrentAdminUser>>
82
76
  activeLocale: LocaleCode
83
77
  }
84
- const { i18n } = getClientConfig()
78
+ const { i18n } = getAdminConfig()
85
79
  const localeDefinitions = buildLocaleDefinitions(
86
- i18n.interface.locales,
87
- i18n.interface.localeDefinitions
80
+ i18n.admin.locales,
81
+ i18n.admin.localeDefinitions
88
82
  )
89
83
  // Re-assert the admin user's stored theme when the shell mounts.
90
84
  // Byline shares the host's theme contract, but a host theme provider
@@ -93,18 +87,18 @@ export function createAdminLayoutRoute(path: string, opts: AdminLayoutOpts = {})
93
87
  useEffect(() => {
94
88
  applyStoredTheme()
95
89
  }, [])
96
- // Cookie + DB write happen in setInterfaceLocaleFn; full reload
90
+ // Cookie + DB write happen in setAdminLocaleFn; full reload
97
91
  // re-runs beforeLoad so the provider re-renders with the new
98
92
  // bundle/locale (no in-place bundle swap needed for PR 1's scope).
99
93
  const handleSetLocale = async (next: LocaleCode) => {
100
- await setInterfaceLocaleFn({ data: { locale: next } })
94
+ await setAdminLocaleFn({ data: { locale: next } })
101
95
  window.location.reload()
102
96
  }
103
97
  return (
104
98
  <I18nProvider
105
99
  bundle={i18n.translations ?? {}}
106
100
  activeLocale={activeLocale}
107
- defaultLocale={i18n.interface.defaultLocale}
101
+ defaultLocale={i18n.admin.defaultLocale}
108
102
  localeDefinitions={localeDefinitions}
109
103
  setLocale={handleSetLocale}
110
104
  >
@@ -22,13 +22,23 @@ import type { LocaleCode } from '@byline/i18n'
22
22
  import { SignInPage } from '../admin-shell/chrome/sign-in-page.js'
23
23
  import { getActiveLocaleFn } from '../server-fns/i18n/index.js'
24
24
  import { resolveAdminCallbackPath, resolveAdminSignInRedirect } from './admin-path.js'
25
+ import { resolveSignInHomeUrl } from './sign-in-home-url.js'
25
26
 
26
27
  interface SignInSearch {
27
28
  callbackUrl?: string
28
29
  }
29
30
 
30
- export function createSignInRoute(path: string) {
31
- // biome-ignore lint/suspicious/noExplicitAny: dynamic path bypasses route-tree typing
31
+ export interface CreateSignInRouteOptions {
32
+ /**
33
+ * Client-safe URL for the sign-in page's Home link. Defaults to `/`, which
34
+ * preserves the link for integrated hosts without requiring an absolute site
35
+ * origin. Pass an absolute URL when the public home is on another origin.
36
+ */
37
+ homeUrl?: string
38
+ }
39
+
40
+ export function createSignInRoute(path: string, { homeUrl }: CreateSignInRouteOptions = {}) {
41
+ const resolvedHomeUrl = resolveSignInHomeUrl(homeUrl)
32
42
  const Route: any = createFileRoute(path as never)({
33
43
  validateSearch: (search: Record<string, unknown>): SignInSearch => {
34
44
  const callbackUrl = resolveAdminCallbackPath(search.callbackUrl)
@@ -50,6 +60,7 @@ export function createSignInRoute(path: string) {
50
60
  <SignInPage
51
61
  redirectTo={resolveAdminSignInRedirect(callbackUrl)}
52
62
  activeLocale={activeLocale}
63
+ homeUrl={resolvedHomeUrl}
53
64
  />
54
65
  )
55
66
  },
@@ -6,7 +6,7 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
 
9
- import { getClientConfig } from '@byline/core'
9
+ import { getAdminConfig } from '@byline/core'
10
10
 
11
11
  import type { ContentLocaleOption } from '../admin-shell/collections/view-menu.js'
12
12
 
@@ -14,7 +14,7 @@ export function getContentLocaleRouteConfig(): {
14
14
  contentLocales: ContentLocaleOption[]
15
15
  defaultContentLocale: string
16
16
  } {
17
- const content = getClientConfig().i18n.content
17
+ const content = getAdminConfig().i18n.content
18
18
  const labels = new Map(
19
19
  content.localeDefinitions?.map(({ code, nativeName }) => [code, nativeName]) ?? []
20
20
  )
@@ -14,7 +14,7 @@ export {
14
14
  resolveAdminCallbackPath,
15
15
  resolveAdminSignInRedirect,
16
16
  } from './admin-path.js'
17
- export { configureSignInRoutePath, getSignInRoutePath } from './sign-in-path.js'
17
+ export { getSignInRoutePath } from './sign-in-path.js'
18
18
 
19
19
  /**
20
20
  * Route factories for the Byline admin UI on TanStack Start.
@@ -45,4 +45,7 @@ export { createCollectionCreateRoute } from './create-collection-create-route.js
45
45
  export { createCollectionEditRoute } from './create-collection-edit-route.js'
46
46
  export { createCollectionHistoryRoute } from './create-collection-history-route.js'
47
47
  export { createCollectionListRoute } from './create-collection-list-route.js'
48
- export { createSignInRoute } from './create-sign-in-route.js'
48
+ export {
49
+ type CreateSignInRouteOptions,
50
+ createSignInRoute,
51
+ } from './create-sign-in-route.js'
@@ -0,0 +1,21 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ import { describe, expect, it } from 'vitest'
10
+
11
+ import { resolveSignInHomeUrl } from './sign-in-home-url.js'
12
+
13
+ describe('resolveSignInHomeUrl', () => {
14
+ it('defaults the sign-in Home link to the same-origin root', () => {
15
+ expect(resolveSignInHomeUrl(undefined)).toBe('/')
16
+ })
17
+
18
+ it('preserves an explicit host-owned Home URL', () => {
19
+ expect(resolveSignInHomeUrl('https://example.test/')).toBe('https://example.test/')
20
+ })
21
+ })
@@ -0,0 +1,12 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ /** Resolve the sign-in Home link without requiring a configured site origin. */
10
+ export function resolveSignInHomeUrl(homeUrl: string | undefined): string {
11
+ return homeUrl ?? '/'
12
+ }
@@ -1,21 +1,16 @@
1
- import { defineClientConfig } from '@byline/core'
1
+ import { defineAdminConfig } from '@byline/core'
2
2
  import { beforeEach, describe, expect, it } from 'vitest'
3
3
 
4
- import {
5
- configureSignInRoutePath,
6
- createSignInRoutePathResolver,
7
- getSignInRoutePath,
8
- } from './sign-in-path.js'
4
+ import { getSignInRoutePath } from './sign-in-path.js'
9
5
 
10
6
  describe('sign-in route configuration', () => {
11
7
  beforeEach(() => {
12
- defineClientConfig({
13
- serverURL: 'http://localhost:3000',
8
+ defineAdminConfig({
14
9
  routes: { signIn: '/staff/login/' },
15
10
  collections: [],
16
11
  admin: [],
17
12
  i18n: {
18
- interface: { defaultLocale: 'en', locales: [] },
13
+ admin: { defaultLocale: 'en', locales: [] },
19
14
  content: { defaultLocale: 'en', locales: [] },
20
15
  },
21
16
  })
@@ -24,56 +19,4 @@ describe('sign-in route configuration', () => {
24
19
  it('reads and normalizes routes.signIn', () => {
25
20
  expect(getSignInRoutePath()).toBe('/staff/login')
26
21
  })
27
-
28
- it('allows a deprecated signInPath override when it matches canonical config', () => {
29
- expect(configureSignInRoutePath('staff/login/')).toBe('/staff/login')
30
- })
31
-
32
- it('rejects an invalid or divergent deprecated override when resolved', () => {
33
- expect(() => configureSignInRoutePath('/auth/login/')).toThrow(/must match routes\.signIn/)
34
- expect(() => configureSignInRoutePath('/auth/../login')).toThrow(/routes\.signIn/)
35
- })
36
-
37
- it('validates an override against configured rather than default route trees', () => {
38
- defineClientConfig({
39
- serverURL: 'http://localhost:3000',
40
- routes: { admin: '/cms', api: '/rpc', signIn: '/admin/login' },
41
- collections: [],
42
- admin: [],
43
- i18n: {
44
- interface: { defaultLocale: 'en', locales: [] },
45
- content: { defaultLocale: 'en', locales: [] },
46
- },
47
- })
48
-
49
- expect(configureSignInRoutePath('/admin/login/')).toBe('/admin/login')
50
- expect(() => configureSignInRoutePath('/cms/login')).toThrow(/routes\.signIn/)
51
- })
52
-
53
- it('defers a deprecated override until parent beforeLoad config registration', () => {
54
- defineClientConfig({
55
- serverURL: 'http://localhost:3000',
56
- routes: { signIn: '/old/login' },
57
- collections: [],
58
- admin: [],
59
- i18n: {
60
- interface: { defaultLocale: 'en', locales: [] },
61
- content: { defaultLocale: 'en', locales: [] },
62
- },
63
- })
64
- const resolveSignInPath = createSignInRoutePathResolver('/staff/login/')
65
-
66
- defineClientConfig({
67
- serverURL: 'http://localhost:3000',
68
- routes: { signIn: '/staff/login' },
69
- collections: [],
70
- admin: [],
71
- i18n: {
72
- interface: { defaultLocale: 'en', locales: [] },
73
- content: { defaultLocale: 'en', locales: [] },
74
- },
75
- })
76
-
77
- expect(resolveSignInPath()).toBe('/staff/login')
78
- })
79
22
  })
@@ -6,31 +6,9 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
 
9
- import { getClientConfig, resolveRoutes } from '@byline/core'
9
+ import { getAdminConfig } from '@byline/core'
10
10
 
11
11
  /** Read the canonical client-safe sign-in destination. */
12
12
  export function getSignInRoutePath(): string {
13
- return getClientConfig().routes.signIn
14
- }
15
-
16
- /** @deprecated Configure `routes.signIn`; retained for route-factory input validation. */
17
- export function configureSignInRoutePath(legacyOverride?: string): string {
18
- const routes = getClientConfig().routes
19
- const configured = routes.signIn
20
- if (legacyOverride === undefined) return configured
21
-
22
- const normalizedOverride = resolveRoutes({ ...routes, signIn: legacyOverride }).signIn
23
- if (normalizedOverride !== configured) {
24
- throw new Error(
25
- `createAdminLayoutRoute signInPath (${normalizedOverride}) must match routes.signIn (${configured})`
26
- )
27
- }
28
- return configured
29
- }
30
-
31
- /** Defer deprecated override validation until the admin route's beforeLoad boundary. */
32
- export function createSignInRoutePathResolver(legacyOverride?: string): () => string {
33
- return legacyOverride === undefined
34
- ? getSignInRoutePath
35
- : () => configureSignInRoutePath(legacyOverride)
13
+ return getAdminConfig().routes.signIn
36
14
  }
@@ -91,7 +91,7 @@ export const adminSignIn = createServerFn({ method: 'POST' })
91
91
  *
92
92
  * - The cookie is unset (the cascade falls through to the existing
93
93
  * column / Accept-Language / default anyway).
94
- * - The cookie carries a locale outside `i18n.interface.locales`
94
+ * - The cookie carries a locale outside `i18n.admin.locales`
95
95
  * (stale value pointing at a removed locale — let the resolver
96
96
  * fall through cleanly).
97
97
  * - No admin store is configured (headless tooling paths).
@@ -102,7 +102,7 @@ async function reconcileLocaleAfterSignIn(adminUserId: string): Promise<void> {
102
102
  if (cookieLocale == null) return
103
103
 
104
104
  const core = bylineCore()
105
- const locales = core.config.i18n.interface.locales
105
+ const locales = core.config.i18n.admin.locales
106
106
  if (!locales.includes(cookieLocale)) return
107
107
 
108
108
  const adminStore = core.adminStore
@@ -7,5 +7,5 @@
7
7
  */
8
8
 
9
9
  export { getActiveLocaleFn } from './get-active-locale.js'
10
- export { setInterfaceLocaleFn } from './set-locale.js'
11
- export type { SetInterfaceLocaleInput, SetInterfaceLocaleResult } from './set-locale.js'
10
+ export { setAdminLocaleFn } from './set-locale.js'
11
+ export type { SetAdminLocaleInput, SetAdminLocaleResult } from './set-locale.js'
@@ -15,7 +15,7 @@
15
15
  * is also updated so the choice follows the user across devices.
16
16
  *
17
17
  * Locale validation is host-side: the request locale must appear in
18
- * `config.i18n.interface.locales` (or be `null`, which clears the
18
+ * `config.i18n.admin.locales` (or be `null`, which clears the
19
19
  * cookie + column and re-engages the detection cascade). Validation
20
20
  * lives here rather than in the admin module so the rule stays close
21
21
  * to the host's i18n config.
@@ -35,12 +35,12 @@ import { getAdminRequestContext } from '@byline/client/server'
35
35
  import { clearAdminLocaleCookie, setAdminLocaleCookie } from '../../i18n/locale-cookie.js'
36
36
  import { bylineCore } from '../../integrations/byline-core.js'
37
37
 
38
- export interface SetInterfaceLocaleInput {
38
+ export interface SetAdminLocaleInput {
39
39
  /** BCP 47 tag, or `null` to clear the preference. */
40
40
  locale: string | null
41
41
  }
42
42
 
43
- export interface SetInterfaceLocaleResult {
43
+ export interface SetAdminLocaleResult {
44
44
  ok: true
45
45
  /** Echo of the persisted value. `null` means the column was cleared. */
46
46
  locale: string | null
@@ -53,17 +53,17 @@ export interface SetInterfaceLocaleResult {
53
53
  account: AccountResponse | null
54
54
  }
55
55
 
56
- export const setInterfaceLocaleFn = createServerFn({ method: 'POST' })
57
- .validator((input: SetInterfaceLocaleInput) => input)
58
- .handler(async ({ data }): Promise<SetInterfaceLocaleResult> => {
56
+ export const setAdminLocaleFn = createServerFn({ method: 'POST' })
57
+ .validator((input: SetAdminLocaleInput) => input)
58
+ .handler(async ({ data }): Promise<SetAdminLocaleResult> => {
59
59
  const core = bylineCore()
60
- const locales = core.config.i18n.interface.locales
60
+ const locales = core.config.i18n.admin.locales
61
61
 
62
62
  // Validate against the permitted set. `null` is always permitted —
63
63
  // it's the "use detection" signal.
64
64
  if (data.locale != null && !locales.includes(data.locale)) {
65
65
  throw new Error(
66
- `[setInterfaceLocaleFn] locale '${data.locale}' is not in i18n.interface.locales [${locales.join(', ')}].`
66
+ `[setAdminLocaleFn] locale '${data.locale}' is not in i18n.admin.locales [${locales.join(', ')}].`
67
67
  )
68
68
  }
69
69