@marcwelti/mw-core 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,17 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface AuthDialogProps {
4
+ /** Whether the dialog is open. */
5
+ open: boolean;
6
+ /** Called when the dialog open state changes. */
7
+ onOpenChange: (open: boolean) => void;
8
+ /** Called after successful authentication. Use this to reload the session. */
9
+ onSuccess: () => void;
10
+ /** URL for the session creation endpoint. Defaults to '/api/auth/login'. */
11
+ sessionEndpoint?: string;
12
+ /** Default mode: 'login' or 'register'. Defaults to 'login'. */
13
+ defaultMode?: 'login' | 'register';
14
+ }
15
+ declare function AuthDialog({ open, onOpenChange, onSuccess, sessionEndpoint, defaultMode, }: AuthDialogProps): react_jsx_runtime.JSX.Element;
16
+
17
+ export { AuthDialog, type AuthDialogProps };
@@ -0,0 +1,17 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface AuthDialogProps {
4
+ /** Whether the dialog is open. */
5
+ open: boolean;
6
+ /** Called when the dialog open state changes. */
7
+ onOpenChange: (open: boolean) => void;
8
+ /** Called after successful authentication. Use this to reload the session. */
9
+ onSuccess: () => void;
10
+ /** URL for the session creation endpoint. Defaults to '/api/auth/login'. */
11
+ sessionEndpoint?: string;
12
+ /** Default mode: 'login' or 'register'. Defaults to 'login'. */
13
+ defaultMode?: 'login' | 'register';
14
+ }
15
+ declare function AuthDialog({ open, onOpenChange, onSuccess, sessionEndpoint, defaultMode, }: AuthDialogProps): react_jsx_runtime.JSX.Element;
16
+
17
+ export { AuthDialog, type AuthDialogProps };
@@ -0,0 +1,332 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var React = require('react');
5
+ var auth = require('firebase/auth');
6
+ var app = require('firebase/app');
7
+ var firestore = require('firebase/firestore');
8
+ var storage = require('firebase/storage');
9
+ var mwUi = require('@marcwelti/mw-ui');
10
+ var jsxRuntime = require('react/jsx-runtime');
11
+
12
+ function _interopNamespace(e) {
13
+ if (e && e.__esModule) return e;
14
+ var n = Object.create(null);
15
+ if (e) {
16
+ Object.keys(e).forEach(function (k) {
17
+ if (k !== 'default') {
18
+ var d = Object.getOwnPropertyDescriptor(e, k);
19
+ Object.defineProperty(n, k, d.get ? d : {
20
+ enumerable: true,
21
+ get: function () { return e[k]; }
22
+ });
23
+ }
24
+ });
25
+ }
26
+ n.default = e;
27
+ return Object.freeze(n);
28
+ }
29
+
30
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
31
+
32
+ // src/components/AuthDialog.tsx
33
+ function getFirebaseConfig() {
34
+ const config = {
35
+ apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY || "",
36
+ authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN || "",
37
+ projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID || "",
38
+ storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET || "",
39
+ messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID || "",
40
+ appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID || "",
41
+ measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID
42
+ };
43
+ const requiredFields = [
44
+ "apiKey",
45
+ "authDomain",
46
+ "projectId",
47
+ "storageBucket",
48
+ "messagingSenderId",
49
+ "appId"
50
+ ];
51
+ const missingFields = requiredFields.filter((field) => !config[field]);
52
+ if (missingFields.length > 0) {
53
+ console.warn(
54
+ `[mw-core] Missing Firebase config fields: ${missingFields.join(", ")}. Make sure to set NEXT_PUBLIC_FIREBASE_* environment variables.`
55
+ );
56
+ }
57
+ return config;
58
+ }
59
+ function initializeFirebase(config) {
60
+ if (app.getApps().length > 0) {
61
+ return app.getApp();
62
+ }
63
+ const firebaseConfig = getFirebaseConfig();
64
+ return app.initializeApp(firebaseConfig);
65
+ }
66
+ var _app = null;
67
+ var _auth = null;
68
+ var _db = null;
69
+ var _storage = null;
70
+ function getFirebaseApp() {
71
+ if (!_app) {
72
+ _app = initializeFirebase();
73
+ }
74
+ return _app;
75
+ }
76
+ function getFirebaseAuth() {
77
+ if (!_auth) {
78
+ _auth = auth.getAuth(getFirebaseApp());
79
+ }
80
+ return _auth;
81
+ }
82
+ function getFirebaseFirestore() {
83
+ if (!_db) {
84
+ const databaseId = process.env.NEXT_PUBLIC_FIRESTORE_DATABASE_ID;
85
+ if (databaseId) {
86
+ _db = firestore.getFirestore(getFirebaseApp(), databaseId);
87
+ } else {
88
+ _db = firestore.getFirestore(getFirebaseApp());
89
+ }
90
+ }
91
+ return _db;
92
+ }
93
+ function getFirebaseStorage() {
94
+ if (!_storage) {
95
+ _storage = storage.getStorage(getFirebaseApp());
96
+ }
97
+ return _storage;
98
+ }
99
+ typeof window !== "undefined" ? getFirebaseApp() : null;
100
+ typeof window !== "undefined" ? getFirebaseAuth() : null;
101
+ typeof window !== "undefined" ? getFirebaseFirestore() : null;
102
+ typeof window !== "undefined" ? getFirebaseStorage() : null;
103
+
104
+ // src/firebase/auth.ts
105
+ async function signInWithEmail(email, password) {
106
+ const auth2 = getFirebaseAuth();
107
+ return auth.signInWithEmailAndPassword(auth2, email, password);
108
+ }
109
+ async function signUpWithEmail(email, password, displayName) {
110
+ const auth2 = getFirebaseAuth();
111
+ const credential = await auth.createUserWithEmailAndPassword(auth2, email, password);
112
+ return credential;
113
+ }
114
+ async function signInWithGoogle() {
115
+ const auth2 = getFirebaseAuth();
116
+ const provider = new auth.GoogleAuthProvider();
117
+ provider.addScope("email");
118
+ provider.addScope("profile");
119
+ return auth.signInWithPopup(auth2, provider);
120
+ }
121
+ function getAuthErrorMessage(error) {
122
+ if (error instanceof Error) {
123
+ if (error.message.includes("auth/user-not-found")) return "Kein Account mit dieser E-Mail gefunden.";
124
+ if (error.message.includes("auth/wrong-password")) return "Falsches Passwort.";
125
+ if (error.message.includes("auth/too-many-requests")) return "Zu viele Versuche. Bitte warte einen Moment.";
126
+ if (error.message.includes("auth/invalid-credential")) return "Ung\xFCltige Anmeldedaten.";
127
+ if (error.message.includes("auth/popup-closed-by-user")) return "Anmeldung abgebrochen.";
128
+ if (error.message.includes("auth/email-already-in-use")) return "Diese E-Mail wird bereits verwendet.";
129
+ if (error.message.includes("auth/weak-password")) return "Passwort ist zu schwach.";
130
+ if (error.message.includes("auth/invalid-email")) return "Ung\xFCltige E-Mail-Adresse.";
131
+ return error.message;
132
+ }
133
+ return "Ein Fehler ist aufgetreten.";
134
+ }
135
+ function GoogleIcon() {
136
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { className: "h-5 w-5 mr-2", viewBox: "0 0 24 24", children: [
137
+ /* @__PURE__ */ jsxRuntime.jsx("path", { fill: "#4285F4", d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" }),
138
+ /* @__PURE__ */ jsxRuntime.jsx("path", { fill: "#34A853", d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" }),
139
+ /* @__PURE__ */ jsxRuntime.jsx("path", { fill: "#FBBC05", d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" }),
140
+ /* @__PURE__ */ jsxRuntime.jsx("path", { fill: "#EA4335", d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" })
141
+ ] });
142
+ }
143
+ function AuthDialog({
144
+ open,
145
+ onOpenChange,
146
+ onSuccess,
147
+ sessionEndpoint = "/api/auth/login",
148
+ defaultMode = "login"
149
+ }) {
150
+ const [mode, setMode] = React__namespace.useState(defaultMode);
151
+ const [email, setEmail] = React__namespace.useState("");
152
+ const [password, setPassword] = React__namespace.useState("");
153
+ const [confirmPassword, setConfirmPassword] = React__namespace.useState("");
154
+ const [loading, setLoading] = React__namespace.useState(false);
155
+ const [error, setError] = React__namespace.useState(null);
156
+ const resetForm = React__namespace.useCallback(() => {
157
+ setEmail("");
158
+ setPassword("");
159
+ setConfirmPassword("");
160
+ setError(null);
161
+ setLoading(false);
162
+ }, []);
163
+ React__namespace.useEffect(() => {
164
+ if (open) {
165
+ setMode(defaultMode);
166
+ resetForm();
167
+ }
168
+ }, [open, defaultMode, resetForm]);
169
+ const createSession = async (idToken) => {
170
+ const response = await fetch(sessionEndpoint, {
171
+ method: "POST",
172
+ headers: { "Content-Type": "application/json" },
173
+ body: JSON.stringify({ idToken })
174
+ });
175
+ if (!response.ok) throw new Error("Sitzung konnte nicht erstellt werden.");
176
+ };
177
+ const handleSubmit = async (e) => {
178
+ e.preventDefault();
179
+ setError(null);
180
+ if (mode === "register" && password !== confirmPassword) {
181
+ setError("Passw\xF6rter stimmen nicht \xFCberein.");
182
+ return;
183
+ }
184
+ if (mode === "register" && password.length < 8) {
185
+ setError("Passwort muss mindestens 8 Zeichen lang sein.");
186
+ return;
187
+ }
188
+ setLoading(true);
189
+ try {
190
+ const credential = mode === "login" ? await signInWithEmail(email, password) : await signUpWithEmail(email, password);
191
+ const idToken = await credential.user.getIdToken();
192
+ await createSession(idToken);
193
+ resetForm();
194
+ onOpenChange(false);
195
+ onSuccess();
196
+ } catch (err) {
197
+ setError(getAuthErrorMessage(err));
198
+ } finally {
199
+ setLoading(false);
200
+ }
201
+ };
202
+ const handleGoogle = async () => {
203
+ setError(null);
204
+ setLoading(true);
205
+ try {
206
+ const credential = await signInWithGoogle();
207
+ const idToken = await credential.user.getIdToken();
208
+ await createSession(idToken);
209
+ resetForm();
210
+ onOpenChange(false);
211
+ onSuccess();
212
+ } catch (err) {
213
+ setError(getAuthErrorMessage(err));
214
+ } finally {
215
+ setLoading(false);
216
+ }
217
+ };
218
+ return /* @__PURE__ */ jsxRuntime.jsx(mwUi.Dialog, { open, onOpenChange: (v) => {
219
+ onOpenChange(v);
220
+ if (!v) resetForm();
221
+ }, children: /* @__PURE__ */ jsxRuntime.jsxs(mwUi.DialogContent, { className: "max-w-md", children: [
222
+ /* @__PURE__ */ jsxRuntime.jsxs(mwUi.DialogHeader, { children: [
223
+ /* @__PURE__ */ jsxRuntime.jsx(mwUi.DialogTitle, { className: "text-xl", children: mode === "login" ? "Willkommen zur\xFCck" : "Account erstellen" }),
224
+ /* @__PURE__ */ jsxRuntime.jsx(mwUi.DialogDescription, { children: mode === "login" ? "Melde dich bei deinem Account an." : "Erstelle einen neuen Account." })
225
+ ] }),
226
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
227
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
228
+ /* @__PURE__ */ jsxRuntime.jsx(mwUi.Label, { htmlFor: "auth-email", children: "E-Mail" }),
229
+ /* @__PURE__ */ jsxRuntime.jsx(
230
+ mwUi.Input,
231
+ {
232
+ id: "auth-email",
233
+ type: "email",
234
+ value: email,
235
+ onChange: (e) => setEmail(e.target.value),
236
+ placeholder: "du@beispiel.ch",
237
+ required: true,
238
+ disabled: loading
239
+ }
240
+ )
241
+ ] }),
242
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
243
+ /* @__PURE__ */ jsxRuntime.jsx(mwUi.Label, { htmlFor: "auth-password", children: "Passwort" }),
244
+ /* @__PURE__ */ jsxRuntime.jsx(
245
+ mwUi.Input,
246
+ {
247
+ id: "auth-password",
248
+ type: "password",
249
+ value: password,
250
+ onChange: (e) => setPassword(e.target.value),
251
+ placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
252
+ required: true,
253
+ disabled: loading,
254
+ minLength: mode === "register" ? 8 : void 0
255
+ }
256
+ )
257
+ ] }),
258
+ mode === "register" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
259
+ /* @__PURE__ */ jsxRuntime.jsx(mwUi.Label, { htmlFor: "auth-confirm", children: "Passwort best\xE4tigen" }),
260
+ /* @__PURE__ */ jsxRuntime.jsx(
261
+ mwUi.Input,
262
+ {
263
+ id: "auth-confirm",
264
+ type: "password",
265
+ value: confirmPassword,
266
+ onChange: (e) => setConfirmPassword(e.target.value),
267
+ placeholder: "Passwort wiederholen",
268
+ required: true,
269
+ disabled: loading
270
+ }
271
+ )
272
+ ] }),
273
+ error && /* @__PURE__ */ jsxRuntime.jsx(mwUi.Alert, { variant: "destructive", children: /* @__PURE__ */ jsxRuntime.jsx(mwUi.AlertDescription, { children: error }) }),
274
+ /* @__PURE__ */ jsxRuntime.jsx(mwUi.Button, { type: "submit", className: "w-full", disabled: loading, children: loading ? "Wird geladen..." : mode === "login" ? "Anmelden" : "Account erstellen" })
275
+ ] }),
276
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
277
+ /* @__PURE__ */ jsxRuntime.jsx(mwUi.Separator, { className: "flex-1" }),
278
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: "oder" }),
279
+ /* @__PURE__ */ jsxRuntime.jsx(mwUi.Separator, { className: "flex-1" })
280
+ ] }),
281
+ /* @__PURE__ */ jsxRuntime.jsxs(
282
+ mwUi.Button,
283
+ {
284
+ type: "button",
285
+ intent: "secondary",
286
+ background: "solid",
287
+ className: "w-full",
288
+ onClick: handleGoogle,
289
+ disabled: loading,
290
+ children: [
291
+ /* @__PURE__ */ jsxRuntime.jsx(GoogleIcon, {}),
292
+ "Mit Google anmelden"
293
+ ]
294
+ }
295
+ ),
296
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center text-sm text-muted-foreground", children: mode === "login" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
297
+ "Noch kein Account?",
298
+ " ",
299
+ /* @__PURE__ */ jsxRuntime.jsx(
300
+ "button",
301
+ {
302
+ type: "button",
303
+ onClick: () => {
304
+ setMode("register");
305
+ setError(null);
306
+ },
307
+ className: "text-brand-gold hover:underline",
308
+ children: "Registrieren"
309
+ }
310
+ )
311
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
312
+ "Bereits ein Account?",
313
+ " ",
314
+ /* @__PURE__ */ jsxRuntime.jsx(
315
+ "button",
316
+ {
317
+ type: "button",
318
+ onClick: () => {
319
+ setMode("login");
320
+ setError(null);
321
+ },
322
+ className: "text-brand-gold hover:underline",
323
+ children: "Anmelden"
324
+ }
325
+ )
326
+ ] }) })
327
+ ] }) });
328
+ }
329
+
330
+ exports.AuthDialog = AuthDialog;
331
+ //# sourceMappingURL=index.js.map
332
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/firebase/config.ts","../../src/firebase/auth.ts","../../src/components/AuthDialog.tsx"],"names":["getApps","getApp","initializeApp","getAuth","getFirestore","getStorage","auth","signInWithEmailAndPassword","createUserWithEmailAndPassword","GoogleAuthProvider","signInWithPopup","jsxs","jsx","React","Dialog","DialogContent","DialogHeader","DialogTitle","DialogDescription","Label","Input","Alert","AlertDescription","Button","Separator","Fragment"],"mappings":"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","file":"index.js","sourcesContent":["import { initializeApp, getApps, getApp, FirebaseApp } from 'firebase/app';\nimport { getAuth, Auth } from 'firebase/auth';\nimport { getFirestore, Firestore } from 'firebase/firestore';\nimport { getStorage, FirebaseStorage } from 'firebase/storage';\n\n/**\n * Firebase configuration interface\n * All values should be provided via environment variables\n */\nexport interface FirebaseConfig {\n apiKey: string;\n authDomain: string;\n projectId: string;\n storageBucket: string;\n messagingSenderId: string;\n appId: string;\n measurementId?: string;\n}\n\n/**\n * Get Firebase configuration from environment variables\n * Works with Next.js NEXT_PUBLIC_ prefix\n */\nexport function getFirebaseConfig(): FirebaseConfig {\n const config: FirebaseConfig = {\n apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY || '',\n authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN || '',\n projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID || '',\n storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET || '',\n messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID || '',\n appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID || '',\n measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,\n };\n\n // Validate required fields\n const requiredFields: (keyof FirebaseConfig)[] = [\n 'apiKey',\n 'authDomain',\n 'projectId',\n 'storageBucket',\n 'messagingSenderId',\n 'appId',\n ];\n\n const missingFields = requiredFields.filter((field) => !config[field]);\n\n if (missingFields.length > 0) {\n console.warn(\n `[mw-core] Missing Firebase config fields: ${missingFields.join(', ')}. ` +\n `Make sure to set NEXT_PUBLIC_FIREBASE_* environment variables.`\n );\n }\n\n return config;\n}\n\n/**\n * Initialize Firebase app (singleton pattern)\n * Safe to call multiple times - returns existing instance if already initialized\n */\nexport function initializeFirebase(config?: FirebaseConfig): FirebaseApp {\n if (getApps().length > 0) {\n return getApp();\n }\n\n const firebaseConfig = config || getFirebaseConfig();\n return initializeApp(firebaseConfig);\n}\n\n// Lazy initialization singletons\nlet _app: FirebaseApp | null = null;\nlet _auth: Auth | null = null;\nlet _db: Firestore | null = null;\nlet _storage: FirebaseStorage | null = null;\n\n/**\n * Get the Firebase app instance\n */\nexport function getFirebaseApp(): FirebaseApp {\n if (!_app) {\n _app = initializeFirebase();\n }\n return _app;\n}\n\n/**\n * Get the Firebase Auth instance\n */\nexport function getFirebaseAuth(): Auth {\n if (!_auth) {\n _auth = getAuth(getFirebaseApp());\n }\n return _auth;\n}\n\n/**\n * Get the Firestore database instance\n * Supports named databases via NEXT_PUBLIC_FIRESTORE_DATABASE_ID env var\n */\nexport function getFirebaseFirestore(): Firestore {\n if (!_db) {\n const databaseId = process.env.NEXT_PUBLIC_FIRESTORE_DATABASE_ID;\n if (databaseId) {\n _db = getFirestore(getFirebaseApp(), databaseId);\n } else {\n _db = getFirestore(getFirebaseApp());\n }\n }\n return _db;\n}\n\n/**\n * Get the Firebase Storage instance\n */\nexport function getFirebaseStorage(): FirebaseStorage {\n if (!_storage) {\n _storage = getStorage(getFirebaseApp());\n }\n return _storage;\n}\n\n// Convenience exports for direct access\nexport const app = typeof window !== 'undefined' ? getFirebaseApp() : null;\nexport const auth = typeof window !== 'undefined' ? getFirebaseAuth() : null;\nexport const db = typeof window !== 'undefined' ? getFirebaseFirestore() : null;\nexport const storage = typeof window !== 'undefined' ? getFirebaseStorage() : null;\n\n","import {\n signInWithEmailAndPassword,\n createUserWithEmailAndPassword,\n signOut as firebaseSignOut,\n sendPasswordResetEmail,\n sendEmailVerification,\n updateProfile,\n updatePassword as firebaseUpdatePassword,\n GoogleAuthProvider,\n EmailAuthProvider,\n signInWithPopup,\n signInWithRedirect,\n getRedirectResult,\n onAuthStateChanged,\n reauthenticateWithCredential as firebaseReauthenticateWithCredential,\n reauthenticateWithPopup as firebaseReauthenticateWithPopup,\n verifyBeforeUpdateEmail as firebaseVerifyBeforeUpdateEmail,\n deleteUser as firebaseDeleteUser,\n getAdditionalUserInfo as firebaseGetAdditionalUserInfo,\n User,\n UserCredential,\n Auth,\n AuthCredential,\n} from 'firebase/auth';\nimport { getFirebaseAuth } from './config';\n\n/**\n * Sign in with email and password\n */\nexport async function signInWithEmail(\n email: string,\n password: string\n): Promise<UserCredential> {\n const auth = getFirebaseAuth();\n return signInWithEmailAndPassword(auth, email, password);\n}\n\n/**\n * Create a new account with email and password\n */\nexport async function signUpWithEmail(\n email: string,\n password: string,\n displayName?: string\n): Promise<UserCredential> {\n const auth = getFirebaseAuth();\n const credential = await createUserWithEmailAndPassword(auth, email, password);\n \n if (displayName && credential.user) {\n await updateProfile(credential.user, { displayName });\n }\n \n return credential;\n}\n\n/**\n * Sign out the current user\n */\nexport async function signOut(): Promise<void> {\n const auth = getFirebaseAuth();\n return firebaseSignOut(auth);\n}\n\n/**\n * Send a password reset email\n */\nexport async function resetPassword(email: string): Promise<void> {\n const auth = getFirebaseAuth();\n return sendPasswordResetEmail(auth, email);\n}\n\n/**\n * Send email verification to the current user\n */\nexport async function sendVerificationEmail(user: User): Promise<void> {\n return sendEmailVerification(user);\n}\n\n/**\n * Update the current user's profile\n */\nexport async function updateUserProfile(\n user: User,\n profile: { displayName?: string; photoURL?: string }\n): Promise<void> {\n return updateProfile(user, profile);\n}\n\n/**\n * Sign in with Google using popup\n */\nexport async function signInWithGoogle(): Promise<UserCredential> {\n const auth = getFirebaseAuth();\n const provider = new GoogleAuthProvider();\n provider.addScope('email');\n provider.addScope('profile');\n return signInWithPopup(auth, provider);\n}\n\n/**\n * Sign in with Google using redirect (better for mobile)\n */\nexport async function signInWithGoogleRedirect(): Promise<void> {\n const auth = getFirebaseAuth();\n const provider = new GoogleAuthProvider();\n provider.addScope('email');\n provider.addScope('profile');\n return signInWithRedirect(auth, provider);\n}\n\n/**\n * Get the result of a redirect sign-in\n */\nexport async function getGoogleRedirectResult(): Promise<UserCredential | null> {\n const auth = getFirebaseAuth();\n return getRedirectResult(auth);\n}\n\n/**\n * Subscribe to auth state changes\n */\nexport function subscribeToAuthState(\n callback: (user: User | null) => void\n): () => void {\n const auth = getFirebaseAuth();\n return onAuthStateChanged(auth, callback);\n}\n\n/**\n * Get the current user (may be null if not authenticated)\n */\nexport function getCurrentUser(): User | null {\n const auth = getFirebaseAuth();\n return auth.currentUser;\n}\n\n/**\n * Wait for the auth state to be determined\n * Useful for SSR/initial load\n */\nexport function waitForAuthState(): Promise<User | null> {\n return new Promise((resolve) => {\n const auth = getFirebaseAuth();\n const unsubscribe = onAuthStateChanged(auth, (user) => {\n unsubscribe();\n resolve(user);\n });\n });\n}\n\n/**\n * Update the current user's password\n * Requires recent authentication\n */\nexport async function updatePassword(\n user: User,\n newPassword: string\n): Promise<void> {\n return firebaseUpdatePassword(user, newPassword);\n}\n\n/**\n * Re-authenticate user with email/password credential\n * Required before sensitive operations like password change, email change, account deletion\n */\nexport async function reauthenticateWithCredential(\n user: User,\n credential: AuthCredential\n): Promise<UserCredential> {\n return firebaseReauthenticateWithCredential(user, credential);\n}\n\n/**\n * Re-authenticate user with Google popup\n * Required before sensitive operations for Google-authenticated users\n */\nexport async function reauthenticateWithPopup(\n user: User\n): Promise<UserCredential> {\n const provider = new GoogleAuthProvider();\n return firebaseReauthenticateWithPopup(user, provider);\n}\n\n/**\n * Send verification email to new email address before updating\n * User must verify new email before it becomes active\n */\nexport async function verifyBeforeUpdateEmail(\n user: User,\n newEmail: string\n): Promise<void> {\n return firebaseVerifyBeforeUpdateEmail(user, newEmail);\n}\n\n/**\n * Delete the authenticated user\n * Requires recent authentication\n */\nexport async function deleteUser(user: User): Promise<void> {\n return firebaseDeleteUser(user);\n}\n\n/**\n * Get additional user info from credential (e.g., isNewUser)\n */\nexport function getAdditionalUserInfo(credential: UserCredential) {\n return firebaseGetAdditionalUserInfo(credential);\n}\n\n/**\n * Create email/password credential for re-authentication\n */\nexport function createEmailCredential(email: string, password: string): AuthCredential {\n return EmailAuthProvider.credential(email, password);\n}\n\n/**\n * Get the Firebase Auth instance\n */\nexport function getAuth(): Auth {\n return getFirebaseAuth();\n}\n\n// Re-export useful types\nexport type { User, UserCredential, Auth, AuthCredential };\n\n// Re-export providers for direct use\nexport { GoogleAuthProvider, EmailAuthProvider };\n\n","'use client';\n\nimport * as React from 'react';\nimport { signInWithEmail, signInWithGoogle, signUpWithEmail } from '../firebase/auth';\nimport {\n Button,\n Input,\n Label,\n Alert,\n AlertDescription,\n Separator,\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogDescription,\n} from '@marcwelti/mw-ui';\n\n// ---------------------------------------------------------------------------\n// Error helper\n// ---------------------------------------------------------------------------\n\nfunction getAuthErrorMessage(error: unknown): string {\n if (error instanceof Error) {\n if (error.message.includes('auth/user-not-found')) return 'Kein Account mit dieser E-Mail gefunden.';\n if (error.message.includes('auth/wrong-password')) return 'Falsches Passwort.';\n if (error.message.includes('auth/too-many-requests')) return 'Zu viele Versuche. Bitte warte einen Moment.';\n if (error.message.includes('auth/invalid-credential')) return 'Ungültige Anmeldedaten.';\n if (error.message.includes('auth/popup-closed-by-user')) return 'Anmeldung abgebrochen.';\n if (error.message.includes('auth/email-already-in-use')) return 'Diese E-Mail wird bereits verwendet.';\n if (error.message.includes('auth/weak-password')) return 'Passwort ist zu schwach.';\n if (error.message.includes('auth/invalid-email')) return 'Ungültige E-Mail-Adresse.';\n return error.message;\n }\n return 'Ein Fehler ist aufgetreten.';\n}\n\n// ---------------------------------------------------------------------------\n// Google SVG icon\n// ---------------------------------------------------------------------------\n\nfunction GoogleIcon() {\n return (\n <svg className=\"h-5 w-5 mr-2\" viewBox=\"0 0 24 24\">\n <path fill=\"#4285F4\" d=\"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z\" />\n <path fill=\"#34A853\" d=\"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z\" />\n <path fill=\"#FBBC05\" d=\"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z\" />\n <path fill=\"#EA4335\" d=\"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z\" />\n </svg>\n );\n}\n\n// ---------------------------------------------------------------------------\n// AuthDialog\n// ---------------------------------------------------------------------------\n\nexport interface AuthDialogProps {\n /** Whether the dialog is open. */\n open: boolean;\n /** Called when the dialog open state changes. */\n onOpenChange: (open: boolean) => void;\n /** Called after successful authentication. Use this to reload the session. */\n onSuccess: () => void;\n /** URL for the session creation endpoint. Defaults to '/api/auth/login'. */\n sessionEndpoint?: string;\n /** Default mode: 'login' or 'register'. Defaults to 'login'. */\n defaultMode?: 'login' | 'register';\n}\n\nexport function AuthDialog({\n open,\n onOpenChange,\n onSuccess,\n sessionEndpoint = '/api/auth/login',\n defaultMode = 'login',\n}: AuthDialogProps) {\n const [mode, setMode] = React.useState<'login' | 'register'>(defaultMode);\n const [email, setEmail] = React.useState('');\n const [password, setPassword] = React.useState('');\n const [confirmPassword, setConfirmPassword] = React.useState('');\n const [loading, setLoading] = React.useState(false);\n const [error, setError] = React.useState<string | null>(null);\n\n const resetForm = React.useCallback(() => {\n setEmail('');\n setPassword('');\n setConfirmPassword('');\n setError(null);\n setLoading(false);\n }, []);\n\n // Reset mode to default when dialog opens\n React.useEffect(() => {\n if (open) {\n setMode(defaultMode);\n resetForm();\n }\n }, [open, defaultMode, resetForm]);\n\n const createSession = async (idToken: string) => {\n const response = await fetch(sessionEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ idToken }),\n });\n if (!response.ok) throw new Error('Sitzung konnte nicht erstellt werden.');\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (mode === 'register' && password !== confirmPassword) {\n setError('Passwörter stimmen nicht überein.');\n return;\n }\n if (mode === 'register' && password.length < 8) {\n setError('Passwort muss mindestens 8 Zeichen lang sein.');\n return;\n }\n\n setLoading(true);\n try {\n const credential = mode === 'login'\n ? await signInWithEmail(email, password)\n : await signUpWithEmail(email, password);\n const idToken = await credential.user.getIdToken();\n await createSession(idToken);\n resetForm();\n onOpenChange(false);\n onSuccess();\n } catch (err) {\n setError(getAuthErrorMessage(err));\n } finally {\n setLoading(false);\n }\n };\n\n const handleGoogle = async () => {\n setError(null);\n setLoading(true);\n try {\n const credential = await signInWithGoogle();\n const idToken = await credential.user.getIdToken();\n await createSession(idToken);\n resetForm();\n onOpenChange(false);\n onSuccess();\n } catch (err) {\n setError(getAuthErrorMessage(err));\n } finally {\n setLoading(false);\n }\n };\n\n return (\n <Dialog open={open} onOpenChange={(v) => { onOpenChange(v); if (!v) resetForm(); }}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle className=\"text-xl\">\n {mode === 'login' ? 'Willkommen zurück' : 'Account erstellen'}\n </DialogTitle>\n <DialogDescription>\n {mode === 'login'\n ? 'Melde dich bei deinem Account an.'\n : 'Erstelle einen neuen Account.'}\n </DialogDescription>\n </DialogHeader>\n\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"auth-email\">E-Mail</Label>\n <Input\n id=\"auth-email\"\n type=\"email\"\n value={email}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) => setEmail(e.target.value)}\n placeholder=\"du@beispiel.ch\"\n required\n disabled={loading}\n />\n </div>\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"auth-password\">Passwort</Label>\n <Input\n id=\"auth-password\"\n type=\"password\"\n value={password}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) => setPassword(e.target.value)}\n placeholder=\"••••••••\"\n required\n disabled={loading}\n minLength={mode === 'register' ? 8 : undefined}\n />\n </div>\n\n {mode === 'register' && (\n <div className=\"space-y-2\">\n <Label htmlFor=\"auth-confirm\">Passwort bestätigen</Label>\n <Input\n id=\"auth-confirm\"\n type=\"password\"\n value={confirmPassword}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) => setConfirmPassword(e.target.value)}\n placeholder=\"Passwort wiederholen\"\n required\n disabled={loading}\n />\n </div>\n )}\n\n {error && (\n <Alert variant=\"destructive\">\n <AlertDescription>{error}</AlertDescription>\n </Alert>\n )}\n\n <Button type=\"submit\" className=\"w-full\" disabled={loading}>\n {loading\n ? 'Wird geladen...'\n : mode === 'login'\n ? 'Anmelden'\n : 'Account erstellen'}\n </Button>\n </form>\n\n <div className=\"flex items-center gap-4\">\n <Separator className=\"flex-1\" />\n <span className=\"text-sm text-muted-foreground\">oder</span>\n <Separator className=\"flex-1\" />\n </div>\n\n <Button\n type=\"button\"\n intent=\"secondary\"\n background=\"solid\"\n className=\"w-full\"\n onClick={handleGoogle}\n disabled={loading}\n >\n <GoogleIcon />\n Mit Google anmelden\n </Button>\n\n <div className=\"text-center text-sm text-muted-foreground\">\n {mode === 'login' ? (\n <>\n Noch kein Account?{' '}\n <button\n type=\"button\"\n onClick={() => { setMode('register'); setError(null); }}\n className=\"text-brand-gold hover:underline\"\n >\n Registrieren\n </button>\n </>\n ) : (\n <>\n Bereits ein Account?{' '}\n <button\n type=\"button\"\n onClick={() => { setMode('login'); setError(null); }}\n className=\"text-brand-gold hover:underline\"\n >\n Anmelden\n </button>\n </>\n )}\n </div>\n </DialogContent>\n </Dialog>\n );\n}\n\n"]}