next-lite-auth 0.2.2 → 0.2.4

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.
package/README.md CHANGED
@@ -58,24 +58,38 @@ export const { GET, POST } = handlers;
58
58
 
59
59
  ### 3. Wrap root layout
60
60
 
61
+ `layout.tsx` is a Server Component — wrap `LiteAuthProvider` in a client component first:
62
+
61
63
  ```tsx
62
- // app/layout.tsx
64
+ // components/auth-provider.tsx
65
+ "use client";
63
66
  import { LiteAuthProvider } from "next-lite-auth/client";
64
67
 
68
+ export function AuthProvider({ children }: { children: React.ReactNode }) {
69
+ return (
70
+ <LiteAuthProvider protect={["/dashboard", "/settings"]} appName="My App">
71
+ {children}
72
+ </LiteAuthProvider>
73
+ );
74
+ }
75
+ ```
76
+
77
+ ```tsx
78
+ // app/layout.tsx
79
+ import { AuthProvider } from "@/components/auth-provider";
80
+
65
81
  export default function RootLayout({ children }: { children: React.ReactNode }) {
66
82
  return (
67
83
  <html>
68
84
  <body>
69
- <LiteAuthProvider protect={["/dashboard", "/settings"]}>
70
- {children}
71
- </LiteAuthProvider>
85
+ <AuthProvider>{children}</AuthProvider>
72
86
  </body>
73
87
  </html>
74
88
  );
75
89
  }
76
90
  ```
77
91
 
78
- **Done.** Visiting a protected route while logged out automatically shows the built-in login UI. After login, the original page renders instantly — no redirects, no separate login page needed.
92
+ **Done.** Visiting a protected route while logged out automatically shows the built-in login UI with your app name. After login, the original page renders instantly — no redirects, no separate login page needed.
79
93
 
80
94
  ---
81
95
 
@@ -97,10 +111,17 @@ LITE_AUTH_SECRET=your-secret
97
111
 
98
112
  ## Optional: server-side middleware
99
113
 
114
+ Strings protect exact and prefix routes. Use `"/"` to protect everything, or `RegExp` for dynamic routes:
115
+
100
116
  ```ts
101
117
  // middleware.ts
102
118
  import { middleware } from "@/auth";
103
- export default middleware({ protect: ["/dashboard", "/settings"] });
119
+
120
+ export default middleware({
121
+ protect: ["/dashboard", "/settings"], // string prefixes
122
+ // protect: ["/"], // protect all routes
123
+ // protect: [/^\/[^/]+\/name$/], // dynamic route /:id/name
124
+ });
104
125
 
105
126
  export const config = {
106
127
  matcher: ["/((?!_next|api/auth).*)"],
@@ -146,7 +167,7 @@ const user = await getUserFromCookies(cookies());
146
167
  | `handlers.GET / POST` | Catch-all route handlers |
147
168
  | `middleware(options)` | Edge middleware for server-side route protection |
148
169
  | `getUserFromCookies(cookies)` | Server-side session helper |
149
- | `LiteAuthProvider` | Root provider — manages session and auto-shows login UI |
170
+ | `LiteAuthProvider` | Root provider — manages session, auto-shows login UI (`appName`, `protect` props) |
150
171
  | `useLiteAuth()` | Hook — `user`, `loading`, `login`, `logout` |
151
172
 
152
173
  ---
package/dist/client.js CHANGED
@@ -118,6 +118,22 @@ var styles = {
118
118
  border: "1px solid #6366f1",
119
119
  boxShadow: "0 0 0 3px rgba(99,102,241,0.15)"
120
120
  },
121
+ passwordWrapper: {
122
+ position: "relative",
123
+ display: "flex",
124
+ alignItems: "center"
125
+ },
126
+ passwordToggle: {
127
+ position: "absolute",
128
+ right: "10px",
129
+ background: "none",
130
+ border: "none",
131
+ cursor: "pointer",
132
+ padding: "0",
133
+ display: "flex",
134
+ alignItems: "center",
135
+ color: "#9ca3af"
136
+ },
121
137
  button: {
122
138
  height: "36px",
123
139
  width: "100%",
@@ -178,6 +194,7 @@ var darkModeCSS = `
178
194
  }
