@olmocms/front 0.1.2 → 0.1.6

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.
@@ -25,6 +25,7 @@ __export(components_exports, {
25
25
  JsonLd: () => JsonLd,
26
26
  RouteProvider: () => RouteProvider,
27
27
  RouteSetter: () => RouteSetter,
28
+ StageLogin: () => StageLogin,
28
29
  gtmClick: () => gtmClick,
29
30
  gtmFormSent: () => gtmFormSent,
30
31
  gtmPageView: () => gtmPageView,
@@ -100,12 +101,104 @@ function gtmClick({ category, action, label }) {
100
101
  label: cleanLabel[cleanLabel.length - 1]
101
102
  });
102
103
  }
104
+
105
+ // src/components/StageLogin.tsx
106
+ var import_react4 = require("react");
107
+ var import_navigation = require("next/navigation");
108
+ var import_jsx_runtime4 = require("react/jsx-runtime");
109
+ function OlmoLogo({ className }) {
110
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
111
+ "svg",
112
+ {
113
+ role: "img",
114
+ "aria-label": "Olmo",
115
+ xmlns: "http://www.w3.org/2000/svg",
116
+ viewBox: "0 0 225 232",
117
+ className,
118
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M102.3,12.2c75-6.4,129.5,63.8,108.1,135.4-23.3,77.6-124.6,98.3-176.6,36.2C-18.2,121.7,19.1,19.2,102.4,12.2h-.1ZM87.4,202.3c35.6,9.9,73.6-2.3,95.6-32.1,37.4-50.9,12.4-125.6-49.4-140.8C59.5,11.2,1.3,86.9,32.6,154.9c7.7,16.8,23.8,34.9,41.6,40.8l5.5-20.1c-20-27-17.3-63.2,7.3-86.4,11-10.3,38.6-24.9,53.1-28.9s8.1-.9,10.4,3.2,6.5,23.1,7.4,28.4c4,22.6,2.1,44.6-13,62.8-13.1,15.8-31,22.5-50.8,25.5l-6.6,22.1h0ZM139.2,77c-33.7,10.7-72,38.9-54.1,79.1l11-20.1c4.1-4.6,12.6-23.1,17.7-24.7,6.3-2,10.9,3.5,8.7,9.7s-11.8,18.1-15.5,25.1-9,17.8-8.3,18.5c43-8.8,54.8-49.5,40.5-87.6h0Z" })
119
+ }
120
+ );
121
+ }
122
+ function StageLogin({
123
+ action,
124
+ title = "Olmo Staging access",
125
+ description = "This environment is restricted. Please sign in to continue.",
126
+ usernameLabel = "Username",
127
+ passwordLabel = "Password",
128
+ submitLabel = "Sign in",
129
+ invalidCredentialsMessage = "Invalid username or password.",
130
+ missingConfigMessage = "Stage authentication is not configured."
131
+ }) {
132
+ const params = (0, import_navigation.useSearchParams)();
133
+ const router = (0, import_navigation.useRouter)();
134
+ const from = params.get("from") ?? "/";
135
+ const [state, formAction, pending] = (0, import_react4.useActionState)(action, {});
136
+ (0, import_react4.useEffect)(() => {
137
+ if (state.redirectTo) router.replace(state.redirectTo);
138
+ }, [state.redirectTo, router]);
139
+ const errorMessage = state.error === "invalid_credentials" ? invalidCredentialsMessage : state.error === "missing_config" ? missingConfigMessage : null;
140
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex min-h-full flex-col justify-center px-6 py-12 lg:px-8", children: [
141
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "sm:mx-auto sm:w-full sm:max-w-[384px]", children: [
142
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(OlmoLogo, { className: "mx-auto h-10 w-auto " }),
143
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("header", { className: "space-y-1", children: [
144
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h1", { className: "text-xl text-center font-semibold text-neutral-900", children: title }),
145
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-sm text-center text-neutral-600", children: description })
146
+ ] })
147
+ ] }),
148
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mt-10 sm:mx-auto sm:w-full sm:max-w-[384px]", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("form", { action: formAction, method: "POST", className: "space-y-6", children: [
149
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "hidden", name: "from", value: from }),
150
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
151
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { htmlFor: "username", className: "block text-sm/6 font-medium text-gray-900", children: usernameLabel }),
152
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mt-2", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
153
+ "input",
154
+ {
155
+ id: "username",
156
+ name: "username",
157
+ type: "username",
158
+ disabled: pending,
159
+ required: true,
160
+ autoComplete: "username",
161
+ className: "block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-black sm:text-sm/6"
162
+ }
163
+ ) })
164
+ ] }),
165
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
166
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { htmlFor: "password", className: "block text-sm/6 font-medium text-gray-900", children: passwordLabel }) }),
167
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mt-2", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
168
+ "input",
169
+ {
170
+ id: "password",
171
+ name: "password",
172
+ type: "password",
173
+ disabled: pending,
174
+ required: true,
175
+ autoComplete: "current-password",
176
+ className: "block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-black sm:text-sm/6"
177
+ }
178
+ ) })
179
+ ] }),
180
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
181
+ errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { role: "alert", className: "text-sm text-red-600", children: errorMessage }) : null,
182
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
183
+ "button",
184
+ {
185
+ type: "submit",
186
+ disabled: pending,
187
+ className: "cursor-pointer flex w-full justify-center rounded-md bg-black px-3 py-1.5 text-sm/6 font-semibold text-white shadow-xs hover:bg-black focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-black",
188
+ children: pending ? "\u2026" : submitLabel
189
+ }
190
+ )
191
+ ] })
192
+ ] }) })
193
+ ] }) });
194
+ }
103
195
  // Annotate the CommonJS export names for ESM import in node:
