@insforge/nextjs 0.7.0 → 0.7.2
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 +2 -0
- package/dist/api.js +2 -2
- package/dist/api.js.map +1 -1
- package/dist/api.mjs +2 -2
- package/dist/api.mjs.map +1 -1
- package/dist/index.d.mts +14 -14
- package/dist/index.d.ts +14 -14
- package/dist/index.js +222 -151
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +219 -148
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
// Auto-inject InsForge styles
|
|
4
|
+
if (typeof document !== 'undefined' && typeof window !== 'undefined') {
|
|
5
|
+
const styleId = 'insforge-nextjs-styles';
|
|
6
|
+
if (!document.getElementById(styleId)) {
|
|
7
|
+
const style = document.createElement('style');
|
|
8
|
+
style.id = styleId;
|
|
9
|
+
style.textContent = "/**\n * InsForge Next.js Component Library Styles\n * A standalone CSS file for auth components - no Tailwind required!\n */\n\n/* Font Face Declaration */\n@font-face {\n font-family: 'Manrope';\n src: url('./fonts/Manrope-VariableFont_wght.ttf') format('truetype');\n font-weight: 100 900;\n font-style: normal;\n font-display: swap;\n}\n\n/* CSS Variables */\n:root {\n --font-manrope: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n}\n\n/* Reset and Base Styles */\n.insforge-auth-container * {\n box-sizing: border-box;\n}\n\n/* Main Container - App handles layout, we just provide the card */\n.insforge-auth-container {\n width: 100%;\n max-width: 400px;\n background: white;\n}\n\n.insforge-branding {\n background: #FAFAFA;\n padding: 8px 8px 16px 8px;\n display: flex;\n flex-direction: row;\n justify-content: center;\n align-items: center;\n gap: 4px;\n}\n\n.insforge-branding-text {\n color: #000;\n font-family: var(--font-manrope);\n font-size: 12px;\n font-style: normal;\n font-weight: 400;\n line-height: normal;\n}\n\n/* Form Card */\n.insforge-auth-card {\n width: 100%;\n border-radius: 12px;\n overflow: hidden;\n box-shadow: 0 4px 12px 0 rgba(0, 0, 0, 0.25);\n}\n\n.insforge-auth-content {\n padding: 24px;\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: stretch;\n gap: 24px;\n}\n\n/* Header */\n.insforge-auth-header {\n display: flex;\n flex-direction: column;\n justify-content: start;\n align-items: start;\n gap: 8px;\n}\n\n.insforge-auth-title {\n color: #000;\n font-family: Inter;\n font-size: 24px;\n font-style: normal;\n font-weight: 600;\n line-height: 32px; /* 133.333% */\n}\n\n.insforge-auth-subtitle {\n color: #828282;\n font-family: Inter;\n font-size: 14px;\n font-style: normal;\n font-weight: 400;\n line-height: 24px; /* 171.429% */\n}\n\n/* Error Banner */\n.insforge-error-banner {\n display: flex;\n padding: 8px 8px 8px 12px;\n margin-bottom: 16px;\n align-items: center;\n gap: 8px;\n align-self: stretch;\n border-radius: 4px;\n border: 2px solid #DC2626;\n background: #FEF2F2;\n color: #DC2626;\n font-family: Inter;\n font-size: 14px;\n font-style: normal;\n font-weight: 400;\n line-height: 20px; /* 142.857% */\n}\n\n.insforge-error-icon {\n color: #EF4444;\n flex-shrink: 0;\n width: 24px;\n height: 24px;\n}\n\n/* Form Elements */\n.insforge-form {\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: stretch;\n gap: 24px;\n}\n\n.insforge-form-group {\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: stretch;\n gap: 4px;\n}\n\n.insforge-form-label-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n}\n\n.insforge-form-label {\n color: #000;\n font-family: Inter;\n font-size: 14px;\n font-style: normal;\n font-weight: 400;\n line-height: 24px;\n}\n\n.insforge-form-link {\n color: #828282;\n text-align: right;\n font-family: Inter;\n font-size: 14px;\n font-style: normal;\n font-weight: 400;\n line-height: 24px;\n}\n\n.insforge-form-link:hover {\n color: #828282;\n text-decoration: underline;\n}\n\n/* Input Container for Password (with icon) */\n.insforge-input-wrapper {\n position: relative;\n}\n\n.insforge-input {\n width: 100%;\n display: flex;\n padding: 8px 8px 10px 12px;\n align-items: center;\n gap: 8px;\n align-self: stretch;\n border-radius: 4px;\n border: 1px solid #BCBCBC;\n background: #FFF;\n font-family: Inter;\n font-size: 16px;\n font-style: normal;\n font-weight: 400;\n line-height: 20px;\n}\n\n.insforge-input::placeholder {\n color: #A6A6A6;\n}\n\n.insforge-input:focus {\n outline: none;\n}\n\n.insforge-input-with-icon {\n padding-right: 3rem;\n}\n\n.insforge-input-icon-btn {\n position: absolute;\n right: 8px;\n top: 50%;\n transform: translateY(-50%);\n background: transparent;\n border: none;\n color: #A6A6A6;\n cursor: pointer;\n transition: color 0.2s;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.insforge-input-icon-btn:hover {\n color: #6b7280;\n}\n\n/* Primary Button */\n.insforge-btn-primary {\n border-radius: 4px;\n background: #000;\n width: 100%;\n display: flex;\n margin: 16px 0 0 0;\n padding: 8px 16px;\n justify-content: center;\n align-items: center;\n gap: 10px;\n align-self: stretch;\n color: #FFF;\n font-family: Manrope;\n font-size: 16px;\n font-style: normal;\n font-weight: 600;\n line-height: normal;\n border: none;\n cursor: pointer;\n}\n\n.insforge-btn-primary:hover {\n background: #303030;\n}\n\n.insforge-btn-primary:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.insforge-btn-primary .insforge-btn-loader {\n display: none;\n animation: insforge-spin 1s linear infinite;\n}\n\n.insforge-btn-primary[data-loading] .insforge-btn-loader {\n display: block;\n}\n\n.insforge-btn-primary .insforge-btn-check {\n display: none;\n}\n\n.insforge-btn-primary[data-confirmed] .insforge-btn-check {\n display: block;\n}\n\n/* Text Link Section */\n.insforge-text-center {\n text-align: center;\n color: #828282;\n font-family: Inter;\n font-size: 14px;\n font-style: normal;\n font-weight: 400;\n line-height: 24px;\n}\n\n.insforge-link-primary {\n color: #000;\n font-family: Inter;\n font-size: 14px;\n font-style: normal;\n font-weight: 500;\n line-height: 24px;\n}\n\n/* Divider */\n.insforge-divider {\n display: flex;\n justify-content: center;\n align-items: center;\n gap: 24px;\n align-self: stretch;\n}\n\n.insforge-divider::before,\n.insforge-divider::after {\n content: '';\n flex: 1;\n height: 1px;\n background: #C6C6C6;\n}\n\n.insforge-divider-text {\n color: #C6C6C6;\n font-family: Manrope;\n font-size: 14px;\n font-style: normal;\n font-weight: 600;\n line-height: normal;\n}\n\n/* OAuth Section */\n.insforge-oauth-container {\n display: grid;\n gap: 12px;\n width: 100%;\n}\n\n/* ============================================================================\n SMART OAUTH GRID LAYOUT SYSTEM\n Pattern: 1→1x1, 2→1x2, 3→1x3, 4→2x2, 5+→auto (3 per row, last row centered)\n ============================================================================ */\n\n/* 1 provider: single column, full width - displays \"Continue with Provider\" */\n.insforge-oauth-container[data-provider-count=\"1\"] {\n grid-template-columns: 1fr;\n}\n\n/* 2 providers: two columns - displays \"Provider\" */\n.insforge-oauth-container[data-provider-count=\"2\"] {\n grid-template-columns: repeat(2, 1fr);\n}\n\n/* 3 providers: three columns - icon only */\n.insforge-oauth-container[data-provider-count=\"3\"] {\n grid-template-columns: repeat(3, 1fr);\n}\n\n/* 4 providers: 2x2 grid - displays \"Provider\" */\n.insforge-oauth-container[data-provider-count=\"4\"] {\n grid-template-columns: repeat(2, 1fr);\n}\n\n/* 5+ providers: Universal 6-column grid system\n - Grid columns managed by OAuthProviderList component via inline styles\n - This provides precise control over button positioning */\n.insforge-oauth-container:not([data-provider-count=\"1\"]):not([data-provider-count=\"2\"]):not([data-provider-count=\"3\"]):not([data-provider-count=\"4\"]) {\n grid-template-columns: repeat(6, 1fr);\n}\n\n/* OAuth Button */\n.insforge-oauth-btn {\n display: flex;\n width: 100%;\n height: 36px;\n padding: 8px 12px;\n flex-direction: row;\n justify-content: center;\n align-items: center;\n gap: 12px;\n border-radius: 6px;\n border: 1px solid #E4E4E7;\n background: #FFF;\n box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.10);\n color: #09090B;\n text-align: center;\n font-family: Inter;\n font-size: 14px;\n font-style: normal;\n font-weight: 500;\n line-height: 20px;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n/* Full mode: show icon + \"Continue with Provider\" */\n.insforge-oauth-btn[data-display-mode=\"full\"] {\n justify-content: center;\n}\n\n/* Short mode: show icon + \"Provider\" */\n.insforge-oauth-btn[data-display-mode=\"short\"] {\n justify-content: center;\n padding: 8px;\n gap: 8px;\n}\n\n/* Icon only mode: show only icon */\n.insforge-oauth-btn[data-display-mode=\"icon\"] {\n justify-content: center;\n gap: 0;\n}\n\n.insforge-oauth-btn[data-display-mode=\"icon\"] .insforge-oauth-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.insforge-oauth-btn:hover {\n background: #f9fafb;\n border-color: #9ca3af;\n}\n\n.insforge-oauth-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.insforge-oauth-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.insforge-oauth-loader {\n display: none;\n animation: insforge-spin 1s linear infinite;\n}\n\n.insforge-oauth-btn[data-loading] .insforge-oauth-icon {\n display: none;\n}\n\n.insforge-oauth-btn[data-loading] .insforge-oauth-loader {\n display: block;\n}\n\n/* Spin Animation */\n@keyframes insforge-spin {\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n}\n\n/* UserButton Styles */\n.insforge-user-button-container {\n position: relative;\n display: inline-block;\n}\n\n.insforge-user-button {\n padding: 0.25rem;\n background: transparent;\n border: none;\n border-radius: 9999px;\n cursor: pointer;\n transition: all 0.2s;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n}\n\n.insforge-user-button:hover {\n background: rgba(0, 0, 0, 0.05);\n}\n\n.insforge-user-button-detailed {\n border-radius: 0.5rem;\n padding: 0.5rem;\n}\n\n.insforge-user-button-info {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 0.125rem;\n}\n\n.insforge-user-button-name {\n font-size: 0.875rem;\n font-weight: 600;\n color: #111827;\n line-height: 1.25rem;\n text-align: left;\n}\n\n.insforge-user-button-email {\n font-size: 0.75rem;\n color: #6b7280;\n line-height: 1rem;\n text-align: left;\n}\n\n.insforge-user-avatar {\n width: 2.5rem;\n height: 2.5rem;\n border-radius: 9999px;\n object-fit: cover;\n}\n\n.insforge-user-avatar-placeholder {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 2.5rem;\n height: 2.5rem;\n background: #3b82f6;\n color: white;\n font-weight: 600;\n font-size: 0.875rem;\n border-radius: 9999px;\n}\n\n.insforge-user-dropdown {\n position: absolute;\n top: 100%;\n right: 0;\n margin-top: 0.5rem;\n min-width: 10rem;\n background: white;\n border: 1px solid #e5e7eb;\n border-radius: 0.5rem;\n box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);\n z-index: 50;\n overflow: hidden;\n padding: 0.25rem;\n}\n\n.insforge-sign-out-button {\n display: flex;\n align-items: center;\n justify-content: flex-start;\n gap: 0.5rem;\n width: 100%;\n padding: 0.5rem 0.75rem;\n font-size: 0.875rem;\n font-family: inherit;\n color: #dc2626;\n background: transparent;\n border: none;\n border-radius: 0.375rem;\n cursor: pointer;\n transition: background 0.2s;\n text-align: left;\n}\n\n.insforge-sign-out-button:hover {\n background: #fef2f2;\n}\n\n/* Loading State */\n.insforge-loading {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 2rem;\n color: #6b7280;\n font-size: 0.875rem;\n}\n\n/* Password Strength Indicator */\n.insforge-password-strength {\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n margin-top: 0.5rem;\n}\n\n.insforge-password-requirement {\n height: 1.5rem;\n display: flex;\n align-items: center;\n gap: 0.25rem;\n}\n\n.insforge-password-check {\n width: 1.25rem;\n height: 1.25rem;\n border-radius: 9999px;\n display: flex;\n border-width: 2px;\n border-style: solid;\n align-items: center;\n justify-content: center;\n transition: all 0.2s ease-in-out;\n background-color: transparent;\n border-color: #a3a3a3;\n flex-shrink: 0;\n}\n\n.insforge-password-check-valid {\n background-color: #22c55e;\n border-color: transparent;\n}\n\n.insforge-password-check-icon {\n color: #fff;\n stroke-width: 3;\n}\n\n.insforge-password-requirement-label {\n color:#000;\n font-family: Inter;\n font-size: 14px;\n font-style: normal;\n font-weight: 400;\n line-height: 24px;\n}\n\n/* Verification Code Input */\n.insforge-verification-code-container {\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n gap: 24px;\n}\n\n.insforge-verification-instructions {\n color: #525252;\n font-family: Inter;\n font-size: 14px;\n font-style: normal;\n font-weight: 400;\n line-height: 20px;\n}\n\n.insforge-verification-instructions > span {\n color: #000;\n font-family: Inter;\n font-size: 14px;\n font-style: normal;\n font-weight: 600;\n line-height: 20px;\n}\n\n.insforge-verification-code-inputs {\n display: flex;\n flex-direction: row;\n gap: 12px;\n justify-content: center;\n align-items: center;\n}\n\n.insforge-verification-code-input {\n width: 100%;\n height: 48px;\n padding: 8px 12px;\n border-radius: 4px;\n border: 1px solid #E0E0E0;\n background: #FFF;\n text-align: center;\n font-size: 16px;\n font-style: normal;\n line-height: 20px;\n font-weight: 600;\n font-family: var(--font-manrope);\n color: #000;\n transition: all 0.2s ease-in-out;\n outline: none;\n}\n\n.insforge-verification-code-input:focus {\n border-color: #000;\n box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);\n}\n\n.insforge-verification-code-input:disabled {\n background-color: #F5F5F5;\n cursor: not-allowed;\n opacity: 0.6;\n}\n\n/* Verification Instructions */\n.insforge-verification-instructions {\n color: #4F4F4F;\n font-family: Inter;\n font-size: 14px;\n font-style: normal;\n font-weight: 400;\n line-height: 24px;\n margin-bottom: 8px;\n}\n\n.insforge-verification-email {\n color: #000;\n font-weight: 600;\n}\n\n.insforge-resend-code {\n color: #525252;\n font-family: Inter;\n font-size: 14px;\n font-style: normal;\n font-weight: 400;\n line-height: 24px;\n text-align: center;\n}\n\n.insforge-resend-link {\n color: #000;\n font-family: Inter;\n font-size: 14px;\n font-style: normal;\n font-weight: 500;\n line-height: 24px;\n}\n\n.insforge-resend-link:hover {\n text-decoration: underline;\n}\n\n.insforge-resend-link:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n/* Responsive adjustments */\n@media (max-width: 640px) {\n .insforge-auth-card {\n padding: 2rem 1.5rem;\n }\n\n .insforge-auth-title {\n font-size: 1.75rem;\n }\n\n .insforge-verification-code-container {\n gap: 8px;\n }\n\n .insforge-verification-code-input {\n width: 40px;\n height: 48px;\n font-size: 20px;\n }\n}\n\n";
|
|
10
|
+
if (document.head) {
|
|
11
|
+
document.head.appendChild(style);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
3
17
|
// src/provider/InsforgeProvider.tsx
|
|
4
18
|
import { createContext, useContext, useEffect, useState, useCallback, useRef } from "react";
|
|
5
19
|
import { createClient } from "@insforge/sdk";
|
|
@@ -59,9 +73,8 @@ function InsforgeProvider({
|
|
|
59
73
|
const userData = {
|
|
60
74
|
id: cachedData.user.id,
|
|
61
75
|
email: cachedData.user.email,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
...cachedData.profile
|
|
76
|
+
name: cachedData.profile?.nickname || "",
|
|
77
|
+
avatarUrl: cachedData.profile?.avatar_url || ""
|
|
65
78
|
};
|
|
66
79
|
setUser(userData);
|
|
67
80
|
setSession({
|
|
@@ -88,9 +101,8 @@ function InsforgeProvider({
|
|
|
88
101
|
const userData = {
|
|
89
102
|
id: userResult.data.user.id,
|
|
90
103
|
email: userResult.data.user.email,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
...userResult.data.profile
|
|
104
|
+
name: userResult.data.profile?.nickname || "",
|
|
105
|
+
avatarUrl: userResult.data.profile?.avatar_url || ""
|
|
94
106
|
};
|
|
95
107
|
setUser(userData);
|
|
96
108
|
setSession({
|
|
@@ -141,120 +153,90 @@ function InsforgeProvider({
|
|
|
141
153
|
}
|
|
142
154
|
};
|
|
143
155
|
}, []);
|
|
156
|
+
const handleAuthSuccess = useCallback(
|
|
157
|
+
async (authToken, fallbackUser) => {
|
|
158
|
+
const userResult = await insforge.auth.getCurrentUser();
|
|
159
|
+
if (userResult.data) {
|
|
160
|
+
const userData = {
|
|
161
|
+
id: userResult.data.user.id,
|
|
162
|
+
email: userResult.data.user.email,
|
|
163
|
+
name: userResult.data.profile?.nickname || "",
|
|
164
|
+
avatarUrl: userResult.data.profile?.avatar_url || ""
|
|
165
|
+
};
|
|
166
|
+
const sessionData = {
|
|
167
|
+
userId: userResult.data.user.id,
|
|
168
|
+
token: authToken,
|
|
169
|
+
expiresAt: "",
|
|
170
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
171
|
+
};
|
|
172
|
+
setUser(userData);
|
|
173
|
+
setSession(sessionData);
|
|
174
|
+
localStorage.setItem("insforge-user-profile", JSON.stringify(userResult.data));
|
|
175
|
+
if (onAuthChange) {
|
|
176
|
+
onAuthChange(userData);
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
await syncTokenToCookie(authToken);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
}
|
|
182
|
+
} else if (fallbackUser) {
|
|
183
|
+
const userData = {
|
|
184
|
+
id: fallbackUser.id || "",
|
|
185
|
+
email: fallbackUser.email || "",
|
|
186
|
+
name: fallbackUser.name || "",
|
|
187
|
+
avatarUrl: ""
|
|
188
|
+
};
|
|
189
|
+
setUser(userData);
|
|
190
|
+
setSession({
|
|
191
|
+
userId: fallbackUser.id || "",
|
|
192
|
+
token: authToken,
|
|
193
|
+
expiresAt: "",
|
|
194
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
195
|
+
});
|
|
196
|
+
if (onAuthChange) {
|
|
197
|
+
onAuthChange(userData);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
[insforge, onAuthChange]
|
|
202
|
+
);
|
|
144
203
|
const signIn = useCallback(
|
|
145
204
|
async (email, password) => {
|
|
146
205
|
const sdkResult = await insforge.auth.signInWithPassword({ email, password });
|
|
147
206
|
if (sdkResult.data) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
id: userResult.data.user.id,
|
|
152
|
-
email: userResult.data.user.email,
|
|
153
|
-
name: userResult.data.user.name || void 0,
|
|
154
|
-
createdAt: userResult.data.user.createdAt,
|
|
155
|
-
updatedAt: userResult.data.user.updatedAt,
|
|
156
|
-
...userResult.data.profile
|
|
157
|
-
// Include profile fields (nickname, avatar_url, etc.)
|
|
158
|
-
};
|
|
159
|
-
const sessionData = {
|
|
160
|
-
userId: userResult.data.user.id,
|
|
161
|
-
token: sdkResult.data.accessToken || "",
|
|
162
|
-
expiresAt: "",
|
|
163
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
164
|
-
};
|
|
165
|
-
setUser(userData);
|
|
166
|
-
setSession(sessionData);
|
|
167
|
-
localStorage.setItem("insforge-user-profile", JSON.stringify(userResult.data));
|
|
168
|
-
if (onAuthChange) {
|
|
169
|
-
onAuthChange(userData);
|
|
170
|
-
}
|
|
171
|
-
try {
|
|
172
|
-
await syncTokenToCookie(sdkResult.data.accessToken || "");
|
|
173
|
-
} catch (error) {
|
|
174
|
-
console.error("Please add /api/auth route to your server to sync token to cookie:", error);
|
|
175
|
-
}
|
|
176
|
-
} else {
|
|
177
|
-
const userData = {
|
|
207
|
+
await handleAuthSuccess(
|
|
208
|
+
sdkResult.data.accessToken || "",
|
|
209
|
+
sdkResult.data.user ? {
|
|
178
210
|
id: sdkResult.data.user.id,
|
|
179
211
|
email: sdkResult.data.user.email,
|
|
180
|
-
name: sdkResult.data.user.name
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
};
|
|
184
|
-
setUser(userData);
|
|
185
|
-
setSession({
|
|
186
|
-
userId: sdkResult.data.user.id,
|
|
187
|
-
token: sdkResult.data.accessToken || "",
|
|
188
|
-
expiresAt: "",
|
|
189
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
190
|
-
});
|
|
191
|
-
if (onAuthChange) {
|
|
192
|
-
onAuthChange(userData);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
212
|
+
name: sdkResult.data.user.name
|
|
213
|
+
} : void 0
|
|
214
|
+
);
|
|
195
215
|
} else {
|
|
196
216
|
const errorMessage = sdkResult.error?.message || "Invalid email or password";
|
|
197
217
|
throw new Error(errorMessage);
|
|
198
218
|
}
|
|
199
219
|
},
|
|
200
|
-
[insforge,
|
|
220
|
+
[insforge, handleAuthSuccess]
|
|
201
221
|
);
|
|
202
222
|
const signUp = useCallback(
|
|
203
223
|
async (email, password) => {
|
|
204
224
|
const sdkResult = await insforge.auth.signUp({ email, password });
|
|
205
225
|
if (sdkResult.data) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
id: userResult.data.user.id,
|
|
210
|
-
email: userResult.data.user.email,
|
|
211
|
-
name: userResult.data.user.name || void 0,
|
|
212
|
-
createdAt: userResult.data.user.createdAt,
|
|
213
|
-
updatedAt: userResult.data.user.updatedAt,
|
|
214
|
-
...userResult.data.profile
|
|
215
|
-
// Include profile fields (nickname, avatar_url, etc.)
|
|
216
|
-
};
|
|
217
|
-
const sessionData = {
|
|
218
|
-
userId: userResult.data.user.id,
|
|
219
|
-
token: sdkResult.data.accessToken || "",
|
|
220
|
-
expiresAt: "",
|
|
221
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
222
|
-
};
|
|
223
|
-
setUser(userData);
|
|
224
|
-
setSession(sessionData);
|
|
225
|
-
localStorage.setItem("insforge-user-profile", JSON.stringify(userResult.data));
|
|
226
|
-
if (onAuthChange) {
|
|
227
|
-
onAuthChange(userData);
|
|
228
|
-
}
|
|
229
|
-
try {
|
|
230
|
-
await syncTokenToCookie(sdkResult.data.accessToken || "");
|
|
231
|
-
} catch (error) {
|
|
232
|
-
}
|
|
233
|
-
} else {
|
|
234
|
-
const userData = {
|
|
226
|
+
await handleAuthSuccess(
|
|
227
|
+
sdkResult.data.accessToken || "",
|
|
228
|
+
sdkResult.data.user ? {
|
|
235
229
|
id: sdkResult.data.user.id,
|
|
236
230
|
email: sdkResult.data.user.email,
|
|
237
|
-
name: sdkResult.data.user.name
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
};
|
|
241
|
-
setUser(userData);
|
|
242
|
-
setSession({
|
|
243
|
-
userId: sdkResult.data.user.id,
|
|
244
|
-
token: sdkResult.data.accessToken || "",
|
|
245
|
-
expiresAt: "",
|
|
246
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
247
|
-
});
|
|
248
|
-
if (onAuthChange) {
|
|
249
|
-
onAuthChange(userData);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
231
|
+
name: sdkResult.data.user.name
|
|
232
|
+
} : void 0
|
|
233
|
+
);
|
|
252
234
|
} else {
|
|
253
235
|
const errorMessage = sdkResult.error?.message || "Sign up failed";
|
|
254
236
|
throw new Error(errorMessage);
|
|
255
237
|
}
|
|
256
238
|
},
|
|
257
|
-
[insforge,
|
|
239
|
+
[insforge, handleAuthSuccess]
|
|
258
240
|
);
|
|
259
241
|
const signOut = useCallback(async () => {
|
|
260
242
|
await insforge.auth.signOut();
|
|
@@ -273,7 +255,10 @@ function InsforgeProvider({
|
|
|
273
255
|
const updateUser = useCallback(
|
|
274
256
|
async (data) => {
|
|
275
257
|
if (!user) throw new Error("No user signed in");
|
|
276
|
-
const result = await insforge.auth.setProfile(
|
|
258
|
+
const result = await insforge.auth.setProfile({
|
|
259
|
+
nickname: data.name || "",
|
|
260
|
+
avatar_url: data.avatarUrl || ""
|
|
261
|
+
});
|
|
277
262
|
if (result.data) {
|
|
278
263
|
const updatedUser = { ...user, ...result.data };
|
|
279
264
|
setUser(updatedUser);
|
|
@@ -336,8 +321,8 @@ function useSession() {
|
|
|
336
321
|
}
|
|
337
322
|
|
|
338
323
|
// src/components/SignIn.tsx
|
|
339
|
-
import { useState as
|
|
340
|
-
import { createClient as
|
|
324
|
+
import { useState as useState5 } from "react";
|
|
325
|
+
import { createClient as createClient4 } from "@insforge/sdk";
|
|
341
326
|
|
|
342
327
|
// src/hooks/useOAuthProviders.ts
|
|
343
328
|
import { useState as useState2, useEffect as useEffect2 } from "react";
|
|
@@ -357,8 +342,8 @@ function useOAuthProviders() {
|
|
|
357
342
|
console.warn("[useOAuthProviders] Failed to fetch OAuth providers:", error);
|
|
358
343
|
setProviders([]);
|
|
359
344
|
} else if (data) {
|
|
360
|
-
const
|
|
361
|
-
setProviders(
|
|
345
|
+
const providerNames = data.map((p) => p.provider);
|
|
346
|
+
setProviders(providerNames);
|
|
362
347
|
} else {
|
|
363
348
|
setProviders([]);
|
|
364
349
|
}
|
|
@@ -379,6 +364,43 @@ function useOAuthProviders() {
|
|
|
379
364
|
return { providers, isLoaded };
|
|
380
365
|
}
|
|
381
366
|
|
|
367
|
+
// src/hooks/useEmailAuthConfig.ts
|
|
368
|
+
import { useState as useState3, useEffect as useEffect3 } from "react";
|
|
369
|
+
import { createClient as createClient3 } from "@insforge/sdk";
|
|
370
|
+
function useEmailAuthConfig() {
|
|
371
|
+
const { baseUrl } = useInsforge();
|
|
372
|
+
const [config, setConfig] = useState3(null);
|
|
373
|
+
const [isLoaded, setIsLoaded] = useState3(false);
|
|
374
|
+
useEffect3(() => {
|
|
375
|
+
let mounted = true;
|
|
376
|
+
async function fetchConfig() {
|
|
377
|
+
try {
|
|
378
|
+
const insforge = createClient3({ baseUrl });
|
|
379
|
+
const { data, error } = await insforge.auth.getEmailAuthConfig();
|
|
380
|
+
if (!mounted) return;
|
|
381
|
+
if (error) {
|
|
382
|
+
console.warn("[useEmailAuthConfig] Failed to fetch email auth config:", error);
|
|
383
|
+
setConfig(null);
|
|
384
|
+
} else {
|
|
385
|
+
setConfig(data);
|
|
386
|
+
}
|
|
387
|
+
setIsLoaded(true);
|
|
388
|
+
} catch (error) {
|
|
389
|
+
console.warn("[useEmailAuthConfig] Unexpected error:", error);
|
|
390
|
+
if (mounted) {
|
|
391
|
+
setConfig(null);
|
|
392
|
+
setIsLoaded(true);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
fetchConfig();
|
|
397
|
+
return () => {
|
|
398
|
+
mounted = false;
|
|
399
|
+
};
|
|
400
|
+
}, [baseUrl]);
|
|
401
|
+
return { config, isLoaded };
|
|
402
|
+
}
|
|
403
|
+
|
|
382
404
|
// src/components/auth/AuthBranding.tsx
|
|
383
405
|
import Link from "next/link";
|
|
384
406
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
@@ -489,35 +511,59 @@ function AuthFormField({ label, id, className = "", ...props }) {
|
|
|
489
511
|
}
|
|
490
512
|
|
|
491
513
|
// src/components/auth/AuthPasswordField.tsx
|
|
492
|
-
import { useState as
|
|
514
|
+
import { useState as useState4 } from "react";
|
|
493
515
|
import { Eye, EyeOff } from "lucide-react";
|
|
494
516
|
|
|
495
517
|
// src/components/auth/AuthPasswordStrengthIndicator.tsx
|
|
496
518
|
import { Check } from "lucide-react";
|
|
497
519
|
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
}
|
|
511
|
-
{
|
|
512
|
-
|
|
513
|
-
|
|
520
|
+
function createRequirements(config) {
|
|
521
|
+
const requirements = [];
|
|
522
|
+
const minLength = config.passwordMinLength;
|
|
523
|
+
const requireUppercase = config.requireUppercase;
|
|
524
|
+
const requireLowercase = config.requireLowercase;
|
|
525
|
+
const requireNumber = config.requireNumber;
|
|
526
|
+
const requireSpecialChar = config.requireSpecialChar;
|
|
527
|
+
if (requireUppercase) {
|
|
528
|
+
requirements.push({
|
|
529
|
+
label: "At least 1 Uppercase letter",
|
|
530
|
+
test: (pwd) => /[A-Z]/.test(pwd)
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
if (requireLowercase) {
|
|
534
|
+
requirements.push({
|
|
535
|
+
label: "At least 1 Lowercase letter",
|
|
536
|
+
test: (pwd) => /[a-z]/.test(pwd)
|
|
537
|
+
});
|
|
514
538
|
}
|
|
515
|
-
|
|
516
|
-
|
|
539
|
+
if (requireNumber) {
|
|
540
|
+
requirements.push({
|
|
541
|
+
label: "At least 1 Number",
|
|
542
|
+
test: (pwd) => /\d/.test(pwd)
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
if (requireSpecialChar) {
|
|
546
|
+
requirements.push({
|
|
547
|
+
label: "Special character (e.g. !?<>@#$%)",
|
|
548
|
+
test: (pwd) => /[!@#$%^&*()_+\-=[\]{};\\|,.<>/?]/.test(pwd)
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
requirements.push({
|
|
552
|
+
label: `${minLength} characters or more`,
|
|
553
|
+
test: (pwd) => pwd.length >= minLength
|
|
554
|
+
});
|
|
555
|
+
return requirements;
|
|
556
|
+
}
|
|
557
|
+
function validatePasswordStrength(password, config) {
|
|
517
558
|
if (!password) return false;
|
|
559
|
+
const requirements = createRequirements(config);
|
|
518
560
|
return requirements.every((req) => req.test(password));
|
|
519
561
|
}
|
|
520
|
-
function AuthPasswordStrengthIndicator({
|
|
562
|
+
function AuthPasswordStrengthIndicator({
|
|
563
|
+
password,
|
|
564
|
+
config
|
|
565
|
+
}) {
|
|
566
|
+
const requirements = createRequirements(config);
|
|
521
567
|
return /* @__PURE__ */ jsx7("div", { className: "insforge-password-strength", children: requirements.map((requirement, index) => {
|
|
522
568
|
const isValid = requirement.test(password);
|
|
523
569
|
return /* @__PURE__ */ jsxs6("div", { className: "insforge-password-requirement", children: [
|
|
@@ -539,14 +585,15 @@ function AuthPasswordField({
|
|
|
539
585
|
label,
|
|
540
586
|
id,
|
|
541
587
|
showStrengthIndicator = false,
|
|
588
|
+
emailAuthConfig,
|
|
542
589
|
forgotPasswordLink,
|
|
543
590
|
value,
|
|
544
591
|
className = "",
|
|
545
592
|
onFocus,
|
|
546
593
|
...props
|
|
547
594
|
}) {
|
|
548
|
-
const [showPassword, setShowPassword] =
|
|
549
|
-
const [showStrength, setShowStrength] =
|
|
595
|
+
const [showPassword, setShowPassword] = useState4(false);
|
|
596
|
+
const [showStrength, setShowStrength] = useState4(false);
|
|
550
597
|
const handleFocus = (e) => {
|
|
551
598
|
if (showStrengthIndicator) {
|
|
552
599
|
setShowStrength(true);
|
|
@@ -581,7 +628,13 @@ function AuthPasswordField({
|
|
|
581
628
|
}
|
|
582
629
|
)
|
|
583
630
|
] }),
|
|
584
|
-
showStrengthIndicator && showStrength && /* @__PURE__ */ jsx8(
|
|
631
|
+
showStrengthIndicator && showStrength && /* @__PURE__ */ jsx8(
|
|
632
|
+
AuthPasswordStrengthIndicator,
|
|
633
|
+
{
|
|
634
|
+
password: String(value || ""),
|
|
635
|
+
config: emailAuthConfig
|
|
636
|
+
}
|
|
637
|
+
)
|
|
585
638
|
] });
|
|
586
639
|
}
|
|
587
640
|
|
|
@@ -968,12 +1021,13 @@ function SignIn({
|
|
|
968
1021
|
}) {
|
|
969
1022
|
const { signIn, baseUrl } = useInsforge();
|
|
970
1023
|
const { providers: oauthProviders } = useOAuthProviders();
|
|
971
|
-
const
|
|
972
|
-
const [
|
|
973
|
-
const [
|
|
974
|
-
const [
|
|
975
|
-
const [
|
|
976
|
-
const
|
|
1024
|
+
const { config: emailAuthConfig } = useEmailAuthConfig();
|
|
1025
|
+
const [email, setEmail] = useState5("");
|
|
1026
|
+
const [password, setPassword] = useState5("");
|
|
1027
|
+
const [error, setError] = useState5("");
|
|
1028
|
+
const [loading, setLoading] = useState5(false);
|
|
1029
|
+
const [oauthLoading, setOauthLoading] = useState5(null);
|
|
1030
|
+
const insforge = useState5(() => createClient4({ baseUrl }))[0];
|
|
977
1031
|
async function handleSubmit(e) {
|
|
978
1032
|
e.preventDefault();
|
|
979
1033
|
setLoading(true);
|
|
@@ -1035,7 +1089,15 @@ function SignIn({
|
|
|
1035
1089
|
value: password,
|
|
1036
1090
|
onChange: (e) => setPassword(e.target.value),
|
|
1037
1091
|
required: true,
|
|
1038
|
-
autoComplete: "current-password"
|
|
1092
|
+
autoComplete: "current-password",
|
|
1093
|
+
emailAuthConfig: emailAuthConfig || {
|
|
1094
|
+
requireEmailVerification: false,
|
|
1095
|
+
passwordMinLength: 6,
|
|
1096
|
+
requireNumber: false,
|
|
1097
|
+
requireLowercase: false,
|
|
1098
|
+
requireUppercase: false,
|
|
1099
|
+
requireSpecialChar: false
|
|
1100
|
+
}
|
|
1039
1101
|
}
|
|
1040
1102
|
),
|
|
1041
1103
|
/* @__PURE__ */ jsx16(
|
|
@@ -1065,8 +1127,8 @@ function SignIn({
|
|
|
1065
1127
|
}
|
|
1066
1128
|
|
|
1067
1129
|
// src/components/SignUp.tsx
|
|
1068
|
-
import { useState as
|
|
1069
|
-
import { createClient as
|
|
1130
|
+
import { useState as useState6 } from "react";
|
|
1131
|
+
import { createClient as createClient5 } from "@insforge/sdk";
|
|
1070
1132
|
import { Fragment as Fragment2, jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1071
1133
|
function SignUp({
|
|
1072
1134
|
afterSignUpUrl = "/",
|
|
@@ -1091,17 +1153,18 @@ function SignUp({
|
|
|
1091
1153
|
}) {
|
|
1092
1154
|
const { signUp, baseUrl } = useInsforge();
|
|
1093
1155
|
const { providers: oauthProviders } = useOAuthProviders();
|
|
1094
|
-
const
|
|
1095
|
-
const [
|
|
1096
|
-
const [
|
|
1097
|
-
const [
|
|
1098
|
-
const [
|
|
1099
|
-
const
|
|
1156
|
+
const { config: emailAuthConfig } = useEmailAuthConfig();
|
|
1157
|
+
const [email, setEmail] = useState6("");
|
|
1158
|
+
const [password, setPassword] = useState6("");
|
|
1159
|
+
const [error, setError] = useState6("");
|
|
1160
|
+
const [loading, setLoading] = useState6(false);
|
|
1161
|
+
const [oauthLoading, setOauthLoading] = useState6(null);
|
|
1162
|
+
const insforge = useState6(() => createClient5({ baseUrl }))[0];
|
|
1100
1163
|
async function handleCredentialsSubmit(e) {
|
|
1101
1164
|
e.preventDefault();
|
|
1102
1165
|
setLoading(true);
|
|
1103
1166
|
setError("");
|
|
1104
|
-
if (!validatePasswordStrength(password)) {
|
|
1167
|
+
if (emailAuthConfig && !validatePasswordStrength(password, emailAuthConfig)) {
|
|
1105
1168
|
setError("Password does not meet all requirements");
|
|
1106
1169
|
setLoading(false);
|
|
1107
1170
|
return;
|
|
@@ -1166,9 +1229,17 @@ function SignUp({
|
|
|
1166
1229
|
value: password,
|
|
1167
1230
|
onChange: (e) => setPassword(e.target.value),
|
|
1168
1231
|
required: true,
|
|
1169
|
-
minLength: 8,
|
|
1232
|
+
minLength: emailAuthConfig?.passwordMinLength ?? 8,
|
|
1170
1233
|
autoComplete: "new-password",
|
|
1171
|
-
showStrengthIndicator: true
|
|
1234
|
+
showStrengthIndicator: true,
|
|
1235
|
+
emailAuthConfig: emailAuthConfig || {
|
|
1236
|
+
requireEmailVerification: false,
|
|
1237
|
+
passwordMinLength: 6,
|
|
1238
|
+
requireNumber: false,
|
|
1239
|
+
requireLowercase: false,
|
|
1240
|
+
requireUppercase: false,
|
|
1241
|
+
requireSpecialChar: false
|
|
1242
|
+
}
|
|
1172
1243
|
}
|
|
1173
1244
|
),
|
|
1174
1245
|
/* @__PURE__ */ jsx17(
|
|
@@ -1205,7 +1276,7 @@ function SignUp({
|
|
|
1205
1276
|
}
|
|
1206
1277
|
|
|
1207
1278
|
// src/components/UserButton.tsx
|
|
1208
|
-
import { useState as
|
|
1279
|
+
import { useState as useState7, useRef as useRef3, useEffect as useEffect4 } from "react";
|
|
1209
1280
|
import { LogOut } from "lucide-react";
|
|
1210
1281
|
import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1211
1282
|
function UserButton({
|
|
@@ -1214,9 +1285,9 @@ function UserButton({
|
|
|
1214
1285
|
appearance = {}
|
|
1215
1286
|
}) {
|
|
1216
1287
|
const { user, signOut } = useInsforge();
|
|
1217
|
-
const [isOpen, setIsOpen] =
|
|
1288
|
+
const [isOpen, setIsOpen] = useState7(false);
|
|
1218
1289
|
const dropdownRef = useRef3(null);
|
|
1219
|
-
|
|
1290
|
+
useEffect4(() => {
|
|
1220
1291
|
function handleClickOutside(event) {
|
|
1221
1292
|
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
|
1222
1293
|
setIsOpen(false);
|
|
@@ -1281,7 +1352,7 @@ function SignedOut({ children }) {
|
|
|
1281
1352
|
}
|
|
1282
1353
|
|
|
1283
1354
|
// src/components/Protect.tsx
|
|
1284
|
-
import { useEffect as
|
|
1355
|
+
import { useEffect as useEffect5 } from "react";
|
|
1285
1356
|
import { useRouter } from "next/navigation";
|
|
1286
1357
|
import { Fragment as Fragment5, jsx as jsx21 } from "react/jsx-runtime";
|
|
1287
1358
|
function Protect({
|
|
@@ -1292,7 +1363,7 @@ function Protect({
|
|
|
1292
1363
|
}) {
|
|
1293
1364
|
const { isSignedIn, isLoaded, user } = useInsforge();
|
|
1294
1365
|
const router = useRouter();
|
|
1295
|
-
|
|
1366
|
+
useEffect5(() => {
|
|
1296
1367
|
if (isLoaded && !isSignedIn) {
|
|
1297
1368
|
router.push(redirectTo);
|
|
1298
1369
|
} else if (isLoaded && isSignedIn && condition && user) {
|