179
195
  .lite-auth-footer { color: #52525b !important; }
180
196
  .lite-auth-link { color: #71717a !important; }
197
+ .lite-auth-pw-toggle { color: #52525b !important; }
181
198
  }
182
199
  `;
183
200
  function LiteLoginPage({
@@ -189,6 +206,7 @@ function LiteLoginPage({
189
206
  const [error, setError] = (0, import_react.useState)("");
190
207
  const [loading, setLoading] = (0, import_react.useState)(false);
191
208
  const [focusedField, setFocusedField] = (0, import_react.useState)(null);
209
+ const [showPassword, setShowPassword] = (0, import_react.useState)(false);
192
210
  async function handleSubmit(e) {
193
211
  e.preventDefault();
194
212
  setError("");
@@ -237,24 +255,46 @@ function LiteLoginPage({
237
255
  ] }),
238
256
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.field, children: [
239
257
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "password", className: "lite-auth-label", style: styles.label, children: "Password" }),
240
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
241
- "input",
242
- {
243
- id: "password",
244
- name: "password",
245
- type: "password",
246
- required: true,
247
- autoComplete: "current-password",
248
- placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
249
- className: "lite-auth-input",
250
- style: {
251
- ...styles.input,
252
- ...focusedField === "password" ? styles.inputFocus : {}
253
- },
254
- onFocus: () => setFocusedField("password"),
255
- onBlur: () => setFocusedField(null)
256
- }
257
- )
258
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.passwordWrapper, children: [
259
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
260
+ "input",
261
+ {
262
+ id: "password",
263
+ name: "password",
264
+ type: showPassword ? "text" : "password",
265
+ required: true,
266
+ autoComplete: "current-password",
267
+ placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
268
+ className: "lite-auth-input",
269
+ style: {
270
+ ...styles.input,
271
+ paddingRight: "36px",
272
+ ...focusedField === "password" ? styles.inputFocus : {}
273
+ },
274
+ onFocus: () => setFocusedField("password"),
275
+ onBlur: () => setFocusedField(null)
276
+ }
277
+ ),
278
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
279
+ "button",
280
+ {
281
+ type: "button",
282
+ onClick: () => setShowPassword((v) => !v),
283
+ className: "lite-auth-pw-toggle",
284
+ style: styles.passwordToggle,
285
+ "aria-label": showPassword ? "Hide password" : "Show password",
286
+ tabIndex: -1,
287
+ children: showPassword ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
288
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94" }),
289
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19" }),
290
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "1", y1: "1", x2: "23", y2: "23" })
291
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
292
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" }),
293
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "3" })
294
+ ] })
295
+ }
296
+ )
297
+ ] })
258
298
  ] }),
259
299
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
260
300
  "button",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client/index.ts","../src/client/LiteAuthProvider.tsx","../src/client/LiteLoginPage.tsx","../src/core/matchesProtect.ts"],"sourcesContent":["export { LiteAuthProvider, useLiteAuth } from \"./LiteAuthProvider\";\nexport { LiteLoginPage } from \"./LiteLoginPage\";\n","\"use client\";\n\nimport { createContext, useContext, useState, useEffect, useCallback, ReactNode } from \"react\";\nimport { usePathname } from \"next/navigation\";\nimport { PublicUser } from \"../core/types\";\nimport { LiteLoginPage } from \"./LiteLoginPage\";\nimport { matchesProtect } from \"../core/matchesProtect\";\n\ntype LiteAuthContextValue = {\n user: PublicUser | null;\n loading: boolean;\n login: (creds: { email: string; password: string }) => Promise<{ error?: string }>;\n logout: () => Promise<void>;\n};\n\nconst LiteAuthContext = createContext<LiteAuthContextValue | null>(null);\n\ntype LiteAuthProviderProps = {\n children: ReactNode;\n protect?: (string | RegExp)[];\n appName?: string;\n loginPath?: string;\n logoutPath?: string;\n mePath?: string;\n};\n\nexport function LiteAuthProvider({\n children,\n protect = [],\n appName,\n loginPath = \"/api/auth/login\",\n logoutPath = \"/api/auth/logout\",\n mePath = \"/api/auth/me\",\n}: LiteAuthProviderProps) {\n const [user, setUser] = useState<PublicUser | null>(null);\n const [loading, setLoading] = useState(true);\n const pathname = usePathname();\n\n useEffect(() => {\n fetch(mePath)\n .then((r) => r.json())\n .then(({ user }) => setUser(user ?? null))\n .catch(() => setUser(null))\n .finally(() => setLoading(false));\n }, [mePath]);\n\n const login = useCallback(\n async ({ email, password }: { email: string; password: string }) => {\n const res = await fetch(loginPath, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ email, password }),\n });\n const data = await res.json();\n if (!res.ok) return { error: data.error ?? \"Login failed\" };\n setUser(data.user);\n return {};\n },\n [loginPath]\n );\n\n const logout = useCallback(async () => {\n await fetch(logoutPath, { method: \"POST\" });\n setUser(null);\n }, [logoutPath]);\n\n const value = { user, loading, login, logout };\n\n const isProtected = matchesProtect(protect, pathname);\n\n if (loading) {\n return (\n <LiteAuthContext.Provider value={value}>\n {children}\n </LiteAuthContext.Provider>\n );\n }\n\n if (!user && isProtected) {\n return (\n <LiteAuthContext.Provider value={value}>\n <LiteLoginPage appName={appName} />\n </LiteAuthContext.Provider>\n );\n }\n\n return (\n <LiteAuthContext.Provider value={value}>\n {children}\n </LiteAuthContext.Provider>\n );\n}\n\nexport function useLiteAuth(): LiteAuthContextValue {\n const ctx = useContext(LiteAuthContext);\n if (!ctx) throw new Error(\"useLiteAuth must be used inside <LiteAuthProvider>\");\n return ctx;\n}\n","\"use client\";\n\nimport { CSSProperties, FormEvent, useState } from \"react\";\nimport { useLiteAuth } from \"./LiteAuthProvider\";\n\ntype LiteLoginPageProps = {\n appName?: string;\n title?: string;\n description?: string;\n};\n\nconst styles: Record<string, CSSProperties> = {\n page: {\n minHeight: \"100vh\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n backgroundColor: \"#f9fafb\",\n padding: \"0 16px\",\n fontFamily: \"system-ui, -apple-system, sans-serif\",\n },\n container: {\n width: \"100%\",\n maxWidth: \"360px\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"24px\",\n },\n header: {\n textAlign: \"center\",\n },\n appName: {\n margin: \"0 0 8px\",\n fontSize: \"13px\",\n fontWeight: 600,\n color: \"#6366f1\",\n textTransform: \"uppercase\" as const,\n letterSpacing: \"0.08em\",\n },\n title: {\n margin: \"0 0 4px\",\n fontSize: \"22px\",\n fontWeight: 600,\n color: \"#111827\",\n letterSpacing: \"-0.01em\",\n },\n description: {\n margin: 0,\n fontSize: \"14px\",\n color: \"#6b7280\",\n },\n card: {\n backgroundColor: \"#ffffff\",\n border: \"1px solid #e5e7eb\",\n borderRadius: \"12px\",\n padding: \"24px\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"16px\",\n boxShadow: \"0 1px 3px rgba(0,0,0,0.07)\",\n },\n error: {\n backgroundColor: \"#fef2f2\",\n border: \"1px solid #fecaca\",\n borderRadius: \"6px\",\n padding: \"8px 12px\",\n fontSize: \"13px\",\n color: \"#dc2626\",\n },\n field: {\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"6px\",\n },\n label: {\n fontSize: \"13px\",\n fontWeight: 500,\n color: \"#374151\",\n },\n input: {\n height: \"36px\",\n width: \"100%\",\n borderRadius: \"6px\",\n border: \"1px solid #d1d5db\",\n padding: \"0 12px\",\n fontSize: \"14px\",\n color: \"#111827\",\n backgroundColor: \"#fff\",\n outline: \"none\",\n boxSizing: \"border-box\",\n transition: \"border-color 0.15s\",\n },\n inputFocus: {\n border: \"1px solid #6366f1\",\n boxShadow: \"0 0 0 3px rgba(99,102,241,0.15)\",\n },\n button: {\n height: \"36px\",\n width: \"100%\",\n borderRadius: \"6px\",\n border: \"none\",\n backgroundColor: \"#111827\",\n color: \"#ffffff\",\n fontSize: \"14px\",\n fontWeight: 500,\n cursor: \"pointer\",\n transition: \"opacity 0.15s\",\n },\n buttonDisabled: {\n opacity: 0.5,\n pointerEvents: \"none\",\n },\n footer: {\n textAlign: \"center\",\n fontSize: \"12px\",\n color: \"#9ca3af\",\n },\n link: {\n color: \"#6b7280\",\n textDecoration: \"underline\",\n textUnderlineOffset: \"3px\",\n },\n};\n\nconst darkModeCSS = `\n @media (prefers-color-scheme: dark) {\n .lite-auth-page { background-color: #09090b !important; }\n .lite-auth-appname { color: #818cf8 !important; }\n .lite-auth-title { color: #fafafa !important; }\n .lite-auth-desc { color: #a1a1aa !important; }\n .lite-auth-card {\n background-color: #18181b !important;\n border-color: #27272a !important;\n box-shadow: none !important;\n }\n .lite-auth-error {\n background-color: rgba(239,68,68,0.1) !important;\n border-color: rgba(239,68,68,0.3) !important;\n color: #f87171 !important;\n }\n .lite-auth-label { color: #e4e4e7 !important; }\n .lite-auth-input {\n background-color: transparent !important;\n border-color: #3f3f46 !important;\n color: #fafafa !important;\n }\n .lite-auth-input::placeholder { color: #52525b !important; }\n .lite-auth-input:focus {\n border-color: #6366f1 !important;\n box-shadow: 0 0 0 3px rgba(99,102,241,0.2) !important;\n }\n .lite-auth-btn {\n background-color: #fafafa !important;\n color: #09090b !important;\n }\n .lite-auth-footer { color: #52525b !important; }\n .lite-auth-link { color: #71717a !important; }\n }\n`;\n\nexport function LiteLoginPage({\n appName,\n title = \"Sign in\",\n description = \"Enter your credentials to continue\",\n}: LiteLoginPageProps) {\n const { login } = useLiteAuth();\n const [error, setError] = useState(\"\");\n const [loading, setLoading] = useState(false);\n const [focusedField, setFocusedField] = useState<string | null>(null);\n\n async function handleSubmit(e: FormEvent<HTMLFormElement>) {\n e.preventDefault();\n setError(\"\");\n setLoading(true);\n\n const form = e.currentTarget;\n const result = await login({\n email: (form.elements.namedItem(\"email\") as HTMLInputElement).value,\n password: (form.elements.namedItem(\"password\") as HTMLInputElement).value,\n });\n\n setLoading(false);\n\n if (result.error) {\n setError(result.error);\n }\n }\n\n return (\n <>\n <style>{darkModeCSS}</style>\n <div className=\"lite-auth-page\" style={styles.page}>\n <div style={styles.container}>\n <div style={styles.header}>\n {appName && <p className=\"lite-auth-appname\" style={styles.appName}>{appName}</p>}\n <h1 className=\"lite-auth-title\" style={styles.title}>{title}</h1>\n <p className=\"lite-auth-desc\" style={styles.description}>{description}</p>\n </div>\n\n <div className=\"lite-auth-card\" style={styles.card}>\n {error && (\n <div className=\"lite-auth-error\" style={styles.error}>{error}</div>\n )}\n\n <form onSubmit={handleSubmit} style={{ display: \"flex\", flexDirection: \"column\", gap: \"16px\" }}>\n <div style={styles.field}>\n <label htmlFor=\"email\" className=\"lite-auth-label\" style={styles.label}>Email</label>\n <input\n id=\"email\"\n name=\"email\"\n type=\"email\"\n required\n autoComplete=\"email\"\n placeholder=\"you@example.com\"\n className=\"lite-auth-input\"\n style={{\n ...styles.input,\n ...(focusedField === \"email\" ? styles.inputFocus : {}),\n }}\n onFocus={() => setFocusedField(\"email\")}\n onBlur={() => setFocusedField(null)}\n />\n </div>\n\n <div style={styles.field}>\n <label htmlFor=\"password\" className=\"lite-auth-label\" style={styles.label}>Password</label>\n <input\n id=\"password\"\n name=\"password\"\n type=\"password\"\n required\n autoComplete=\"current-password\"\n placeholder=\"••••••••\"\n className=\"lite-auth-input\"\n style={{\n ...styles.input,\n ...(focusedField === \"password\" ? styles.inputFocus : {}),\n }}\n onFocus={() => setFocusedField(\"password\")}\n onBlur={() => setFocusedField(null)}\n />\n </div>\n\n <button\n type=\"submit\"\n disabled={loading}\n className=\"lite-auth-btn\"\n style={{\n ...styles.button,\n ...(loading ? styles.buttonDisabled : {}),\n }}\n >\n {loading ? \"Signing in…\" : \"Sign in\"}\n </button>\n </form>\n </div>\n\n <p className=\"lite-auth-footer\" style={styles.footer}>\n Powered by{\" \"}\n <a\n href=\"https://github.com/amide-init/next-lite-auth\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"lite-auth-link\"\n style={styles.link}\n >\n next-lite-auth\n </a>\n </p>\n </div>\n </div>\n </>\n );\n}\n","export function matchesProtect(patterns: (string | RegExp)[], pathname: string): boolean {\n return patterns.some((p) =>\n p instanceof RegExp\n ? p.test(pathname)\n : p === \"/\" || pathname === p || pathname.startsWith(p + \"/\")\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEA,IAAAA,gBAAuF;AACvF,wBAA4B;;;ACD5B,mBAAmD;AA2L/C;AAlLJ,IAAM,SAAwC;AAAA,EAC5C,MAAM;AAAA,IACJ,WAAW;AAAA,IACX,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,SAAS;AAAA,IACT,YAAY;AAAA,EACd;AAAA,EACA,WAAW;AAAA,IACT,OAAO;AAAA,IACP,UAAU;AAAA,IACV,SAAS;AAAA,IACT,eAAe;AAAA,IACf,KAAK;AAAA,EACP;AAAA,EACA,QAAQ;AAAA,IACN,WAAW;AAAA,EACb;AAAA,EACA,SAAS;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,eAAe;AAAA,IACf,eAAe;AAAA,EACjB;AAAA,EACA,OAAO;AAAA,IACL,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,eAAe;AAAA,EACjB;AAAA,EACA,aAAa;AAAA,IACX,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,OAAO;AAAA,EACT;AAAA,EACA,MAAM;AAAA,IACJ,iBAAiB;AAAA,IACjB,QAAQ;AAAA,IACR,cAAc;AAAA,IACd,SAAS;AAAA,IACT,SAAS;AAAA,IACT,eAAe;AAAA,IACf,KAAK;AAAA,IACL,WAAW;AAAA,EACb;AAAA,EACA,OAAO;AAAA,IACL,iBAAiB;AAAA,IACjB,QAAQ;AAAA,IACR,cAAc;AAAA,IACd,SAAS;AAAA,IACT,UAAU;AAAA,IACV,OAAO;AAAA,EACT;AAAA,EACA,OAAO;AAAA,IACL,SAAS;AAAA,IACT,eAAe;AAAA,IACf,KAAK;AAAA,EACP;AAAA,EACA,OAAO;AAAA,IACL,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,EACT;AAAA,EACA,OAAO;AAAA,IACL,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,cAAc;AAAA,IACd,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,IACV,OAAO;AAAA,IACP,iBAAiB;AAAA,IACjB,SAAS;AAAA,IACT,WAAW;AAAA,IACX,YAAY;AAAA,EACd;AAAA,EACA,YAAY;AAAA,IACV,QAAQ;AAAA,IACR,WAAW;AAAA,EACb;AAAA,EACA,QAAQ;AAAA,IACN,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,cAAc;AAAA,IACd,QAAQ;AAAA,IACR,iBAAiB;AAAA,IACjB,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,YAAY;AAAA,EACd;AAAA,EACA,gBAAgB;AAAA,IACd,SAAS;AAAA,IACT,eAAe;AAAA,EACjB;AAAA,EACA,QAAQ;AAAA,IACN,WAAW;AAAA,IACX,UAAU;AAAA,IACV,OAAO;AAAA,EACT;AAAA,EACA,MAAM;AAAA,IACJ,OAAO;AAAA,IACP,gBAAgB;AAAA,IAChB,qBAAqB;AAAA,EACvB;AACF;AAEA,IAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoCb,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA,QAAQ;AAAA,EACR,cAAc;AAChB,GAAuB;AACrB,QAAM,EAAE,MAAM,IAAI,YAAY;AAC9B,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAS,EAAE;AACrC,QAAM,CAAC,SAAS,UAAU,QAAI,uBAAS,KAAK;AAC5C,QAAM,CAAC,cAAc,eAAe,QAAI,uBAAwB,IAAI;AAEpE,iBAAe,aAAa,GAA+B;AACzD,MAAE,eAAe;AACjB,aAAS,EAAE;AACX,eAAW,IAAI;AAEf,UAAM,OAAO,EAAE;AACf,UAAM,SAAS,MAAM,MAAM;AAAA,MACzB,OAAQ,KAAK,SAAS,UAAU,OAAO,EAAuB;AAAA,MAC9D,UAAW,KAAK,SAAS,UAAU,UAAU,EAAuB;AAAA,IACtE,CAAC;AAED,eAAW,KAAK;AAEhB,QAAI,OAAO,OAAO;AAChB,eAAS,OAAO,KAAK;AAAA,IACvB;AAAA,EACF;AAEA,SACE,4EACE;AAAA,gDAAC,WAAO,uBAAY;AAAA,IACpB,4CAAC,SAAI,WAAU,kBAAiB,OAAO,OAAO,MAC5C,uDAAC,SAAI,OAAO,OAAO,WACjB;AAAA,mDAAC,SAAI,OAAO,OAAO,QAChB;AAAA,mBAAW,4CAAC,OAAE,WAAU,qBAAoB,OAAO,OAAO,SAAU,mBAAQ;AAAA,QAC7E,4CAAC,QAAG,WAAU,mBAAkB,OAAO,OAAO,OAAQ,iBAAM;AAAA,QAC5D,4CAAC,OAAE,WAAU,kBAAiB,OAAO,OAAO,aAAc,uBAAY;AAAA,SACxE;AAAA,MAEA,6CAAC,SAAI,WAAU,kBAAiB,OAAO,OAAO,MAC3C;AAAA,iBACC,4CAAC,SAAI,WAAU,mBAAkB,OAAO,OAAO,OAAQ,iBAAM;AAAA,QAG/D,6CAAC,UAAK,UAAU,cAAc,OAAO,EAAE,SAAS,QAAQ,eAAe,UAAU,KAAK,OAAO,GAC3F;AAAA,uDAAC,SAAI,OAAO,OAAO,OACjB;AAAA,wDAAC,WAAM,SAAQ,SAAQ,WAAU,mBAAkB,OAAO,OAAO,OAAO,mBAAK;AAAA,YAC7E;AAAA,cAAC;AAAA;AAAA,gBACC,IAAG;AAAA,gBACH,MAAK;AAAA,gBACL,MAAK;AAAA,gBACL,UAAQ;AAAA,gBACR,cAAa;AAAA,gBACb,aAAY;AAAA,gBACZ,WAAU;AAAA,gBACV,OAAO;AAAA,kBACL,GAAG,OAAO;AAAA,kBACV,GAAI,iBAAiB,UAAU,OAAO,aAAa,CAAC;AAAA,gBACtD;AAAA,gBACA,SAAS,MAAM,gBAAgB,OAAO;AAAA,gBACtC,QAAQ,MAAM,gBAAgB,IAAI;AAAA;AAAA,YACpC;AAAA,aACF;AAAA,UAEA,6CAAC,SAAI,OAAO,OAAO,OACjB;AAAA,wDAAC,WAAM,SAAQ,YAAW,WAAU,mBAAkB,OAAO,OAAO,OAAO,sBAAQ;AAAA,YACnF;AAAA,cAAC;AAAA;AAAA,gBACC,IAAG;AAAA,gBACH,MAAK;AAAA,gBACL,MAAK;AAAA,gBACL,UAAQ;AAAA,gBACR,cAAa;AAAA,gBACb,aAAY;AAAA,gBACZ,WAAU;AAAA,gBACV,OAAO;AAAA,kBACL,GAAG,OAAO;AAAA,kBACV,GAAI,iBAAiB,aAAa,OAAO,aAAa,CAAC;AAAA,gBACzD;AAAA,gBACA,SAAS,MAAM,gBAAgB,UAAU;AAAA,gBACzC,QAAQ,MAAM,gBAAgB,IAAI;AAAA;AAAA,YACpC;AAAA,aACF;AAAA,UAEA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,UAAU;AAAA,cACV,WAAU;AAAA,cACV,OAAO;AAAA,gBACL,GAAG,OAAO;AAAA,gBACV,GAAI,UAAU,OAAO,iBAAiB,CAAC;AAAA,cACzC;AAAA,cAEC,oBAAU,qBAAgB;AAAA;AAAA,UAC7B;AAAA,WACF;AAAA,SACF;AAAA,MAEA,6CAAC,OAAE,WAAU,oBAAmB,OAAO,OAAO,QAAQ;AAAA;AAAA,QACzC;AAAA,QACX;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,QAAO;AAAA,YACP,KAAI;AAAA,YACJ,WAAU;AAAA,YACV,OAAO,OAAO;AAAA,YACf;AAAA;AAAA,QAED;AAAA,SACF;AAAA,OACF,GACF;AAAA,KACF;AAEJ;;;ACjRO,SAAS,eAAe,UAA+B,UAA2B;AACvF,SAAO,SAAS;AAAA,IAAK,CAAC,MACpB,aAAa,SACT,EAAE,KAAK,QAAQ,IACf,MAAM,OAAO,aAAa,KAAK,SAAS,WAAW,IAAI,GAAG;AAAA,EAChE;AACF;;;AFkEM,IAAAC,sBAAA;AAzDN,IAAM,sBAAkB,6BAA2C,IAAI;AAWhE,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA,UAAU,CAAC;AAAA,EACX;AAAA,EACA,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,SAAS;AACX,GAA0B;AACxB,QAAM,CAAC,MAAM,OAAO,QAAI,wBAA4B,IAAI;AACxD,QAAM,CAAC,SAAS,UAAU,QAAI,wBAAS,IAAI;AAC3C,QAAM,eAAW,+BAAY;AAE7B,+BAAU,MAAM;AACd,UAAM,MAAM,EACT,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,EACpB,KAAK,CAAC,EAAE,MAAAC,MAAK,MAAM,QAAQA,SAAQ,IAAI,CAAC,EACxC,MAAM,MAAM,QAAQ,IAAI,CAAC,EACzB,QAAQ,MAAM,WAAW,KAAK,CAAC;AAAA,EACpC,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,YAAQ;AAAA,IACZ,OAAO,EAAE,OAAO,SAAS,MAA2C;AAClE,YAAM,MAAM,MAAM,MAAM,WAAW;AAAA,QACjC,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,OAAO,SAAS,CAAC;AAAA,MAC1C,CAAC;AACD,YAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,UAAI,CAAC,IAAI,GAAI,QAAO,EAAE,OAAO,KAAK,SAAS,eAAe;AAC1D,cAAQ,KAAK,IAAI;AACjB,aAAO,CAAC;AAAA,IACV;AAAA,IACA,CAAC,SAAS;AAAA,EACZ;AAEA,QAAM,aAAS,2BAAY,YAAY;AACrC,UAAM,MAAM,YAAY,EAAE,QAAQ,OAAO,CAAC;AAC1C,YAAQ,IAAI;AAAA,EACd,GAAG,CAAC,UAAU,CAAC;AAEf,QAAM,QAAQ,EAAE,MAAM,SAAS,OAAO,OAAO;AAE7C,QAAM,cAAc,eAAe,SAAS,QAAQ;AAEpD,MAAI,SAAS;AACX,WACE,6CAAC,gBAAgB,UAAhB,EAAyB,OACvB,UACH;AAAA,EAEJ;AAEA,MAAI,CAAC,QAAQ,aAAa;AACxB,WACE,6CAAC,gBAAgB,UAAhB,EAAyB,OACxB,uDAAC,iBAAc,SAAkB,GACnC;AAAA,EAEJ;AAEA,SACE,6CAAC,gBAAgB,UAAhB,EAAyB,OACvB,UACH;AAEJ;AAEO,SAAS,cAAoC;AAClD,QAAM,UAAM,0BAAW,eAAe;AACtC,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,oDAAoD;AAC9E,SAAO;AACT;","names":["import_react","import_jsx_runtime","user"]}
1
+ {"version":3,"sources":["../src/client/index.ts","../src/client/LiteAuthProvider.tsx","../src/client/LiteLoginPage.tsx","../src/core/matchesProtect.ts"],"sourcesContent":["export { LiteAuthProvider, useLiteAuth } from \"./LiteAuthProvider\";\nexport { LiteLoginPage } from \"./LiteLoginPage\";\n","\"use client\";\n\nimport { createContext, useContext, useState, useEffect, useCallback, ReactNode } from \"react\";\nimport { usePathname } from \"next/navigation\";\nimport { PublicUser } from \"../core/types\";\nimport { LiteLoginPage } from \"./LiteLoginPage\";\nimport { matchesProtect } from \"../core/matchesProtect\";\n\ntype LiteAuthContextValue = {\n user: PublicUser | null;\n loading: boolean;\n login: (creds: { email: string; password: string }) => Promise<{ error?: string }>;\n logout: () => Promise<void>;\n};\n\nconst LiteAuthContext = createContext<LiteAuthContextValue | null>(null);\n\ntype LiteAuthProviderProps = {\n children: ReactNode;\n protect?: (string | RegExp)[];\n appName?: string;\n loginPath?: string;\n logoutPath?: string;\n mePath?: string;\n};\n\nexport function LiteAuthProvider({\n children,\n protect = [],\n appName,\n loginPath = \"/api/auth/login\",\n logoutPath = \"/api/auth/logout\",\n mePath = \"/api/auth/me\",\n}: LiteAuthProviderProps) {\n const [user, setUser] = useState<PublicUser | null>(null);\n const [loading, setLoading] = useState(true);\n const pathname = usePathname();\n\n useEffect(() => {\n fetch(mePath)\n .then((r) => r.json())\n .then(({ user }) => setUser(user ?? null))\n .catch(() => setUser(null))\n .finally(() => setLoading(false));\n }, [mePath]);\n\n const login = useCallback(\n async ({ email, password }: { email: string; password: string }) => {\n const res = await fetch(loginPath, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ email, password }),\n });\n const data = await res.json();\n if (!res.ok) return { error: data.error ?? \"Login failed\" };\n setUser(data.user);\n return {};\n },\n [loginPath]\n );\n\n const logout = useCallback(async () => {\n await fetch(logoutPath, { method: \"POST\" });\n setUser(null);\n }, [logoutPath]);\n\n const value = { user, loading, login, logout };\n\n const isProtected = matchesProtect(protect, pathname);\n\n if (loading) {\n return (\n <LiteAuthContext.Provider value={value}>\n {children}\n </LiteAuthContext.Provider>\n );\n }\n\n if (!user && isProtected) {\n return (\n <LiteAuthContext.Provider value={value}>\n <LiteLoginPage appName={appName} />\n </LiteAuthContext.Provider>\n );\n }\n\n return (\n <LiteAuthContext.Provider value={value}>\n {children}\n </LiteAuthContext.Provider>\n );\n}\n\nexport function useLiteAuth(): LiteAuthContextValue {\n const ctx = useContext(LiteAuthContext);\n if (!ctx) throw new Error(\"useLiteAuth must be used inside <LiteAuthProvider>\");\n return ctx;\n}\n","\"use client\";\n\nimport { CSSProperties, FormEvent, useState } from \"react\";\nimport { useLiteAuth } from \"./LiteAuthProvider\";\n\ntype LiteLoginPageProps = {\n appName?: string;\n title?: string;\n description?: string;\n};\n\nconst styles: Record<string, CSSProperties> = {\n page: {\n minHeight: \"100vh\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n backgroundColor: \"#f9fafb\",\n padding: \"0 16px\",\n fontFamily: \"system-ui, -apple-system, sans-serif\",\n },\n container: {\n width: \"100%\",\n maxWidth: \"360px\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"24px\",\n },\n header: {\n textAlign: \"center\",\n },\n appName: {\n margin: \"0 0 8px\",\n fontSize: \"13px\",\n fontWeight: 600,\n color: \"#6366f1\",\n textTransform: \"uppercase\" as const,\n letterSpacing: \"0.08em\",\n },\n title: {\n margin: \"0 0 4px\",\n fontSize: \"22px\",\n fontWeight: 600,\n color: \"#111827\",\n letterSpacing: \"-0.01em\",\n },\n description: {\n margin: 0,\n fontSize: \"14px\",\n color: \"#6b7280\",\n },\n card: {\n backgroundColor: \"#ffffff\",\n border: \"1px solid #e5e7eb\",\n borderRadius: \"12px\",\n padding: \"24px\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"16px\",\n boxShadow: \"0 1px 3px rgba(0,0,0,0.07)\",\n },\n error: {\n backgroundColor: \"#fef2f2\",\n border: \"1px solid #fecaca\",\n borderRadius: \"6px\",\n padding: \"8px 12px\",\n fontSize: \"13px\",\n color: \"#dc2626\",\n },\n field: {\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"6px\",\n },\n label: {\n fontSize: \"13px\",\n fontWeight: 500,\n color: \"#374151\",\n },\n input: {\n height: \"36px\",\n width: \"100%\",\n borderRadius: \"6px\",\n border: \"1px solid #d1d5db\",\n padding: \"0 12px\",\n fontSize: \"14px\",\n color: \"#111827\",\n backgroundColor: \"#fff\",\n outline: \"none\",\n boxSizing: \"border-box\",\n transition: \"border-color 0.15s\",\n },\n inputFocus: {\n border: \"1px solid #6366f1\",\n boxShadow: \"0 0 0 3px rgba(99,102,241,0.15)\",\n },\n passwordWrapper: {\n position: \"relative\",\n display: \"flex\",\n alignItems: \"center\",\n },\n passwordToggle: {\n position: \"absolute\",\n right: \"10px\",\n background: \"none\",\n border: \"none\",\n cursor: \"pointer\",\n padding: \"0\",\n display: \"flex\",\n alignItems: \"center\",\n color: \"#9ca3af\",\n },\n button: {\n height: \"36px\",\n width: \"100%\",\n borderRadius: \"6px\",\n border: \"none\",\n backgroundColor: \"#111827\",\n color: \"#ffffff\",\n fontSize: \"14px\",\n fontWeight: 500,\n cursor: \"pointer\",\n transition: \"opacity 0.15s\",\n },\n buttonDisabled: {\n opacity: 0.5,\n pointerEvents: \"none\",\n },\n footer: {\n textAlign: \"center\",\n fontSize: \"12px\",\n color: \"#9ca3af\",\n },\n link: {\n color: \"#6b7280\",\n textDecoration: \"underline\",\n textUnderlineOffset: \"3px\",\n },\n};\n\nconst darkModeCSS = `\n @media (prefers-color-scheme: dark) {\n .lite-auth-page { background-color: #09090b !important; }\n .lite-auth-appname { color: #818cf8 !important; }\n .lite-auth-title { color: #fafafa !important; }\n .lite-auth-desc { color: #a1a1aa !important; }\n .lite-auth-card {\n background-color: #18181b !important;\n border-color: #27272a !important;\n box-shadow: none !important;\n }\n .lite-auth-error {\n background-color: rgba(239,68,68,0.1) !important;\n border-color: rgba(239,68,68,0.3) !important;\n color: #f87171 !important;\n }\n .lite-auth-label { color: #e4e4e7 !important; }\n .lite-auth-input {\n background-color: transparent !important;\n border-color: #3f3f46 !important;\n color: #fafafa !important;\n }\n .lite-auth-input::placeholder { color: #52525b !important; }\n .lite-auth-input:focus {\n border-color: #6366f1 !important;\n box-shadow: 0 0 0 3px rgba(99,102,241,0.2) !important;\n }\n .lite-auth-btn {\n background-color: #fafafa !important;\n color: #09090b !important;\n }\n .lite-auth-footer { color: #52525b !important; }\n .lite-auth-link { color: #71717a !important; }\n .lite-auth-pw-toggle { color: #52525b !important; }\n }\n`;\n\nexport function LiteLoginPage({\n appName,\n title = \"Sign in\",\n description = \"Enter your credentials to continue\",\n}: LiteLoginPageProps) {\n const { login } = useLiteAuth();\n const [error, setError] = useState(\"\");\n const [loading, setLoading] = useState(false);\n const [focusedField, setFocusedField] = useState<string | null>(null);\n const [showPassword, setShowPassword] = useState(false);\n\n async function handleSubmit(e: FormEvent<HTMLFormElement>) {\n e.preventDefault();\n setError(\"\");\n setLoading(true);\n\n const form = e.currentTarget;\n const result = await login({\n email: (form.elements.namedItem(\"email\") as HTMLInputElement).value,\n password: (form.elements.namedItem(\"password\") as HTMLInputElement).value,\n });\n\n setLoading(false);\n\n if (result.error) {\n setError(result.error);\n }\n }\n\n return (\n <>\n <style>{darkModeCSS}</style>\n <div className=\"lite-auth-page\" style={styles.page}>\n <div style={styles.container}>\n <div style={styles.header}>\n {appName && <p className=\"lite-auth-appname\" style={styles.appName}>{appName}</p>}\n <h1 className=\"lite-auth-title\" style={styles.title}>{title}</h1>\n <p className=\"lite-auth-desc\" style={styles.description}>{description}</p>\n </div>\n\n <div className=\"lite-auth-card\" style={styles.card}>\n {error && (\n <div className=\"lite-auth-error\" style={styles.error}>{error}</div>\n )}\n\n <form onSubmit={handleSubmit} style={{ display: \"flex\", flexDirection: \"column\", gap: \"16px\" }}>\n <div style={styles.field}>\n <label htmlFor=\"email\" className=\"lite-auth-label\" style={styles.label}>Email</label>\n <input\n id=\"email\"\n name=\"email\"\n type=\"email\"\n required\n autoComplete=\"email\"\n placeholder=\"you@example.com\"\n className=\"lite-auth-input\"\n style={{\n ...styles.input,\n ...(focusedField === \"email\" ? styles.inputFocus : {}),\n }}\n onFocus={() => setFocusedField(\"email\")}\n onBlur={() => setFocusedField(null)}\n />\n </div>\n\n <div style={styles.field}>\n <label htmlFor=\"password\" className=\"lite-auth-label\" style={styles.label}>Password</label>\n <div style={styles.passwordWrapper}>\n <input\n id=\"password\"\n name=\"password\"\n type={showPassword ? \"text\" : \"password\"}\n required\n autoComplete=\"current-password\"\n placeholder=\"••••••••\"\n className=\"lite-auth-input\"\n style={{\n ...styles.input,\n paddingRight: \"36px\",\n ...(focusedField === \"password\" ? styles.inputFocus : {}),\n }}\n onFocus={() => setFocusedField(\"password\")}\n onBlur={() => setFocusedField(null)}\n />\n <button\n type=\"button\"\n onClick={() => setShowPassword((v) => !v)}\n className=\"lite-auth-pw-toggle\"\n style={styles.passwordToggle}\n aria-label={showPassword ? \"Hide password\" : \"Show password\"}\n tabIndex={-1}\n >\n {showPassword ? (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94\"/>\n <path d=\"M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19\"/>\n <line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\"/>\n </svg>\n ) : (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"/>\n <circle cx=\"12\" cy=\"12\" r=\"3\"/>\n </svg>\n )}\n </button>\n </div>\n </div>\n\n <button\n type=\"submit\"\n disabled={loading}\n className=\"lite-auth-btn\"\n style={{\n ...styles.button,\n ...(loading ? styles.buttonDisabled : {}),\n }}\n >\n {loading ? \"Signing in…\" : \"Sign in\"}\n </button>\n </form>\n </div>\n\n <p className=\"lite-auth-footer\" style={styles.footer}>\n Powered by{\" \"}\n <a\n href=\"https://github.com/amide-init/next-lite-auth\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"lite-auth-link\"\n style={styles.link}\n >\n next-lite-auth\n </a>\n </p>\n </div>\n </div>\n </>\n );\n}\n","export function matchesProtect(patterns: (string | RegExp)[], pathname: string): boolean {\n return patterns.some((p) =>\n p instanceof RegExp\n ? p.test(pathname)\n : p === \"/\" || pathname === p || pathname.startsWith(p + \"/\")\n );\n}\n"],"mappings":"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","names":["import_react","import_jsx_runtime","user"]}
package/dist/client.mjs CHANGED
@@ -90,6 +90,22 @@ var styles = {
90
90
  border: "1px solid #6366f1",
91
91
  boxShadow: "0 0 0 3px rgba(99,102,241,0.15)"
92
92
  },
93
+ passwordWrapper: {
94
+ position: "relative",
95
+ display: "flex",
96
+ alignItems: "center"
97
+ },
98
+ passwordToggle: {
99
+ position: "absolute",
100
+ right: "10px",
101
+ background: "none",
102
+ border: "none",
103
+ cursor: "pointer",
104
+ padding: "0",
105
+ display: "flex",
106
+ alignItems: "center",
107
+ color: "#9ca3af"
108
+ },
93
109
  button: {
94
110
  height: "36px",
95
111
  width: "100%",
@@ -150,6 +166,7 @@ var darkModeCSS = `
150
166
  }
151
167
  .lite-auth-footer { color: #52525b !important; }
152
168
  .lite-auth-link { color: #71717a !important; }
169
+ .lite-auth-pw-toggle { color: #52525b !important; }
153
170
  }
154
171
  `;
155
172
  function LiteLoginPage({
@@ -161,6 +178,7 @@ function LiteLoginPage({
161
178
  const [error, setError] = useState("");
162
179
  const [loading, setLoading] = useState(false);
163
180
  const [focusedField, setFocusedField] = useState(null);
181
+ const [showPassword, setShowPassword] = useState(false);
164
182
  async function handleSubmit(e) {
165
183
  e.preventDefault();
166
184
  setError("");
@@ -209,24 +227,46 @@ function LiteLoginPage({
209
227
  ] }),
210
228
  /* @__PURE__ */ jsxs("div", { style: styles.field, children: [
211
229
  /* @__PURE__ */ jsx("label", { htmlFor: "password", className: "lite-auth-label", style: styles.label, children: "Password" }),
212
- /* @__PURE__ */ jsx(
213
- "input",
214
- {
215
- id: "password",
216
- name: "password",
217
- type: "password",
218
- required: true,
219
- autoComplete: "current-password",
220
- placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
221
- className: "lite-auth-input",
222
- style: {
223
- ...styles.input,
224
- ...focusedField === "password" ? styles.inputFocus : {}
225
- },
226
- onFocus: () => setFocusedField("password"),
227
- onBlur: () => setFocusedField(null)
228
- }
229
- )
230
+ /* @__PURE__ */ jsxs("div", { style: styles.passwordWrapper, children: [
231
+ /* @__PURE__ */ jsx(
232
+ "input",
233
+ {
234
+ id: "password",
235
+ name: "password",
236
+ type: showPassword ? "text" : "password",
237
+ required: true,
238
+ autoComplete: "current-password",
239
+ placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
240
+ className: "lite-auth-input",
241
+ style: {
242
+ ...styles.input,
243
+ paddingRight: "36px",
244
+ ...focusedField === "password" ? styles.inputFocus : {}
245
+ },
246
+ onFocus: () => setFocusedField("password"),
247
+ onBlur: () => setFocusedField(null)
248
+ }
249
+ ),
250
+ /* @__PURE__ */ jsx(
251
+ "button",
252
+ {
253
+ type: "button",
254
+ onClick: () => setShowPassword((v) => !v),
255
+ className: "lite-auth-pw-toggle",
256
+ style: styles.passwordToggle,
257
+ "aria-label": showPassword ? "Hide password" : "Show password",
258
+ tabIndex: -1,
259
+ children: showPassword ? /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
260
+ /* @__PURE__ */ jsx("path", { d: "M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94" }),
261
+ /* @__PURE__ */ jsx("path", { d: "M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19" }),
262
+ /* @__PURE__ */ jsx("line", { x1: "1", y1: "1", x2: "23", y2: "23" })
263
+ ] }) : /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
264
+ /* @__PURE__ */ jsx("path", { d: "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" }),
265
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" })
266
+ ] })
267
+ }
268
+ )
269
+ ] })
230
270
  ] }),
231
271
  /* @__PURE__ */ jsx(
232
272
  "button",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client/LiteAuthProvider.tsx","../src/client/LiteLoginPage.tsx","../src/core/matchesProtect.ts"],"sourcesContent":["\"use client\";\n\nimport { createContext, useContext, useState, useEffect, useCallback, ReactNode } from \"react\";\nimport { usePathname } from \"next/navigation\";\nimport { PublicUser } from \"../core/types\";\nimport { LiteLoginPage } from \"./LiteLoginPage\";\nimport { matchesProtect } from \"../core/matchesProtect\";\n\ntype LiteAuthContextValue = {\n user: PublicUser | null;\n loading: boolean;\n login: (creds: { email: string; password: string }) => Promise<{ error?: string }>;\n logout: () => Promise<void>;\n};\n\nconst LiteAuthContext = createContext<LiteAuthContextValue | null>(null);\n\ntype LiteAuthProviderProps = {\n children: ReactNode;\n protect?: (string | RegExp)[];\n appName?: string;\n loginPath?: string;\n logoutPath?: string;\n mePath?: string;\n};\n\nexport function LiteAuthProvider({\n children,\n protect = [],\n appName,\n loginPath = \"/api/auth/login\",\n logoutPath = \"/api/auth/logout\",\n mePath = \"/api/auth/me\",\n}: LiteAuthProviderProps) {\n const [user, setUser] = useState<PublicUser | null>(null);\n const [loading, setLoading] = useState(true);\n const pathname = usePathname();\n\n useEffect(() => {\n fetch(mePath)\n .then((r) => r.json())\n .then(({ user }) => setUser(user ?? null))\n .catch(() => setUser(null))\n .finally(() => setLoading(false));\n }, [mePath]);\n\n const login = useCallback(\n async ({ email, password }: { email: string; password: string }) => {\n const res = await fetch(loginPath, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ email, password }),\n });\n const data = await res.json();\n if (!res.ok) return { error: data.error ?? \"Login failed\" };\n setUser(data.user);\n return {};\n },\n [loginPath]\n );\n\n const logout = useCallback(async () => {\n await fetch(logoutPath, { method: \"POST\" });\n setUser(null);\n }, [logoutPath]);\n\n const value = { user, loading, login, logout };\n\n const isProtected = matchesProtect(protect, pathname);\n\n if (loading) {\n return (\n <LiteAuthContext.Provider value={value}>\n {children}\n </LiteAuthContext.Provider>\n );\n }\n\n if (!user && isProtected) {\n return (\n <LiteAuthContext.Provider value={value}>\n <LiteLoginPage appName={appName} />\n </LiteAuthContext.Provider>\n );\n }\n\n return (\n <LiteAuthContext.Provider value={value}>\n {children}\n </LiteAuthContext.Provider>\n );\n}\n\nexport function useLiteAuth(): LiteAuthContextValue {\n const ctx = useContext(LiteAuthContext);\n if (!ctx) throw new Error(\"useLiteAuth must be used inside <LiteAuthProvider>\");\n return ctx;\n}\n","\"use client\";\n\nimport { CSSProperties, FormEvent, useState } from \"react\";\nimport { useLiteAuth } from \"./LiteAuthProvider\";\n\ntype LiteLoginPageProps = {\n appName?: string;\n title?: string;\n description?: string;\n};\n\nconst styles: Record<string, CSSProperties> = {\n page: {\n minHeight: \"100vh\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n backgroundColor: \"#f9fafb\",\n padding: \"0 16px\",\n fontFamily: \"system-ui, -apple-system, sans-serif\",\n },\n container: {\n width: \"100%\",\n maxWidth: \"360px\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"24px\",\n },\n header: {\n textAlign: \"center\",\n },\n appName: {\n margin: \"0 0 8px\",\n fontSize: \"13px\",\n fontWeight: 600,\n color: \"#6366f1\",\n textTransform: \"uppercase\" as const,\n letterSpacing: \"0.08em\",\n },\n title: {\n margin: \"0 0 4px\",\n fontSize: \"22px\",\n fontWeight: 600,\n color: \"#111827\",\n letterSpacing: \"-0.01em\",\n },\n description: {\n margin: 0,\n fontSize: \"14px\",\n color: \"#6b7280\",\n },\n card: {\n backgroundColor: \"#ffffff\",\n border: \"1px solid #e5e7eb\",\n borderRadius: \"12px\",\n padding: \"24px\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"16px\",\n boxShadow: \"0 1px 3px rgba(0,0,0,0.07)\",\n },\n error: {\n backgroundColor: \"#fef2f2\",\n border: \"1px solid #fecaca\",\n borderRadius: \"6px\",\n padding: \"8px 12px\",\n fontSize: \"13px\",\n color: \"#dc2626\",\n },\n field: {\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"6px\",\n },\n label: {\n fontSize: \"13px\",\n fontWeight: 500,\n color: \"#374151\",\n },\n input: {\n height: \"36px\",\n width: \"100%\",\n borderRadius: \"6px\",\n border: \"1px solid #d1d5db\",\n padding: \"0 12px\",\n fontSize: \"14px\",\n color: \"#111827\",\n backgroundColor: \"#fff\",\n outline: \"none\",\n boxSizing: \"border-box\",\n transition: \"border-color 0.15s\",\n },\n inputFocus: {\n border: \"1px solid #6366f1\",\n boxShadow: \"0 0 0 3px rgba(99,102,241,0.15)\",\n },\n button: {\n height: \"36px\",\n width: \"100%\",\n borderRadius: \"6px\",\n border: \"none\",\n backgroundColor: \"#111827\",\n color: \"#ffffff\",\n fontSize: \"14px\",\n fontWeight: 500,\n cursor: \"pointer\",\n transition: \"opacity 0.15s\",\n },\n buttonDisabled: {\n opacity: 0.5,\n pointerEvents: \"none\",\n },\n footer: {\n textAlign: \"center\",\n fontSize: \"12px\",\n color: \"#9ca3af\",\n },\n link: {\n color: \"#6b7280\",\n textDecoration: \"underline\",\n textUnderlineOffset: \"3px\",\n },\n};\n\nconst darkModeCSS = `\n @media (prefers-color-scheme: dark) {\n .lite-auth-page { background-color: #09090b !important; }\n .lite-auth-appname { color: #818cf8 !important; }\n .lite-auth-title { color: #fafafa !important; }\n .lite-auth-desc { color: #a1a1aa !important; }\n .lite-auth-card {\n background-color: #18181b !important;\n border-color: #27272a !important;\n box-shadow: none !important;\n }\n .lite-auth-error {\n background-color: rgba(239,68,68,0.1) !important;\n border-color: rgba(239,68,68,0.3) !important;\n color: #f87171 !important;\n }\n .lite-auth-label { color: #e4e4e7 !important; }\n .lite-auth-input {\n background-color: transparent !important;\n border-color: #3f3f46 !important;\n color: #fafafa !important;\n }\n .lite-auth-input::placeholder { color: #52525b !important; }\n .lite-auth-input:focus {\n border-color: #6366f1 !important;\n box-shadow: 0 0 0 3px rgba(99,102,241,0.2) !important;\n }\n .lite-auth-btn {\n background-color: #fafafa !important;\n color: #09090b !important;\n }\n .lite-auth-footer { color: #52525b !important; }\n .lite-auth-link { color: #71717a !important; }\n }\n`;\n\nexport function LiteLoginPage({\n appName,\n title = \"Sign in\",\n description = \"Enter your credentials to continue\",\n}: LiteLoginPageProps) {\n const { login } = useLiteAuth();\n const [error, setError] = useState(\"\");\n const [loading, setLoading] = useState(false);\n const [focusedField, setFocusedField] = useState<string | null>(null);\n\n async function handleSubmit(e: FormEvent<HTMLFormElement>) {\n e.preventDefault();\n setError(\"\");\n setLoading(true);\n\n const form = e.currentTarget;\n const result = await login({\n email: (form.elements.namedItem(\"email\") as HTMLInputElement).value,\n password: (form.elements.namedItem(\"password\") as HTMLInputElement).value,\n });\n\n setLoading(false);\n\n if (result.error) {\n setError(result.error);\n }\n }\n\n return (\n <>\n <style>{darkModeCSS}</style>\n <div className=\"lite-auth-page\" style={styles.page}>\n <div style={styles.container}>\n <div style={styles.header}>\n {appName && <p className=\"lite-auth-appname\" style={styles.appName}>{appName}</p>}\n <h1 className=\"lite-auth-title\" style={styles.title}>{title}</h1>\n <p className=\"lite-auth-desc\" style={styles.description}>{description}</p>\n </div>\n\n <div className=\"lite-auth-card\" style={styles.card}>\n {error && (\n <div className=\"lite-auth-error\" style={styles.error}>{error}</div>\n )}\n\n <form onSubmit={handleSubmit} style={{ display: \"flex\", flexDirection: \"column\", gap: \"16px\" }}>\n <div style={styles.field}>\n <label htmlFor=\"email\" className=\"lite-auth-label\" style={styles.label}>Email</label>\n <input\n id=\"email\"\n name=\"email\"\n type=\"email\"\n required\n autoComplete=\"email\"\n placeholder=\"you@example.com\"\n className=\"lite-auth-input\"\n style={{\n ...styles.input,\n ...(focusedField === \"email\" ? styles.inputFocus : {}),\n }}\n onFocus={() => setFocusedField(\"email\")}\n onBlur={() => setFocusedField(null)}\n />\n </div>\n\n <div style={styles.field}>\n <label htmlFor=\"password\" className=\"lite-auth-label\" style={styles.label}>Password</label>\n <input\n id=\"password\"\n name=\"password\"\n type=\"password\"\n required\n autoComplete=\"current-password\"\n placeholder=\"••••••••\"\n className=\"lite-auth-input\"\n style={{\n ...styles.input,\n ...(focusedField === \"password\" ? styles.inputFocus : {}),\n }}\n onFocus={() => setFocusedField(\"password\")}\n onBlur={() => setFocusedField(null)}\n />\n </div>\n\n <button\n type=\"submit\"\n disabled={loading}\n className=\"lite-auth-btn\"\n style={{\n ...styles.button,\n ...(loading ? styles.buttonDisabled : {}),\n }}\n >\n {loading ? \"Signing in…\" : \"Sign in\"}\n </button>\n </form>\n </div>\n\n <p className=\"lite-auth-footer\" style={styles.footer}>\n Powered by{\" \"}\n <a\n href=\"https://github.com/amide-init/next-lite-auth\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"lite-auth-link\"\n style={styles.link}\n >\n next-lite-auth\n </a>\n </p>\n </div>\n </div>\n </>\n );\n}\n","export function matchesProtect(patterns: (string | RegExp)[], pathname: string): boolean {\n return patterns.some((p) =>\n p instanceof RegExp\n ? p.test(pathname)\n : p === \"/\" || pathname === p || pathname.startsWith(p + \"/\")\n );\n}\n"],"mappings":"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","names":["useState","jsx","useState","user"]}
1
+ {"version":3,"sources":["../src/client/LiteAuthProvider.tsx","../src/client/LiteLoginPage.tsx","../src/core/matchesProtect.ts"],"sourcesContent":["\"use client\";\n\nimport { createContext, useContext, useState, useEffect, useCallback, ReactNode } from \"react\";\nimport { usePathname } from \"next/navigation\";\nimport { PublicUser } from \"../core/types\";\nimport { LiteLoginPage } from \"./LiteLoginPage\";\nimport { matchesProtect } from \"../core/matchesProtect\";\n\ntype LiteAuthContextValue = {\n user: PublicUser | null;\n loading: boolean;\n login: (creds: { email: string; password: string }) => Promise<{ error?: string }>;\n logout: () => Promise<void>;\n};\n\nconst LiteAuthContext = createContext<LiteAuthContextValue | null>(null);\n\ntype LiteAuthProviderProps = {\n children: ReactNode;\n protect?: (string | RegExp)[];\n appName?: string;\n loginPath?: string;\n logoutPath?: string;\n mePath?: string;\n};\n\nexport function LiteAuthProvider({\n children,\n protect = [],\n appName,\n loginPath = \"/api/auth/login\",\n logoutPath = \"/api/auth/logout\",\n mePath = \"/api/auth/me\",\n}: LiteAuthProviderProps) {\n const [user, setUser] = useState<PublicUser | null>(null);\n const [loading, setLoading] = useState(true);\n const pathname = usePathname();\n\n useEffect(() => {\n fetch(mePath)\n .then((r) => r.json())\n .then(({ user }) => setUser(user ?? null))\n .catch(() => setUser(null))\n .finally(() => setLoading(false));\n }, [mePath]);\n\n const login = useCallback(\n async ({ email, password }: { email: string; password: string }) => {\n const res = await fetch(loginPath, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ email, password }),\n });\n const data = await res.json();\n if (!res.ok) return { error: data.error ?? \"Login failed\" };\n setUser(data.user);\n return {};\n },\n [loginPath]\n );\n\n const logout = useCallback(async () => {\n await fetch(logoutPath, { method: \"POST\" });\n setUser(null);\n }, [logoutPath]);\n\n const value = { user, loading, login, logout };\n\n const isProtected = matchesProtect(protect, pathname);\n\n if (loading) {\n return (\n <LiteAuthContext.Provider value={value}>\n {children}\n </LiteAuthContext.Provider>\n );\n }\n\n if (!user && isProtected) {\n return (\n <LiteAuthContext.Provider value={value}>\n <LiteLoginPage appName={appName} />\n </LiteAuthContext.Provider>\n );\n }\n\n return (\n <LiteAuthContext.Provider value={value}>\n {children}\n </LiteAuthContext.Provider>\n );\n}\n\nexport function useLiteAuth(): LiteAuthContextValue {\n const ctx = useContext(LiteAuthContext);\n if (!ctx) throw new Error(\"useLiteAuth must be used inside <LiteAuthProvider>\");\n return ctx;\n}\n","\"use client\";\n\nimport { CSSProperties, FormEvent, useState } from \"react\";\nimport { useLiteAuth } from \"./LiteAuthProvider\";\n\ntype LiteLoginPageProps = {\n appName?: string;\n title?: string;\n description?: string;\n};\n\nconst styles: Record<string, CSSProperties> = {\n page: {\n minHeight: \"100vh\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n backgroundColor: \"#f9fafb\",\n padding: \"0 16px\",\n fontFamily: \"system-ui, -apple-system, sans-serif\",\n },\n container: {\n width: \"100%\",\n maxWidth: \"360px\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"24px\",\n },\n header: {\n textAlign: \"center\",\n },\n appName: {\n margin: \"0 0 8px\",\n fontSize: \"13px\",\n fontWeight: 600,\n color: \"#6366f1\",\n textTransform: \"uppercase\" as const,\n letterSpacing: \"0.08em\",\n },\n title: {\n margin: \"0 0 4px\",\n fontSize: \"22px\",\n fontWeight: 600,\n color: \"#111827\",\n letterSpacing: \"-0.01em\",\n },\n description: {\n margin: 0,\n fontSize: \"14px\",\n color: \"#6b7280\",\n },\n card: {\n backgroundColor: \"#ffffff\",\n border: \"1px solid #e5e7eb\",\n borderRadius: \"12px\",\n padding: \"24px\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"16px\",\n boxShadow: \"0 1px 3px rgba(0,0,0,0.07)\",\n },\n error: {\n backgroundColor: \"#fef2f2\",\n border: \"1px solid #fecaca\",\n borderRadius: \"6px\",\n padding: \"8px 12px\",\n fontSize: \"13px\",\n color: \"#dc2626\",\n },\n field: {\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"6px\",\n },\n label: {\n fontSize: \"13px\",\n fontWeight: 500,\n color: \"#374151\",\n },\n input: {\n height: \"36px\",\n width: \"100%\",\n borderRadius: \"6px\",\n border: \"1px solid #d1d5db\",\n padding: \"0 12px\",\n fontSize: \"14px\",\n color: \"#111827\",\n backgroundColor: \"#fff\",\n outline: \"none\",\n boxSizing: \"border-box\",\n transition: \"border-color 0.15s\",\n },\n inputFocus: {\n border: \"1px solid #6366f1\",\n boxShadow: \"0 0 0 3px rgba(99,102,241,0.15)\",\n },\n passwordWrapper: {\n position: \"relative\",\n display: \"flex\",\n alignItems: \"center\",\n },\n passwordToggle: {\n position: \"absolute\",\n right: \"10px\",\n background: \"none\",\n border: \"none\",\n cursor: \"pointer\",\n padding: \"0\",\n display: \"flex\",\n alignItems: \"center\",\n color: \"#9ca3af\",\n },\n button: {\n height: \"36px\",\n width: \"100%\",\n borderRadius: \"6px\",\n border: \"none\",\n backgroundColor: \"#111827\",\n color: \"#ffffff\",\n fontSize: \"14px\",\n fontWeight: 500,\n cursor: \"pointer\",\n transition: \"opacity 0.15s\",\n },\n buttonDisabled: {\n opacity: 0.5,\n pointerEvents: \"none\",\n },\n footer: {\n textAlign: \"center\",\n fontSize: \"12px\",\n color: \"#9ca3af\",\n },\n link: {\n color: \"#6b7280\",\n textDecoration: \"underline\",\n textUnderlineOffset: \"3px\",\n },\n};\n\nconst darkModeCSS = `\n @media (prefers-color-scheme: dark) {\n .lite-auth-page { background-color: #09090b !important; }\n .lite-auth-appname { color: #818cf8 !important; }\n .lite-auth-title { color: #fafafa !important; }\n .lite-auth-desc { color: #a1a1aa !important; }\n .lite-auth-card {\n background-color: #18181b !important;\n border-color: #27272a !important;\n box-shadow: none !important;\n }\n .lite-auth-error {\n background-color: rgba(239,68,68,0.1) !important;\n border-color: rgba(239,68,68,0.3) !important;\n color: #f87171 !important;\n }\n .lite-auth-label { color: #e4e4e7 !important; }\n .lite-auth-input {\n background-color: transparent !important;\n border-color: #3f3f46 !important;\n color: #fafafa !important;\n }\n .lite-auth-input::placeholder { color: #52525b !important; }\n .lite-auth-input:focus {\n border-color: #6366f1 !important;\n box-shadow: 0 0 0 3px rgba(99,102,241,0.2) !important;\n }\n .lite-auth-btn {\n background-color: #fafafa !important;\n color: #09090b !important;\n }\n .lite-auth-footer { color: #52525b !important; }\n .lite-auth-link { color: #71717a !important; }\n .lite-auth-pw-toggle { color: #52525b !important; }\n }\n`;\n\nexport function LiteLoginPage({\n appName,\n title = \"Sign in\",\n description = \"Enter your credentials to continue\",\n}: LiteLoginPageProps) {\n const { login } = useLiteAuth();\n const [error, setError] = useState(\"\");\n const [loading, setLoading] = useState(false);\n const [focusedField, setFocusedField] = useState<string | null>(null);\n const [showPassword, setShowPassword] = useState(false);\n\n async function handleSubmit(e: FormEvent<HTMLFormElement>) {\n e.preventDefault();\n setError(\"\");\n setLoading(true);\n\n const form = e.currentTarget;\n const result = await login({\n email: (form.elements.namedItem(\"email\") as HTMLInputElement).value,\n password: (form.elements.namedItem(\"password\") as HTMLInputElement).value,\n });\n\n setLoading(false);\n\n if (result.error) {\n setError(result.error);\n }\n }\n\n return (\n <>\n <style>{darkModeCSS}</style>\n <div className=\"lite-auth-page\" style={styles.page}>\n <div style={styles.container}>\n <div style={styles.header}>\n {appName && <p className=\"lite-auth-appname\" style={styles.appName}>{appName}</p>}\n <h1 className=\"lite-auth-title\" style={styles.title}>{title}</h1>\n <p className=\"lite-auth-desc\" style={styles.description}>{description}</p>\n </div>\n\n <div className=\"lite-auth-card\" style={styles.card}>\n {error && (\n <div className=\"lite-auth-error\" style={styles.error}>{error}</div>\n )}\n\n <form onSubmit={handleSubmit} style={{ display: \"flex\", flexDirection: \"column\", gap: \"16px\" }}>\n <div style={styles.field}>\n <label htmlFor=\"email\" className=\"lite-auth-label\" style={styles.label}>Email</label>\n <input\n id=\"email\"\n name=\"email\"\n type=\"email\"\n required\n autoComplete=\"email\"\n placeholder=\"you@example.com\"\n className=\"lite-auth-input\"\n style={{\n ...styles.input,\n ...(focusedField === \"email\" ? styles.inputFocus : {}),\n }}\n onFocus={() => setFocusedField(\"email\")}\n onBlur={() => setFocusedField(null)}\n />\n </div>\n\n <div style={styles.field}>\n <label htmlFor=\"password\" className=\"lite-auth-label\" style={styles.label}>Password</label>\n <div style={styles.passwordWrapper}>\n <input\n id=\"password\"\n name=\"password\"\n type={showPassword ? \"text\" : \"password\"}\n required\n autoComplete=\"current-password\"\n placeholder=\"••••••••\"\n className=\"lite-auth-input\"\n style={{\n ...styles.input,\n paddingRight: \"36px\",\n ...(focusedField === \"password\" ? styles.inputFocus : {}),\n }}\n onFocus={() => setFocusedField(\"password\")}\n onBlur={() => setFocusedField(null)}\n />\n <button\n type=\"button\"\n onClick={() => setShowPassword((v) => !v)}\n className=\"lite-auth-pw-toggle\"\n style={styles.passwordToggle}\n aria-label={showPassword ? \"Hide password\" : \"Show password\"}\n tabIndex={-1}\n >\n {showPassword ? (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94\"/>\n <path d=\"M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19\"/>\n <line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\"/>\n </svg>\n ) : (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"/>\n <circle cx=\"12\" cy=\"12\" r=\"3\"/>\n </svg>\n )}\n </button>\n </div>\n </div>\n\n <button\n type=\"submit\"\n disabled={loading}\n className=\"lite-auth-btn\"\n style={{\n ...styles.button,\n ...(loading ? styles.buttonDisabled : {}),\n }}\n >\n {loading ? \"Signing in…\" : \"Sign in\"}\n </button>\n </form>\n </div>\n\n <p className=\"lite-auth-footer\" style={styles.footer}>\n Powered by{\" \"}\n <a\n href=\"https://github.com/amide-init/next-lite-auth\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"lite-auth-link\"\n style={styles.link}\n >\n next-lite-auth\n </a>\n </p>\n </div>\n </div>\n </>\n );\n}\n","export function matchesProtect(patterns: (string | RegExp)[], pathname: string): boolean {\n return patterns.some((p) =>\n p instanceof RegExp\n ? p.test(pathname)\n : p === \"/\" || pathname === p || pathname.startsWith(p + \"/\")\n );\n}\n"],"mappings":"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","names":["useState","jsx","useState","user"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-lite-auth",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Lightweight JWT auth for Next.js using static JSON users (no database)",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -20,6 +20,16 @@
20
20
  "files": [
21
21
  "dist"
22
22
  ],
23
+ "scripts": {
24
+ "build": "tsup",
25
+ "dev": "tsup --watch",
26
+ "test": "vitest run",
27
+ "typecheck": "tsc --noEmit",
28
+ "prepublishOnly": "pnpm build",
29
+ "docs:dev": "vitepress dev docs",
30
+ "docs:build": "vitepress build docs",
31
+ "docs:preview": "vitepress preview docs"
32
+ },
23
33
  "peerDependencies": {
24
34
  "next": ">=13.0.0",
25
35
  "react": ">=18.0.0"
@@ -41,6 +51,7 @@
41
51
  "node": ">=20.0.0",
42
52
  "pnpm": ">=9.0.0"
43
53
  },
54
+ "packageManager": "pnpm@9.0.0",
44
55
  "author": "amide-init",
45
56
  "repository": {
46
57
  "type": "git",
@@ -56,14 +67,5 @@
56
67
  "jwt",
57
68
  "lightweight"
58
69
  ],
59
- "license": "MIT",
60
- "scripts": {
61
- "build": "tsup",
62
- "dev": "tsup --watch",
63
- "test": "vitest run",
64
- "typecheck": "tsc --noEmit",
65
- "docs:dev": "vitepress dev docs",
66
- "docs:build": "vitepress build docs",
67
- "docs:preview": "vitepress preview docs"
68
- }
69
- }
70
+ "license": "MIT"
71
+ }