104
196
  0 && (module.exports = {
105
197
  GtmPage,
106
198
  JsonLd,
107
199
  RouteProvider,
108
200
  RouteSetter,
201
+ StageLogin,
109
202
  gtmClick,
110
203
  gtmFormSent,
111
204
  gtmPageView,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/index.ts","../../src/components/RouteContext.tsx","../../src/components/RouteSetter.tsx","../../src/components/JsonLd.tsx","../../src/components/GtmPage.tsx"],"sourcesContent":["export { RouteProvider, useRoute } from './RouteContext.js';\nexport { RouteSetter } from './RouteSetter.js';\nexport { JsonLd } from './JsonLd.js';\nexport { GtmPage, gtmPageView, gtmFormSent, gtmClick } from './GtmPage.js';\n","'use client';\n\nimport { createContext, useContext, useState } from 'react';\nimport type { ReactNode } from 'react';\n\ntype LocaleSlugs = Record<string, string>;\n\nconst RouteContext = createContext<{\n slugs: LocaleSlugs;\n setSlugs: (slugs: LocaleSlugs) => void;\n}>({ slugs: {}, setSlugs: () => {} });\n\nexport function RouteProvider({ children }: { children: ReactNode }) {\n const [slugs, setSlugs] = useState<LocaleSlugs>({});\n return (\n <RouteContext.Provider value={{ slugs, setSlugs }}>\n {children}\n </RouteContext.Provider>\n );\n}\n\nexport const useRoute = () => useContext(RouteContext);\n","'use client';\n\nimport { useEffect } from 'react';\nimport { useRoute } from './RouteContext.js';\n\nexport function RouteSetter({ slugs }: { slugs: Record<string, string> }) {\n const { setSlugs } = useRoute();\n\n useEffect(() => {\n const leafSlugs = Object.fromEntries(\n Object.entries(slugs).map(([locale, slug]) => [locale, slug.split('/').pop() ?? slug]),\n );\n setSlugs(leafSlugs);\n return () => setSlugs({});\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [JSON.stringify(slugs)]);\n\n return null;\n}\n","export function JsonLd({ schema }: { schema: object }) {\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n />\n );\n}\n","'use client';\n\nimport { useEffect } from 'react';\n\ninterface GtmPageProps {\n slug: string | null;\n lang: string | null;\n name: string | null;\n template: string | null;\n}\n\ninterface GtmFormProps {\n data: object | null;\n name: string | null;\n}\n\ninterface GtmClickProps {\n category: string | null;\n action: string | null;\n label: string | null;\n}\n\ndeclare global {\n interface Window {\n dataLayer: any[];\n }\n}\n\nexport function GtmPage({ slug, lang, name, template }: GtmPageProps) {\n useEffect(() => {\n if (slug) {\n gtmPageView({\n page_name: name,\n page_category: (template as any)?.name,\n page_language: lang,\n path_clean: slug,\n });\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [slug]);\n\n return <></>;\n}\n\nexport function gtmPageView(props: Record<string, any>) {\n return window.dataLayer?.push({ event: 'page_view', url: window.location.href, ...props });\n}\n\nexport function gtmFormSent({ data, name }: GtmFormProps) {\n return window.dataLayer?.push({ ...data, event: 'form_sent', form: name });\n}\n\nexport function gtmClick({ category, action, label }: GtmClickProps) {\n const cleanLabel = label?.split('/') ?? [];\n return window.dataLayer?.push({\n event: 'click',\n category,\n action,\n label: cleanLabel[cleanLabel.length - 1],\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEA,mBAAoD;AAahD;AARJ,IAAM,mBAAe,4BAGlB,EAAE,OAAO,CAAC,GAAG,UAAU,MAAM;AAAC,EAAE,CAAC;AAE7B,SAAS,cAAc,EAAE,SAAS,GAA4B;AACnE,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAsB,CAAC,CAAC;AAClD,SACE,4CAAC,aAAa,UAAb,EAAsB,OAAO,EAAE,OAAO,SAAS,GAC7C,UACH;AAEJ;AAEO,IAAM,WAAW,UAAM,yBAAW,YAAY;;;ACnBrD,IAAAA,gBAA0B;AAGnB,SAAS,YAAY,EAAE,MAAM,GAAsC;AACxE,QAAM,EAAE,SAAS,IAAI,SAAS;AAE9B,+BAAU,MAAM;AACd,UAAM,YAAY,OAAO;AAAA,MACvB,OAAO,QAAQ,KAAK,EAAE,IAAI,CAAC,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,KAAK,MAAM,GAAG,EAAE,IAAI,KAAK,IAAI,CAAC;AAAA,IACvF;AACA,aAAS,SAAS;AAClB,WAAO,MAAM,SAAS,CAAC,CAAC;AAAA,EAE1B,GAAG,CAAC,KAAK,UAAU,KAAK,CAAC,CAAC;AAE1B,SAAO;AACT;;;AChBI,IAAAC,sBAAA;AAFG,SAAS,OAAO,EAAE,OAAO,GAAuB;AACrD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,yBAAyB,EAAE,QAAQ,KAAK,UAAU,MAAM,EAAE;AAAA;AAAA,EAC5D;AAEJ;;;ACLA,IAAAC,gBAA0B;AAuCjB,IAAAC,sBAAA;AAbF,SAAS,QAAQ,EAAE,MAAM,MAAM,MAAM,SAAS,GAAiB;AACpE,+BAAU,MAAM;AACd,QAAI,MAAM;AACR,kBAAY;AAAA,QACV,WAAW;AAAA,QACX,eAAgB,UAAkB;AAAA,QAClC,eAAe;AAAA,QACf,YAAY;AAAA,MACd,CAAC;AAAA,IACH;AAAA,EAEF,GAAG,CAAC,IAAI,CAAC;AAET,SAAO,6EAAE;AACX;AAEO,SAAS,YAAY,OAA4B;AACtD,SAAO,OAAO,WAAW,KAAK,EAAE,OAAO,aAAa,KAAK,OAAO,SAAS,MAAM,GAAG,MAAM,CAAC;AAC3F;AAEO,SAAS,YAAY,EAAE,MAAM,KAAK,GAAiB;AACxD,SAAO,OAAO,WAAW,KAAK,EAAE,GAAG,MAAM,OAAO,aAAa,MAAM,KAAK,CAAC;AAC3E;AAEO,SAAS,SAAS,EAAE,UAAU,QAAQ,MAAM,GAAkB;AACnE,QAAM,aAAa,OAAO,MAAM,GAAG,KAAK,CAAC;AACzC,SAAO,OAAO,WAAW,KAAK;AAAA,IAC5B,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA,OAAO,WAAW,WAAW,SAAS,CAAC;AAAA,EACzC,CAAC;AACH;","names":["import_react","import_jsx_runtime","import_react","import_jsx_runtime"]}
1
+ {"version":3,"sources":["../../src/components/index.ts","../../src/components/RouteContext.tsx","../../src/components/RouteSetter.tsx","../../src/components/JsonLd.tsx","../../src/components/GtmPage.tsx","../../src/components/StageLogin.tsx"],"sourcesContent":["export { RouteProvider, useRoute } from './RouteContext.js';\nexport { RouteSetter } from './RouteSetter.js';\nexport { JsonLd } from './JsonLd.js';\nexport { GtmPage, gtmPageView, gtmFormSent, gtmClick } from './GtmPage.js';\nexport { StageLogin } from './StageLogin.js';\nexport type { StageLoginProps } from './StageLogin.js';\n","'use client';\n\nimport { createContext, useContext, useState } from 'react';\nimport type { ReactNode } from 'react';\n\ntype LocaleSlugs = Record<string, string>;\n\nconst RouteContext = createContext<{\n slugs: LocaleSlugs;\n setSlugs: (slugs: LocaleSlugs) => void;\n}>({ slugs: {}, setSlugs: () => {} });\n\nexport function RouteProvider({ children }: { children: ReactNode }) {\n const [slugs, setSlugs] = useState<LocaleSlugs>({});\n return (\n <RouteContext.Provider value={{ slugs, setSlugs }}>\n {children}\n </RouteContext.Provider>\n );\n}\n\nexport const useRoute = () => useContext(RouteContext);\n","'use client';\n\nimport { useEffect } from 'react';\nimport { useRoute } from './RouteContext.js';\n\nexport function RouteSetter({ slugs }: { slugs: Record<string, string> }) {\n const { setSlugs } = useRoute();\n\n useEffect(() => {\n const leafSlugs = Object.fromEntries(\n Object.entries(slugs).map(([locale, slug]) => [locale, slug.split('/').pop() ?? slug]),\n );\n setSlugs(leafSlugs);\n return () => setSlugs({});\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [JSON.stringify(slugs)]);\n\n return null;\n}\n","export function JsonLd({ schema }: { schema: object }) {\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n />\n );\n}\n","'use client';\n\nimport { useEffect } from 'react';\n\ninterface GtmPageProps {\n slug: string | null;\n lang: string | null;\n name: string | null;\n template: string | null;\n}\n\ninterface GtmFormProps {\n data: object | null;\n name: string | null;\n}\n\ninterface GtmClickProps {\n category: string | null;\n action: string | null;\n label: string | null;\n}\n\ndeclare global {\n interface Window {\n dataLayer: any[];\n }\n}\n\nexport function GtmPage({ slug, lang, name, template }: GtmPageProps) {\n useEffect(() => {\n if (slug) {\n gtmPageView({\n page_name: name,\n page_category: (template as any)?.name,\n page_language: lang,\n path_clean: slug,\n });\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [slug]);\n\n return <></>;\n}\n\nexport function gtmPageView(props: Record<string, any>) {\n return window.dataLayer?.push({ event: 'page_view', url: window.location.href, ...props });\n}\n\nexport function gtmFormSent({ data, name }: GtmFormProps) {\n return window.dataLayer?.push({ ...data, event: 'form_sent', form: name });\n}\n\nexport function gtmClick({ category, action, label }: GtmClickProps) {\n const cleanLabel = label?.split('/') ?? [];\n return window.dataLayer?.push({\n event: 'click',\n category,\n action,\n label: cleanLabel[cleanLabel.length - 1],\n });\n}\n","import { useActionState, useEffect } from 'react';\nimport { useSearchParams, useRouter } from 'next/navigation';\n\nfunction OlmoLogo({ className }: { className?: string }) {\n return (\n <svg\n role=\"img\"\n aria-label=\"Olmo\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 225 232\"\n className={className}\n >\n <path d=\"M102.3,12.2c75-6.4,129.5,63.8,108.1,135.4-23.3,77.6-124.6,98.3-176.6,36.2C-18.2,121.7,19.1,19.2,102.4,12.2h-.1ZM87.4,202.3c35.6,9.9,73.6-2.3,95.6-32.1,37.4-50.9,12.4-125.6-49.4-140.8C59.5,11.2,1.3,86.9,32.6,154.9c7.7,16.8,23.8,34.9,41.6,40.8l5.5-20.1c-20-27-17.3-63.2,7.3-86.4,11-10.3,38.6-24.9,53.1-28.9s8.1-.9,10.4,3.2,6.5,23.1,7.4,28.4c4,22.6,2.1,44.6-13,62.8-13.1,15.8-31,22.5-50.8,25.5l-6.6,22.1h0ZM139.2,77c-33.7,10.7-72,38.9-54.1,79.1l11-20.1c4.1-4.6,12.6-23.1,17.7-24.7,6.3-2,10.9,3.5,8.7,9.7s-11.8,18.1-15.5,25.1-9,17.8-8.3,18.5c43-8.8,54.8-49.5,40.5-87.6h0Z\" />\n </svg>\n );\n}\n\ntype StageLoginState = {\n error?: 'missing_config' | 'invalid_credentials' | null;\n redirectTo?: string | null;\n};\n\ntype StageLoginAction = (prev: StageLoginState, formData: FormData) => Promise<StageLoginState>;\n\nexport type StageLoginProps = {\n action: StageLoginAction;\n title?: string;\n description?: string;\n usernameLabel?: string;\n passwordLabel?: string;\n submitLabel?: string;\n invalidCredentialsMessage?: string;\n missingConfigMessage?: string;\n};\n\nexport function StageLoginOLD({\n action,\n title = 'Staging access',\n description = 'This environment is restricted. Please sign in to continue.',\n usernameLabel = 'Username',\n passwordLabel = 'Password',\n submitLabel = 'Sign in',\n invalidCredentialsMessage = 'Invalid username or password.',\n missingConfigMessage = 'Stage authentication is not configured.',\n}: StageLoginProps) {\n const params = useSearchParams();\n const from = params.get('from') ?? '/';\n const [state, formAction, pending] = useActionState<StageLoginState, FormData>(action, {});\n\n const errorMessage =\n state.error === 'invalid_credentials'\n ? invalidCredentialsMessage\n : state.error === 'missing_config'\n ? missingConfigMessage\n : null;\n\n return (\n <main className=\"flex min-h-screen items-center justify-center bg-neutral-50 px-4 py-12\">\n <form\n action={formAction}\n className=\"relative w-full max-w-md space-y-5 rounded-lg border border-neutral-200 bg-white p-8 shadow-sm\"\n >\n <div className=\"w-full min-w-[200px]\">\n <header className=\"space-y-1 text-center\">\n <h1 className=\"text-xl font-semibold text-neutral-900\">{title}</h1>\n <p className=\"text-sm text-neutral-600\">{description}</p>\n </header>\n\n <input type=\"hidden\" name=\"from\" value={from} />\n\n <label className=\"block space-y-1\">\n <span className=\"text-sm font-medium text-neutral-800\">{usernameLabel}</span>\n <input\n name=\"username\"\n type=\"text\"\n autoComplete=\"username\"\n required\n disabled={pending}\n className=\"w-full rounded border border-neutral-300 px-3 py-2 text-sm outline-none focus:border-neutral-900\"\n />\n </label>\n\n <label className=\"block space-y-1\">\n <span className=\"text-sm font-medium text-neutral-800\">{passwordLabel}</span>\n <input\n name=\"password\"\n type=\"password\"\n autoComplete=\"current-password\"\n required\n disabled={pending}\n className=\"w-full rounded border border-neutral-300 px-3 py-2 text-sm outline-none focus:border-neutral-900\"\n />\n </label>\n\n {errorMessage ? (\n <p role=\"alert\" className=\"text-sm text-red-600\">\n {errorMessage}\n </p>\n ) : null}\n\n <button\n type=\"submit\"\n disabled={pending}\n className=\"w-full rounded bg-neutral-900 px-4 py-2 text-sm font-medium text-white transition hover:bg-neutral-800 disabled:opacity-60\"\n >\n {pending ? '…' : submitLabel}\n </button>\n </div>\n </form>\n </main>\n );\n}\n\n\nexport function StageLogin({\n action,\n title = 'Olmo Staging access',\n description = 'This environment is restricted. Please sign in to continue.',\n usernameLabel = 'Username',\n passwordLabel = 'Password',\n submitLabel = 'Sign in',\n invalidCredentialsMessage = 'Invalid username or password.',\n missingConfigMessage = 'Stage authentication is not configured.', \n}: StageLoginProps) {\n \n const params = useSearchParams();\n const router = useRouter();\n const from = params.get('from') ?? '/';\n const [state, formAction, pending] = useActionState<StageLoginState, FormData>(action, {});\n\n // Once the action returns a redirect target the cookie is already in the\n // browser's jar (it shipped as Set-Cookie on the action response). Only\n // then do we navigate, so the next GET carries the session and middleware\n // doesn't bounce the user back to login.\n useEffect(() => {\n if (state.redirectTo) router.replace(state.redirectTo);\n }, [state.redirectTo, router]);\n\n const errorMessage =\n state.error === 'invalid_credentials'\n ? invalidCredentialsMessage\n : state.error === 'missing_config'\n ? missingConfigMessage\n : null;\n\n return (\n <>\n <div className=\"flex min-h-full flex-col justify-center px-6 py-12 lg:px-8\">\n <div className=\"sm:mx-auto sm:w-full sm:max-w-[384px]\">\n <OlmoLogo className=\"mx-auto h-10 w-auto \" />\n {/* <h2 className=\"mt-10 text-center text-2xl/9 font-bold tracking-tight text-gray-900\">\n Sign in to your account\n </h2> */}\n <header className=\"space-y-1\">\n <h1 className=\"text-xl text-center font-semibold text-neutral-900\">{title}</h1>\n <p className=\"text-sm text-center text-neutral-600\">{description}</p>\n </header> \n </div>\n\n <div className=\"mt-10 sm:mx-auto sm:w-full sm:max-w-[384px]\">\n <form action={formAction} method=\"POST\" className=\"space-y-6\">\n <input type=\"hidden\" name=\"from\" value={from} />\n <div>\n <label htmlFor=\"username\" className=\"block text-sm/6 font-medium text-gray-900\">\n {usernameLabel}\n </label>\n <div className=\"mt-2\">\n <input\n id=\"username\"\n name=\"username\"\n type=\"username\"\n disabled={pending}\n required\n autoComplete=\"username\"\n className=\"block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-black sm:text-sm/6\"\n />\n </div>\n </div>\n\n <div>\n <div className=\"flex items-center justify-between\">\n <label htmlFor=\"password\" className=\"block text-sm/6 font-medium text-gray-900\">\n {passwordLabel}\n </label>\n </div>\n <div className=\"mt-2\">\n <input\n id=\"password\"\n name=\"password\"\n type=\"password\"\n disabled={pending}\n required\n autoComplete=\"current-password\"\n className=\"block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-black sm:text-sm/6\"\n />\n </div>\n </div>\n\n <div>\n {errorMessage ? (\n <p role=\"alert\" className=\"text-sm text-red-600\">\n {errorMessage}\n </p>\n ) : null} \n <button\n type=\"submit\"\n disabled={pending}\n className=\"cursor-pointer flex w-full justify-center rounded-md bg-black px-3 py-1.5 text-sm/6 font-semibold text-white shadow-xs hover:bg-black focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-black\"\n >\n {pending ? '…' : submitLabel}\n </button>\n </div>\n </form>\n\n </div>\n </div>\n </>\n )\n}\n"],"mappings":"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","names":["import_react","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_jsx_runtime"]}
@@ -43,4 +43,21 @@ declare function gtmPageView(props: Record<string, any>): number;
43
43
  declare function gtmFormSent({ data, name }: GtmFormProps): number;
44
44
  declare function gtmClick({ category, action, label }: GtmClickProps): number;
45
45
 
46
- export { GtmPage, JsonLd, RouteProvider, RouteSetter, gtmClick, gtmFormSent, gtmPageView, useRoute };
46
+ type StageLoginState = {
47
+ error?: 'missing_config' | 'invalid_credentials' | null;
48
+ redirectTo?: string | null;
49
+ };
50
+ type StageLoginAction = (prev: StageLoginState, formData: FormData) => Promise<StageLoginState>;
51
+ type StageLoginProps = {
52
+ action: StageLoginAction;
53
+ title?: string;
54
+ description?: string;
55
+ usernameLabel?: string;
56
+ passwordLabel?: string;
57
+ submitLabel?: string;
58
+ invalidCredentialsMessage?: string;
59
+ missingConfigMessage?: string;
60
+ };
61
+ declare function StageLogin({ action, title, description, usernameLabel, passwordLabel, submitLabel, invalidCredentialsMessage, missingConfigMessage, }: StageLoginProps): react_jsx_runtime.JSX.Element;
62
+
63
+ export { GtmPage, JsonLd, RouteProvider, RouteSetter, StageLogin, type StageLoginProps, gtmClick, gtmFormSent, gtmPageView, useRoute };
@@ -43,4 +43,21 @@ declare function gtmPageView(props: Record<string, any>): number;
43
43
  declare function gtmFormSent({ data, name }: GtmFormProps): number;
44
44
  declare function gtmClick({ category, action, label }: GtmClickProps): number;
45
45
 
46
- export { GtmPage, JsonLd, RouteProvider, RouteSetter, gtmClick, gtmFormSent, gtmPageView, useRoute };
46
+ type StageLoginState = {
47
+ error?: 'missing_config' | 'invalid_credentials' | null;
48
+ redirectTo?: string | null;
49
+ };
50
+ type StageLoginAction = (prev: StageLoginState, formData: FormData) => Promise<StageLoginState>;
51
+ type StageLoginProps = {
52
+ action: StageLoginAction;
53
+ title?: string;
54
+ description?: string;
55
+ usernameLabel?: string;
56
+ passwordLabel?: string;
57
+ submitLabel?: string;
58
+ invalidCredentialsMessage?: string;
59
+ missingConfigMessage?: string;
60
+ };
61
+ declare function StageLogin({ action, title, description, usernameLabel, passwordLabel, submitLabel, invalidCredentialsMessage, missingConfigMessage, }: StageLoginProps): react_jsx_runtime.JSX.Element;
62
+
63
+ export { GtmPage, JsonLd, RouteProvider, RouteSetter, StageLogin, type StageLoginProps, gtmClick, gtmFormSent, gtmPageView, useRoute };
@@ -68,11 +68,103 @@ function gtmClick({ category, action, label }) {
68
68
  label: cleanLabel[cleanLabel.length - 1]
69
69
  });
70
70
  }
71
+
72
+ // src/components/StageLogin.tsx
73
+ import { useActionState, useEffect as useEffect3 } from "react";
74
+ import { useSearchParams, useRouter } from "next/navigation";
75
+ import { Fragment as Fragment2, jsx as jsx4, jsxs } from "react/jsx-runtime";
76
+ function OlmoLogo({ className }) {
77
+ return /* @__PURE__ */ jsx4(
78
+ "svg",
79
+ {
80
+ role: "img",
81
+ "aria-label": "Olmo",
82
+ xmlns: "http://www.w3.org/2000/svg",
83
+ viewBox: "0 0 225 232",
84
+ className,
85
+ children: /* @__PURE__ */ jsx4("path", { d: "M102.3,12.2c75-6.4,129.5,63.8,108.1,135.4-23.3,77.6-124.6,98.3-176.6,36.2C-18.2,121.7,19.1,19.2,102.4,12.2h-.1ZM87.4,202.3c35.6,9.9,73.6-2.3,95.6-32.1,37.4-50.9,12.4-125.6-49.4-140.8C59.5,11.2,1.3,86.9,32.6,154.9c7.7,16.8,23.8,34.9,41.6,40.8l5.5-20.1c-20-27-17.3-63.2,7.3-86.4,11-10.3,38.6-24.9,53.1-28.9s8.1-.9,10.4,3.2,6.5,23.1,7.4,28.4c4,22.6,2.1,44.6-13,62.8-13.1,15.8-31,22.5-50.8,25.5l-6.6,22.1h0ZM139.2,77c-33.7,10.7-72,38.9-54.1,79.1l11-20.1c4.1-4.6,12.6-23.1,17.7-24.7,6.3-2,10.9,3.5,8.7,9.7s-11.8,18.1-15.5,25.1-9,17.8-8.3,18.5c43-8.8,54.8-49.5,40.5-87.6h0Z" })
86
+ }
87
+ );
88
+ }
89
+ function StageLogin({
90
+ action,
91
+ title = "Olmo Staging access",
92
+ description = "This environment is restricted. Please sign in to continue.",
93
+ usernameLabel = "Username",
94
+ passwordLabel = "Password",
95
+ submitLabel = "Sign in",
96
+ invalidCredentialsMessage = "Invalid username or password.",
97
+ missingConfigMessage = "Stage authentication is not configured."
98
+ }) {
99
+ const params = useSearchParams();
100
+ const router = useRouter();
101
+ const from = params.get("from") ?? "/";
102
+ const [state, formAction, pending] = useActionState(action, {});
103
+ useEffect3(() => {
104
+ if (state.redirectTo) router.replace(state.redirectTo);
105
+ }, [state.redirectTo, router]);
106
+ const errorMessage = state.error === "invalid_credentials" ? invalidCredentialsMessage : state.error === "missing_config" ? missingConfigMessage : null;
107
+ return /* @__PURE__ */ jsx4(Fragment2, { children: /* @__PURE__ */ jsxs("div", { className: "flex min-h-full flex-col justify-center px-6 py-12 lg:px-8", children: [
108
+ /* @__PURE__ */ jsxs("div", { className: "sm:mx-auto sm:w-full sm:max-w-[384px]", children: [
109
+ /* @__PURE__ */ jsx4(OlmoLogo, { className: "mx-auto h-10 w-auto " }),
110
+ /* @__PURE__ */ jsxs("header", { className: "space-y-1", children: [
111
+ /* @__PURE__ */ jsx4("h1", { className: "text-xl text-center font-semibold text-neutral-900", children: title }),
112
+ /* @__PURE__ */ jsx4("p", { className: "text-sm text-center text-neutral-600", children: description })
113
+ ] })
114
+ ] }),
115
+ /* @__PURE__ */ jsx4("div", { className: "mt-10 sm:mx-auto sm:w-full sm:max-w-[384px]", children: /* @__PURE__ */ jsxs("form", { action: formAction, method: "POST", className: "space-y-6", children: [
116
+ /* @__PURE__ */ jsx4("input", { type: "hidden", name: "from", value: from }),
117
+ /* @__PURE__ */ jsxs("div", { children: [
118
+ /* @__PURE__ */ jsx4("label", { htmlFor: "username", className: "block text-sm/6 font-medium text-gray-900", children: usernameLabel }),
119
+ /* @__PURE__ */ jsx4("div", { className: "mt-2", children: /* @__PURE__ */ jsx4(
120
+ "input",
121
+ {
122
+ id: "username",
123
+ name: "username",
124
+ type: "username",
125
+ disabled: pending,
126
+ required: true,
127
+ autoComplete: "username",
128
+ className: "block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-black sm:text-sm/6"
129
+ }
130
+ ) })
131
+ ] }),
132
+ /* @__PURE__ */ jsxs("div", { children: [
133
+ /* @__PURE__ */ jsx4("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsx4("label", { htmlFor: "password", className: "block text-sm/6 font-medium text-gray-900", children: passwordLabel }) }),
134
+ /* @__PURE__ */ jsx4("div", { className: "mt-2", children: /* @__PURE__ */ jsx4(
135
+ "input",
136
+ {
137
+ id: "password",
138
+ name: "password",
139
+ type: "password",
140
+ disabled: pending,
141
+ required: true,
142
+ autoComplete: "current-password",
143
+ className: "block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-black sm:text-sm/6"
144
+ }
145
+ ) })
146
+ ] }),
147
+ /* @__PURE__ */ jsxs("div", { children: [
148
+ errorMessage ? /* @__PURE__ */ jsx4("p", { role: "alert", className: "text-sm text-red-600", children: errorMessage }) : null,
149
+ /* @__PURE__ */ jsx4(
150
+ "button",
151
+ {
152
+ type: "submit",
153
+ disabled: pending,
154
+ className: "cursor-pointer flex w-full justify-center rounded-md bg-black px-3 py-1.5 text-sm/6 font-semibold text-white shadow-xs hover:bg-black focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-black",
155
+ children: pending ? "\u2026" : submitLabel
156
+ }
157
+ )
158
+ ] })
159
+ ] }) })
160
+ ] }) });
161
+ }
71
162
  export {
72
163
  GtmPage,
73
164
  JsonLd,
74
165
  RouteProvider,
75
166
  RouteSetter,
167
+ StageLogin,
76
168
  gtmClick,
77
169
  gtmFormSent,
78
170
  gtmPageView,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/RouteContext.tsx","../../src/components/RouteSetter.tsx","../../src/components/JsonLd.tsx","../../src/components/GtmPage.tsx"],"sourcesContent":["'use client';\n\nimport { createContext, useContext, useState } from 'react';\nimport type { ReactNode } from 'react';\n\ntype LocaleSlugs = Record<string, string>;\n\nconst RouteContext = createContext<{\n slugs: LocaleSlugs;\n setSlugs: (slugs: LocaleSlugs) => void;\n}>({ slugs: {}, setSlugs: () => {} });\n\nexport function RouteProvider({ children }: { children: ReactNode }) {\n const [slugs, setSlugs] = useState<LocaleSlugs>({});\n return (\n <RouteContext.Provider value={{ slugs, setSlugs }}>\n {children}\n </RouteContext.Provider>\n );\n}\n\nexport const useRoute = () => useContext(RouteContext);\n","'use client';\n\nimport { useEffect } from 'react';\nimport { useRoute } from './RouteContext.js';\n\nexport function RouteSetter({ slugs }: { slugs: Record<string, string> }) {\n const { setSlugs } = useRoute();\n\n useEffect(() => {\n const leafSlugs = Object.fromEntries(\n Object.entries(slugs).map(([locale, slug]) => [locale, slug.split('/').pop() ?? slug]),\n );\n setSlugs(leafSlugs);\n return () => setSlugs({});\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [JSON.stringify(slugs)]);\n\n return null;\n}\n","export function JsonLd({ schema }: { schema: object }) {\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n />\n );\n}\n","'use client';\n\nimport { useEffect } from 'react';\n\ninterface GtmPageProps {\n slug: string | null;\n lang: string | null;\n name: string | null;\n template: string | null;\n}\n\ninterface GtmFormProps {\n data: object | null;\n name: string | null;\n}\n\ninterface GtmClickProps {\n category: string | null;\n action: string | null;\n label: string | null;\n}\n\ndeclare global {\n interface Window {\n dataLayer: any[];\n }\n}\n\nexport function GtmPage({ slug, lang, name, template }: GtmPageProps) {\n useEffect(() => {\n if (slug) {\n gtmPageView({\n page_name: name,\n page_category: (template as any)?.name,\n page_language: lang,\n path_clean: slug,\n });\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [slug]);\n\n return <></>;\n}\n\nexport function gtmPageView(props: Record<string, any>) {\n return window.dataLayer?.push({ event: 'page_view', url: window.location.href, ...props });\n}\n\nexport function gtmFormSent({ data, name }: GtmFormProps) {\n return window.dataLayer?.push({ ...data, event: 'form_sent', form: name });\n}\n\nexport function gtmClick({ category, action, label }: GtmClickProps) {\n const cleanLabel = label?.split('/') ?? [];\n return window.dataLayer?.push({\n event: 'click',\n category,\n action,\n label: cleanLabel[cleanLabel.length - 1],\n });\n}\n"],"mappings":";;;AAEA,SAAS,eAAe,YAAY,gBAAgB;AAahD;AARJ,IAAM,eAAe,cAGlB,EAAE,OAAO,CAAC,GAAG,UAAU,MAAM;AAAC,EAAE,CAAC;AAE7B,SAAS,cAAc,EAAE,SAAS,GAA4B;AACnE,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAsB,CAAC,CAAC;AAClD,SACE,oBAAC,aAAa,UAAb,EAAsB,OAAO,EAAE,OAAO,SAAS,GAC7C,UACH;AAEJ;AAEO,IAAM,WAAW,MAAM,WAAW,YAAY;;;ACnBrD,SAAS,iBAAiB;AAGnB,SAAS,YAAY,EAAE,MAAM,GAAsC;AACxE,QAAM,EAAE,SAAS,IAAI,SAAS;AAE9B,YAAU,MAAM;AACd,UAAM,YAAY,OAAO;AAAA,MACvB,OAAO,QAAQ,KAAK,EAAE,IAAI,CAAC,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,KAAK,MAAM,GAAG,EAAE,IAAI,KAAK,IAAI,CAAC;AAAA,IACvF;AACA,aAAS,SAAS;AAClB,WAAO,MAAM,SAAS,CAAC,CAAC;AAAA,EAE1B,GAAG,CAAC,KAAK,UAAU,KAAK,CAAC,CAAC;AAE1B,SAAO;AACT;;;AChBI,gBAAAA,YAAA;AAFG,SAAS,OAAO,EAAE,OAAO,GAAuB;AACrD,SACE,gBAAAA;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,yBAAyB,EAAE,QAAQ,KAAK,UAAU,MAAM,EAAE;AAAA;AAAA,EAC5D;AAEJ;;;ACLA,SAAS,aAAAC,kBAAiB;AAuCjB,0BAAAC,YAAA;AAbF,SAAS,QAAQ,EAAE,MAAM,MAAM,MAAM,SAAS,GAAiB;AACpE,EAAAD,WAAU,MAAM;AACd,QAAI,MAAM;AACR,kBAAY;AAAA,QACV,WAAW;AAAA,QACX,eAAgB,UAAkB;AAAA,QAClC,eAAe;AAAA,QACf,YAAY;AAAA,MACd,CAAC;AAAA,IACH;AAAA,EAEF,GAAG,CAAC,IAAI,CAAC;AAET,SAAO,gBAAAC,KAAA,YAAE;AACX;AAEO,SAAS,YAAY,OAA4B;AACtD,SAAO,OAAO,WAAW,KAAK,EAAE,OAAO,aAAa,KAAK,OAAO,SAAS,MAAM,GAAG,MAAM,CAAC;AAC3F;AAEO,SAAS,YAAY,EAAE,MAAM,KAAK,GAAiB;AACxD,SAAO,OAAO,WAAW,KAAK,EAAE,GAAG,MAAM,OAAO,aAAa,MAAM,KAAK,CAAC;AAC3E;AAEO,SAAS,SAAS,EAAE,UAAU,QAAQ,MAAM,GAAkB;AACnE,QAAM,aAAa,OAAO,MAAM,GAAG,KAAK,CAAC;AACzC,SAAO,OAAO,WAAW,KAAK;AAAA,IAC5B,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA,OAAO,WAAW,WAAW,SAAS,CAAC;AAAA,EACzC,CAAC;AACH;","names":["jsx","useEffect","jsx"]}
1
+ {"version":3,"sources":["../../src/components/RouteContext.tsx","../../src/components/RouteSetter.tsx","../../src/components/JsonLd.tsx","../../src/components/GtmPage.tsx","../../src/components/StageLogin.tsx"],"sourcesContent":["'use client';\n\nimport { createContext, useContext, useState } from 'react';\nimport type { ReactNode } from 'react';\n\ntype LocaleSlugs = Record<string, string>;\n\nconst RouteContext = createContext<{\n slugs: LocaleSlugs;\n setSlugs: (slugs: LocaleSlugs) => void;\n}>({ slugs: {}, setSlugs: () => {} });\n\nexport function RouteProvider({ children }: { children: ReactNode }) {\n const [slugs, setSlugs] = useState<LocaleSlugs>({});\n return (\n <RouteContext.Provider value={{ slugs, setSlugs }}>\n {children}\n </RouteContext.Provider>\n );\n}\n\nexport const useRoute = () => useContext(RouteContext);\n","'use client';\n\nimport { useEffect } from 'react';\nimport { useRoute } from './RouteContext.js';\n\nexport function RouteSetter({ slugs }: { slugs: Record<string, string> }) {\n const { setSlugs } = useRoute();\n\n useEffect(() => {\n const leafSlugs = Object.fromEntries(\n Object.entries(slugs).map(([locale, slug]) => [locale, slug.split('/').pop() ?? slug]),\n );\n setSlugs(leafSlugs);\n return () => setSlugs({});\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [JSON.stringify(slugs)]);\n\n return null;\n}\n","export function JsonLd({ schema }: { schema: object }) {\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n />\n );\n}\n","'use client';\n\nimport { useEffect } from 'react';\n\ninterface GtmPageProps {\n slug: string | null;\n lang: string | null;\n name: string | null;\n template: string | null;\n}\n\ninterface GtmFormProps {\n data: object | null;\n name: string | null;\n}\n\ninterface GtmClickProps {\n category: string | null;\n action: string | null;\n label: string | null;\n}\n\ndeclare global {\n interface Window {\n dataLayer: any[];\n }\n}\n\nexport function GtmPage({ slug, lang, name, template }: GtmPageProps) {\n useEffect(() => {\n if (slug) {\n gtmPageView({\n page_name: name,\n page_category: (template as any)?.name,\n page_language: lang,\n path_clean: slug,\n });\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [slug]);\n\n return <></>;\n}\n\nexport function gtmPageView(props: Record<string, any>) {\n return window.dataLayer?.push({ event: 'page_view', url: window.location.href, ...props });\n}\n\nexport function gtmFormSent({ data, name }: GtmFormProps) {\n return window.dataLayer?.push({ ...data, event: 'form_sent', form: name });\n}\n\nexport function gtmClick({ category, action, label }: GtmClickProps) {\n const cleanLabel = label?.split('/') ?? [];\n return window.dataLayer?.push({\n event: 'click',\n category,\n action,\n label: cleanLabel[cleanLabel.length - 1],\n });\n}\n","import { useActionState, useEffect } from 'react';\nimport { useSearchParams, useRouter } from 'next/navigation';\n\nfunction OlmoLogo({ className }: { className?: string }) {\n return (\n <svg\n role=\"img\"\n aria-label=\"Olmo\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 225 232\"\n className={className}\n >\n <path d=\"M102.3,12.2c75-6.4,129.5,63.8,108.1,135.4-23.3,77.6-124.6,98.3-176.6,36.2C-18.2,121.7,19.1,19.2,102.4,12.2h-.1ZM87.4,202.3c35.6,9.9,73.6-2.3,95.6-32.1,37.4-50.9,12.4-125.6-49.4-140.8C59.5,11.2,1.3,86.9,32.6,154.9c7.7,16.8,23.8,34.9,41.6,40.8l5.5-20.1c-20-27-17.3-63.2,7.3-86.4,11-10.3,38.6-24.9,53.1-28.9s8.1-.9,10.4,3.2,6.5,23.1,7.4,28.4c4,22.6,2.1,44.6-13,62.8-13.1,15.8-31,22.5-50.8,25.5l-6.6,22.1h0ZM139.2,77c-33.7,10.7-72,38.9-54.1,79.1l11-20.1c4.1-4.6,12.6-23.1,17.7-24.7,6.3-2,10.9,3.5,8.7,9.7s-11.8,18.1-15.5,25.1-9,17.8-8.3,18.5c43-8.8,54.8-49.5,40.5-87.6h0Z\" />\n </svg>\n );\n}\n\ntype StageLoginState = {\n error?: 'missing_config' | 'invalid_credentials' | null;\n redirectTo?: string | null;\n};\n\ntype StageLoginAction = (prev: StageLoginState, formData: FormData) => Promise<StageLoginState>;\n\nexport type StageLoginProps = {\n action: StageLoginAction;\n title?: string;\n description?: string;\n usernameLabel?: string;\n passwordLabel?: string;\n submitLabel?: string;\n invalidCredentialsMessage?: string;\n missingConfigMessage?: string;\n};\n\nexport function StageLoginOLD({\n action,\n title = 'Staging access',\n description = 'This environment is restricted. Please sign in to continue.',\n usernameLabel = 'Username',\n passwordLabel = 'Password',\n submitLabel = 'Sign in',\n invalidCredentialsMessage = 'Invalid username or password.',\n missingConfigMessage = 'Stage authentication is not configured.',\n}: StageLoginProps) {\n const params = useSearchParams();\n const from = params.get('from') ?? '/';\n const [state, formAction, pending] = useActionState<StageLoginState, FormData>(action, {});\n\n const errorMessage =\n state.error === 'invalid_credentials'\n ? invalidCredentialsMessage\n : state.error === 'missing_config'\n ? missingConfigMessage\n : null;\n\n return (\n <main className=\"flex min-h-screen items-center justify-center bg-neutral-50 px-4 py-12\">\n <form\n action={formAction}\n className=\"relative w-full max-w-md space-y-5 rounded-lg border border-neutral-200 bg-white p-8 shadow-sm\"\n >\n <div className=\"w-full min-w-[200px]\">\n <header className=\"space-y-1 text-center\">\n <h1 className=\"text-xl font-semibold text-neutral-900\">{title}</h1>\n <p className=\"text-sm text-neutral-600\">{description}</p>\n </header>\n\n <input type=\"hidden\" name=\"from\" value={from} />\n\n <label className=\"block space-y-1\">\n <span className=\"text-sm font-medium text-neutral-800\">{usernameLabel}</span>\n <input\n name=\"username\"\n type=\"text\"\n autoComplete=\"username\"\n required\n disabled={pending}\n className=\"w-full rounded border border-neutral-300 px-3 py-2 text-sm outline-none focus:border-neutral-900\"\n />\n </label>\n\n <label className=\"block space-y-1\">\n <span className=\"text-sm font-medium text-neutral-800\">{passwordLabel}</span>\n <input\n name=\"password\"\n type=\"password\"\n autoComplete=\"current-password\"\n required\n disabled={pending}\n className=\"w-full rounded border border-neutral-300 px-3 py-2 text-sm outline-none focus:border-neutral-900\"\n />\n </label>\n\n {errorMessage ? (\n <p role=\"alert\" className=\"text-sm text-red-600\">\n {errorMessage}\n </p>\n ) : null}\n\n <button\n type=\"submit\"\n disabled={pending}\n className=\"w-full rounded bg-neutral-900 px-4 py-2 text-sm font-medium text-white transition hover:bg-neutral-800 disabled:opacity-60\"\n >\n {pending ? '…' : submitLabel}\n </button>\n </div>\n </form>\n </main>\n );\n}\n\n\nexport function StageLogin({\n action,\n title = 'Olmo Staging access',\n description = 'This environment is restricted. Please sign in to continue.',\n usernameLabel = 'Username',\n passwordLabel = 'Password',\n submitLabel = 'Sign in',\n invalidCredentialsMessage = 'Invalid username or password.',\n missingConfigMessage = 'Stage authentication is not configured.', \n}: StageLoginProps) {\n \n const params = useSearchParams();\n const router = useRouter();\n const from = params.get('from') ?? '/';\n const [state, formAction, pending] = useActionState<StageLoginState, FormData>(action, {});\n\n // Once the action returns a redirect target the cookie is already in the\n // browser's jar (it shipped as Set-Cookie on the action response). Only\n // then do we navigate, so the next GET carries the session and middleware\n // doesn't bounce the user back to login.\n useEffect(() => {\n if (state.redirectTo) router.replace(state.redirectTo);\n }, [state.redirectTo, router]);\n\n const errorMessage =\n state.error === 'invalid_credentials'\n ? invalidCredentialsMessage\n : state.error === 'missing_config'\n ? missingConfigMessage\n : null;\n\n return (\n <>\n <div className=\"flex min-h-full flex-col justify-center px-6 py-12 lg:px-8\">\n <div className=\"sm:mx-auto sm:w-full sm:max-w-[384px]\">\n <OlmoLogo className=\"mx-auto h-10 w-auto \" />\n {/* <h2 className=\"mt-10 text-center text-2xl/9 font-bold tracking-tight text-gray-900\">\n Sign in to your account\n </h2> */}\n <header className=\"space-y-1\">\n <h1 className=\"text-xl text-center font-semibold text-neutral-900\">{title}</h1>\n <p className=\"text-sm text-center text-neutral-600\">{description}</p>\n </header> \n </div>\n\n <div className=\"mt-10 sm:mx-auto sm:w-full sm:max-w-[384px]\">\n <form action={formAction} method=\"POST\" className=\"space-y-6\">\n <input type=\"hidden\" name=\"from\" value={from} />\n <div>\n <label htmlFor=\"username\" className=\"block text-sm/6 font-medium text-gray-900\">\n {usernameLabel}\n </label>\n <div className=\"mt-2\">\n <input\n id=\"username\"\n name=\"username\"\n type=\"username\"\n disabled={pending}\n required\n autoComplete=\"username\"\n className=\"block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-black sm:text-sm/6\"\n />\n </div>\n </div>\n\n <div>\n <div className=\"flex items-center justify-between\">\n <label htmlFor=\"password\" className=\"block text-sm/6 font-medium text-gray-900\">\n {passwordLabel}\n </label>\n </div>\n <div className=\"mt-2\">\n <input\n id=\"password\"\n name=\"password\"\n type=\"password\"\n disabled={pending}\n required\n autoComplete=\"current-password\"\n className=\"block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-black sm:text-sm/6\"\n />\n </div>\n </div>\n\n <div>\n {errorMessage ? (\n <p role=\"alert\" className=\"text-sm text-red-600\">\n {errorMessage}\n </p>\n ) : null} \n <button\n type=\"submit\"\n disabled={pending}\n className=\"cursor-pointer flex w-full justify-center rounded-md bg-black px-3 py-1.5 text-sm/6 font-semibold text-white shadow-xs hover:bg-black focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-black\"\n >\n {pending ? '…' : submitLabel}\n </button>\n </div>\n </form>\n\n </div>\n </div>\n </>\n )\n}\n"],"mappings":"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","names":["jsx","useEffect","jsx","useEffect","Fragment","jsx","useEffect","jsx","Fragment"]}
package/dist/index.cjs CHANGED
@@ -24,6 +24,7 @@ __export(src_exports, {
24
24
  JsonLd: () => JsonLd,
25
25
  RouteProvider: () => RouteProvider,
26
26
  RouteSetter: () => RouteSetter,
27
+ StageLogin: () => StageLogin,
27
28
  buildArticle: () => buildArticle,
28
29
  buildBreadcrumb: () => buildBreadcrumb,
29
30
  buildItemList: () => buildItemList,
@@ -38,6 +39,7 @@ __export(src_exports, {
38
39
  posting: () => posting,
39
40
  redirectmiddleware: () => redirectmiddleware,
40
41
  setSeoData: () => setSeoData,
42
+ stagegatemiddleware: () => stagegatemiddleware,
41
43
  useRoute: () => useRoute
42
44
  });
43
45
  module.exports = __toCommonJS(src_exports);
@@ -277,6 +279,108 @@ var redirectmiddleware = (next) => {
277
279
  };
278
280
  };
279
281
 
282
+ // src/middleware/stagegatemiddleware.ts
283
+ var import_server4 = require("next/server");
284
+
285
+ // src/auth/stage.ts
286
+ var COOKIE_NAME = "olmo_stage_auth";
287
+ var DEFAULT_SESSION_DAYS = 7;
288
+ function readStageEnv() {
289
+ const username = process.env.OLMO_STAGE_USERNAME;
290
+ const password = process.env.OLMO_STAGE_PASSWORD;
291
+ const secret = process.env.OLMO_STAGE_SECRET;
292
+ if (!username || !password || !secret) return null;
293
+ const days = Number(process.env.OLMO_STAGE_SESSION_DAYS);
294
+ return {
295
+ username,
296
+ password,
297
+ secret,
298
+ sessionDays: Number.isFinite(days) && days > 0 ? days : DEFAULT_SESSION_DAYS
299
+ };
300
+ }
301
+ function getStageCookieName() {
302
+ return COOKIE_NAME;
303
+ }
304
+ function fromBase64Url(input) {
305
+ const pad = input.length % 4 === 0 ? "" : "=".repeat(4 - input.length % 4);
306
+ const b64 = input.replace(/-/g, "+").replace(/_/g, "/") + pad;
307
+ const bin = atob(b64);
308
+ const out = new Uint8Array(bin.length);
309
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
310
+ return out;
311
+ }
312
+ async function importKey(secret) {
313
+ return crypto.subtle.importKey(
314
+ "raw",
315
+ new TextEncoder().encode(secret),
316
+ { name: "HMAC", hash: "SHA-256" },
317
+ false,
318
+ ["sign", "verify"]
319
+ );
320
+ }
321
+ async function verifySession(token, secret) {
322
+ if (!token || typeof token !== "string" || token.indexOf(".") === -1) return null;
323
+ const [body, sig] = token.split(".");
324
+ if (!body || !sig) return null;
325
+ let key;
326
+ try {
327
+ key = await importKey(secret);
328
+ } catch {
329
+ return null;
330
+ }
331
+ let valid = false;
332
+ try {
333
+ valid = await crypto.subtle.verify("HMAC", key, fromBase64Url(sig), new TextEncoder().encode(body));
334
+ } catch {
335
+ return null;
336
+ }
337
+ if (!valid) return null;
338
+ let payload;
339
+ try {
340
+ payload = JSON.parse(new TextDecoder().decode(fromBase64Url(body)));
341
+ } catch {
342
+ return null;
343
+ }
344
+ if (!payload || typeof payload.exp !== "number" || payload.exp < Date.now()) return null;
345
+ return payload;
346
+ }
347
+
348
+ // src/middleware/stagegatemiddleware.ts
349
+ var LOGIN_PATH = "/olmo-stage-login/";
350
+ var NOINDEX = "noindex, nofollow, noarchive";
351
+ function withNoindex(res) {
352
+ res.headers.set("X-Robots-Tag", NOINDEX);
353
+ return res;
354
+ }
355
+ var stagegatemiddleware = (next) => {
356
+ return async (request, _next) => {
357
+ if (process.env.NEXT_PUBLIC_ENV !== "stage") {
358
+ return next(request, _next);
359
+ }
360
+ const { pathname, search } = request.nextUrl;
361
+ if (pathname === LOGIN_PATH) {
362
+ return withNoindex(import_server4.NextResponse.next());
363
+ }
364
+ const env = readStageEnv();
365
+ if (!env) {
366
+ const url2 = request.nextUrl.clone();
367
+ url2.pathname = LOGIN_PATH;
368
+ url2.search = "";
369
+ return withNoindex(import_server4.NextResponse.redirect(url2, { status: 307 }));
370
+ }
371
+ const token = request.cookies.get(getStageCookieName())?.value;
372
+ const session = await verifySession(token, env.secret);
373
+ if (session) {
374
+ const res = await next(request, _next);
375
+ return withNoindex(res instanceof import_server4.NextResponse ? res : import_server4.NextResponse.next());
376
+ }
377
+ const url = request.nextUrl.clone();
378
+ url.pathname = LOGIN_PATH;
379
+ url.search = `?from=${encodeURIComponent(pathname + search)}`;
380
+ return withNoindex(import_server4.NextResponse.redirect(url, { status: 307 }));
381
+ };
382
+ };
383
+
280
384
  // src/components/RouteContext.tsx
281
385
  var import_react = require("react");
282
386
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -345,12 +449,104 @@ function gtmClick({ category, action, label }) {
345
449
  label: cleanLabel[cleanLabel.length - 1]
346
450
  });
347
451
  }
452
+
453
+ // src/components/StageLogin.tsx
454
+ var import_react4 = require("react");
455
+ var import_navigation = require("next/navigation");
456
+ var import_jsx_runtime4 = require("react/jsx-runtime");
457
+ function OlmoLogo({ className }) {
458
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
459
+ "svg",
460
+ {
461
+ role: "img",
462
+ "aria-label": "Olmo",
463
+ xmlns: "http://www.w3.org/2000/svg",
464
+ viewBox: "0 0 225 232",
465
+ className,
466
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M102.3,12.2c75-6.4,129.5,63.8,108.1,135.4-23.3,77.6-124.6,98.3-176.6,36.2C-18.2,121.7,19.1,19.2,102.4,12.2h-.1ZM87.4,202.3c35.6,9.9,73.6-2.3,95.6-32.1,37.4-50.9,12.4-125.6-49.4-140.8C59.5,11.2,1.3,86.9,32.6,154.9c7.7,16.8,23.8,34.9,41.6,40.8l5.5-20.1c-20-27-17.3-63.2,7.3-86.4,11-10.3,38.6-24.9,53.1-28.9s8.1-.9,10.4,3.2,6.5,23.1,7.4,28.4c4,22.6,2.1,44.6-13,62.8-13.1,15.8-31,22.5-50.8,25.5l-6.6,22.1h0ZM139.2,77c-33.7,10.7-72,38.9-54.1,79.1l11-20.1c4.1-4.6,12.6-23.1,17.7-24.7,6.3-2,10.9,3.5,8.7,9.7s-11.8,18.1-15.5,25.1-9,17.8-8.3,18.5c43-8.8,54.8-49.5,40.5-87.6h0Z" })
467
+ }
468
+ );
469
+ }
470
+ function StageLogin({
471
+ action,
472
+ title = "Olmo Staging access",
473
+ description = "This environment is restricted. Please sign in to continue.",
474
+ usernameLabel = "Username",
475
+ passwordLabel = "Password",
476
+ submitLabel = "Sign in",
477
+ invalidCredentialsMessage = "Invalid username or password.",
478
+ missingConfigMessage = "Stage authentication is not configured."
479
+ }) {
480
+ const params = (0, import_navigation.useSearchParams)();
481
+ const router = (0, import_navigation.useRouter)();
482
+ const from = params.get("from") ?? "/";
483
+ const [state, formAction, pending] = (0, import_react4.useActionState)(action, {});
484
+ (0, import_react4.useEffect)(() => {
485
+ if (state.redirectTo) router.replace(state.redirectTo);
486
+ }, [state.redirectTo, router]);
487
+ const errorMessage = state.error === "invalid_credentials" ? invalidCredentialsMessage : state.error === "missing_config" ? missingConfigMessage : null;
488
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex min-h-full flex-col justify-center px-6 py-12 lg:px-8", children: [
489
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "sm:mx-auto sm:w-full sm:max-w-[384px]", children: [
490
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(OlmoLogo, { className: "mx-auto h-10 w-auto " }),
491
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("header", { className: "space-y-1", children: [
492
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h1", { className: "text-xl text-center font-semibold text-neutral-900", children: title }),
493
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-sm text-center text-neutral-600", children: description })
494
+ ] })
495
+ ] }),
496
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mt-10 sm:mx-auto sm:w-full sm:max-w-[384px]", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("form", { action: formAction, method: "POST", className: "space-y-6", children: [
497
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "hidden", name: "from", value: from }),
498
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
499
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { htmlFor: "username", className: "block text-sm/6 font-medium text-gray-900", children: usernameLabel }),
500
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mt-2", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
501
+ "input",
502
+ {
503
+ id: "username",
504
+ name: "username",
505
+ type: "username",
506
+ disabled: pending,
507
+ required: true,
508
+ autoComplete: "username",
509
+ className: "block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-black sm:text-sm/6"
510
+ }
511
+ ) })
512
+ ] }),
513
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
514
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { htmlFor: "password", className: "block text-sm/6 font-medium text-gray-900", children: passwordLabel }) }),
515
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mt-2", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
516
+ "input",
517
+ {
518
+ id: "password",
519
+ name: "password",
520
+ type: "password",
521
+ disabled: pending,
522
+ required: true,
523
+ autoComplete: "current-password",
524
+ className: "block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-black sm:text-sm/6"
525
+ }
526
+ ) })
527
+ ] }),
528
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
529
+ errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { role: "alert", className: "text-sm text-red-600", children: errorMessage }) : null,
530
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
531
+ "button",
532
+ {
533
+ type: "submit",
534
+ disabled: pending,
535
+ className: "cursor-pointer flex w-full justify-center rounded-md bg-black px-3 py-1.5 text-sm/6 font-semibold text-white shadow-xs hover:bg-black focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-black",
536
+ children: pending ? "\u2026" : submitLabel
537
+ }
538
+ )
539
+ ] })
540
+ ] }) })
541
+ ] }) });
542
+ }
348
543
  // Annotate the CommonJS export names for ESM import in node:
349
544
  0 && (module.exports = {
350
545
  GtmPage,
351
546
  JsonLd,
352
547
  RouteProvider,
353
548
  RouteSetter,
549
+ StageLogin,
354
550
  buildArticle,
355
551
  buildBreadcrumb,
356
552
  buildItemList,
@@ -365,6 +561,7 @@ function gtmClick({ category, action, label }) {
365
561
  posting,
366
562
  redirectmiddleware,
367
563
  setSeoData,
564
+ stagegatemiddleware,
368
565
  useRoute
369
566
  });
370
567
  //# sourceMappingURL=index.cjs.map