@beamarco/auth-sdk 0.1.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.
- package/README.md +163 -0
- package/dist/client.d.ts +59 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +149 -0
- package/dist/components/ProtectedRoute.d.ts +13 -0
- package/dist/components/ProtectedRoute.d.ts.map +1 -0
- package/dist/components/ProtectedRoute.js +21 -0
- package/dist/components/SignIn.d.ts +87 -0
- package/dist/components/SignIn.d.ts.map +1 -0
- package/dist/components/SignIn.js +149 -0
- package/dist/components/SignInButton.d.ts +13 -0
- package/dist/components/SignInButton.d.ts.map +1 -0
- package/dist/components/SignInButton.js +14 -0
- package/dist/components/SignUp.d.ts +79 -0
- package/dist/components/SignUp.d.ts.map +1 -0
- package/dist/components/SignUp.js +143 -0
- package/dist/components/SignUpButton.d.ts +13 -0
- package/dist/components/SignUpButton.d.ts.map +1 -0
- package/dist/components/SignUpButton.js +14 -0
- package/dist/components/SignedIn.d.ts +9 -0
- package/dist/components/SignedIn.d.ts.map +1 -0
- package/dist/components/SignedIn.js +13 -0
- package/dist/components/SignedOut.d.ts +9 -0
- package/dist/components/SignedOut.d.ts.map +1 -0
- package/dist/components/SignedOut.js +13 -0
- package/dist/components/UserButton.d.ts +13 -0
- package/dist/components/UserButton.d.ts.map +1 -0
- package/dist/components/UserButton.js +64 -0
- package/dist/context.d.ts +29 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +88 -0
- package/dist/hooks.d.ts +13 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +22 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# @beamarco/auth-sdk
|
|
2
|
+
|
|
3
|
+
Embed authentication like Clerk in your React apps. Works with the Beamar auth-service backend.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @beamarco/auth-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
### 1. Wrap your app with BeamarAuthProvider
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { BeamarAuthProvider } from '@beamarco/auth-sdk'
|
|
17
|
+
|
|
18
|
+
const config = {
|
|
19
|
+
apiUrl: 'https://auth.creztu.com', // or your auth-service URL
|
|
20
|
+
appName: 'my-app', // e.g. GOMEDI, developers-portal
|
|
21
|
+
googleClientId: 'xxx.apps.googleusercontent.com', // optional, for Google Sign In
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function App() {
|
|
25
|
+
return (
|
|
26
|
+
<BeamarAuthProvider config={config}>
|
|
27
|
+
<YourApp />
|
|
28
|
+
</BeamarAuthProvider>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. Use SignedIn / SignedOut for conditional rendering
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { SignedIn, SignedOut, SignIn, SignUp } from '@beamarco/auth-sdk'
|
|
37
|
+
|
|
38
|
+
function App() {
|
|
39
|
+
return (
|
|
40
|
+
<>
|
|
41
|
+
<SignedIn>
|
|
42
|
+
<Dashboard />
|
|
43
|
+
</SignedIn>
|
|
44
|
+
<SignedOut>
|
|
45
|
+
<SignIn signUpUrl="/sign-up" />
|
|
46
|
+
</SignedOut>
|
|
47
|
+
</>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 3. Add routes (e.g. with React Router)
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import { Routes, Route, Navigate } from 'react-router-dom'
|
|
56
|
+
import {
|
|
57
|
+
BeamarAuthProvider,
|
|
58
|
+
SignIn,
|
|
59
|
+
SignUp,
|
|
60
|
+
SignedIn,
|
|
61
|
+
SignedOut,
|
|
62
|
+
useAuth,
|
|
63
|
+
} from '@beamarco/auth-sdk'
|
|
64
|
+
|
|
65
|
+
function ProtectedRoute({ children }) {
|
|
66
|
+
const { isLoaded, isSignedIn } = useAuth()
|
|
67
|
+
if (!isLoaded) return <Spinner />
|
|
68
|
+
if (!isSignedIn) return <Navigate to="/sign-in" replace />
|
|
69
|
+
return children
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function AuthRoute({ children }) {
|
|
73
|
+
const { isLoaded, isSignedIn } = useAuth()
|
|
74
|
+
if (!isLoaded) return <Spinner />
|
|
75
|
+
if (isSignedIn) return <Navigate to="/" replace />
|
|
76
|
+
return children
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function App() {
|
|
80
|
+
return (
|
|
81
|
+
<BeamarAuthProvider config={config}>
|
|
82
|
+
<Routes>
|
|
83
|
+
<Route path="/" element={
|
|
84
|
+
<ProtectedRoute><Home /></ProtectedRoute>
|
|
85
|
+
} />
|
|
86
|
+
<Route path="/sign-in" element={
|
|
87
|
+
<AuthRoute><SignIn /></AuthRoute>
|
|
88
|
+
} />
|
|
89
|
+
<Route path="/sign-up" element={
|
|
90
|
+
<AuthRoute><SignUp /></AuthRoute>
|
|
91
|
+
} />
|
|
92
|
+
</Routes>
|
|
93
|
+
</BeamarAuthProvider>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## API
|
|
99
|
+
|
|
100
|
+
### Components
|
|
101
|
+
|
|
102
|
+
| Component | Description |
|
|
103
|
+
|-----------|-------------|
|
|
104
|
+
| `BeamarAuthProvider` | Wrap your app; provides auth context |
|
|
105
|
+
| `SignIn` | Email/password + Google sign-in form |
|
|
106
|
+
| `SignUp` | Email/password + Google sign-up form |
|
|
107
|
+
| `SignedIn` | Renders children only when signed in |
|
|
108
|
+
| `SignedOut` | Renders children only when signed out |
|
|
109
|
+
| `SignInButton` | Link/button to sign-in page |
|
|
110
|
+
| `SignUpButton` | Link/button to sign-up page |
|
|
111
|
+
| `UserButton` | User avatar/menu with sign out |
|
|
112
|
+
| `ProtectedRoute` | Redirects to sign-in when not authenticated |
|
|
113
|
+
|
|
114
|
+
### Hooks
|
|
115
|
+
|
|
116
|
+
| Hook | Description |
|
|
117
|
+
|------|-------------|
|
|
118
|
+
| `useAuth()` | Full auth context (signIn, signOut, user, etc.) |
|
|
119
|
+
| `useUser()` | Current user and loading state |
|
|
120
|
+
|
|
121
|
+
### Config
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
interface BeamarAuthConfig {
|
|
125
|
+
apiUrl: string // auth-service base URL
|
|
126
|
+
appName: string // application name (multi-tenant)
|
|
127
|
+
googleClientId?: string // for Google OAuth
|
|
128
|
+
parentUrl?: string // for password reset redirect
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## CORS & Cookies
|
|
133
|
+
|
|
134
|
+
The auth-service uses **httpOnly cookies** for sessions. Ensure:
|
|
135
|
+
|
|
136
|
+
1. **Same origin** or **CORS with credentials**: Your frontend and auth-service must allow `credentials: 'include'`.
|
|
137
|
+
2. **auth-service CORS**: Set `APP_CORS` env var to include your frontend origin(s).
|
|
138
|
+
3. **Cookie domain**: In production, auth-service may set `domain` for cross-subdomain cookies (e.g. `.creztu.com`).
|
|
139
|
+
|
|
140
|
+
## Developers Portal & Creztu
|
|
141
|
+
|
|
142
|
+
To protect developers-portal or embed auth in the Creztu portal:
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
// developers-portal - replace Clerk with Beamar Auth
|
|
146
|
+
import { BeamarAuthProvider, SignIn, SignedIn, SignedOut, UserButton } from '@beamarco/auth-sdk'
|
|
147
|
+
|
|
148
|
+
const config = {
|
|
149
|
+
apiUrl: process.env.VITE_AUTH_API_URL,
|
|
150
|
+
appName: 'developers-portal',
|
|
151
|
+
googleClientId: process.env.VITE_GOOGLE_CLIENT_ID,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
<BeamarAuthProvider config={config}>
|
|
155
|
+
<SignedIn>
|
|
156
|
+
<UserButton />
|
|
157
|
+
<MainContent />
|
|
158
|
+
</SignedIn>
|
|
159
|
+
<SignedOut>
|
|
160
|
+
<SignIn />
|
|
161
|
+
</SignedOut>
|
|
162
|
+
</BeamarAuthProvider>
|
|
163
|
+
```
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { User, LoginCredentials, RegisterCredentials, GoogleCredential } from './types';
|
|
2
|
+
export interface BeamarAuthClientOptions {
|
|
3
|
+
apiUrl: string;
|
|
4
|
+
appName: string;
|
|
5
|
+
parentUrl?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Low-level client for Beamar auth-service API.
|
|
9
|
+
* Uses credentials: 'include' for cookie-based sessions.
|
|
10
|
+
*/
|
|
11
|
+
export declare class BeamarAuthClient {
|
|
12
|
+
private apiUrl;
|
|
13
|
+
private appName;
|
|
14
|
+
private parentUrl;
|
|
15
|
+
constructor(options: BeamarAuthClientOptions);
|
|
16
|
+
private request;
|
|
17
|
+
/**
|
|
18
|
+
* Login with email and password (Basic auth)
|
|
19
|
+
*/
|
|
20
|
+
login(credentials: LoginCredentials): Promise<User>;
|
|
21
|
+
/**
|
|
22
|
+
* Register new account with email and password
|
|
23
|
+
*/
|
|
24
|
+
register(credentials: RegisterCredentials): Promise<{
|
|
25
|
+
message: string;
|
|
26
|
+
}>;
|
|
27
|
+
/**
|
|
28
|
+
* Logout (clears session cookie)
|
|
29
|
+
*/
|
|
30
|
+
logout(): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Check if email exists
|
|
33
|
+
*/
|
|
34
|
+
validateEmail(loginId: string): Promise<boolean>;
|
|
35
|
+
/**
|
|
36
|
+
* Get current session - returns user if logged in
|
|
37
|
+
* Uses GET /user/preference which returns user info when cookie is valid
|
|
38
|
+
*/
|
|
39
|
+
getSession(): Promise<User | null>;
|
|
40
|
+
/**
|
|
41
|
+
* Login with Google OAuth credential
|
|
42
|
+
*/
|
|
43
|
+
loginWithGoogle(credential: GoogleCredential): Promise<User>;
|
|
44
|
+
/**
|
|
45
|
+
* Register with Google OAuth credential
|
|
46
|
+
*/
|
|
47
|
+
registerWithGoogle(credential: GoogleCredential): Promise<{
|
|
48
|
+
message: string;
|
|
49
|
+
}>;
|
|
50
|
+
/**
|
|
51
|
+
* Request password reset email
|
|
52
|
+
*/
|
|
53
|
+
requestPasswordReset(loginId: string): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Update password with reset token
|
|
56
|
+
*/
|
|
57
|
+
updatePassword(key: string, password: string): Promise<void>;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAG5F,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;GAGG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,SAAS,CAAQ;gBAEb,OAAO,EAAE,uBAAuB;YAM9B,OAAO;IAoCrB;;OAEG;IACG,KAAK,CAAC,WAAW,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAWzD;;OAEG;IACG,QAAQ,CAAC,WAAW,EAAE,mBAAmB,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAW9E;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7B;;OAEG;IACG,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQtD;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAwBxC;;OAEG;IACG,eAAe,CAAC,UAAU,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAWlE;;OAEG;IACG,kBAAkB,CAAC,UAAU,EAAE,gBAAgB,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAYpF;;OAEG;IACG,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ1D;;OAEG;IACG,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAMnE"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { AuthError } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Low-level client for Beamar auth-service API.
|
|
4
|
+
* Uses credentials: 'include' for cookie-based sessions.
|
|
5
|
+
*/
|
|
6
|
+
export class BeamarAuthClient {
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.apiUrl = options.apiUrl.replace(/\/$/, '');
|
|
9
|
+
this.appName = options.appName;
|
|
10
|
+
this.parentUrl = options.parentUrl ?? (typeof window !== 'undefined' ? window.location.origin : '');
|
|
11
|
+
}
|
|
12
|
+
async request(path, options = {}) {
|
|
13
|
+
const { params, ...fetchOptions } = options;
|
|
14
|
+
const url = new URL(`${this.apiUrl}${path}`);
|
|
15
|
+
if (params) {
|
|
16
|
+
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
|
|
17
|
+
}
|
|
18
|
+
const res = await fetch(url.toString(), {
|
|
19
|
+
...fetchOptions,
|
|
20
|
+
credentials: 'include',
|
|
21
|
+
headers: {
|
|
22
|
+
'Content-Type': 'application/json',
|
|
23
|
+
...fetchOptions.headers,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
if (!res.ok) {
|
|
27
|
+
const body = await res.json().catch(() => ({}));
|
|
28
|
+
const message = body?.message ??
|
|
29
|
+
body?.error ??
|
|
30
|
+
res.statusText ??
|
|
31
|
+
'Request failed';
|
|
32
|
+
throw new AuthError(message, res.status, body?.code);
|
|
33
|
+
}
|
|
34
|
+
if (res.status === 204 || res.headers.get('content-length') === '0') {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
return res.json();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Login with email and password (Basic auth)
|
|
41
|
+
*/
|
|
42
|
+
async login(credentials) {
|
|
43
|
+
const basic = btoa(`${credentials.email}:${credentials.password}`);
|
|
44
|
+
return this.request('/auth/login', {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
params: { appName: this.appName },
|
|
47
|
+
headers: {
|
|
48
|
+
Authorization: `Basic ${basic}`,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Register new account with email and password
|
|
54
|
+
*/
|
|
55
|
+
async register(credentials) {
|
|
56
|
+
const res = await this.request('/auth/register', {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
body: JSON.stringify({
|
|
59
|
+
email: credentials.email,
|
|
60
|
+
password: credentials.password,
|
|
61
|
+
}),
|
|
62
|
+
});
|
|
63
|
+
return { message: res.message ?? 'Account created' };
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Logout (clears session cookie)
|
|
67
|
+
*/
|
|
68
|
+
async logout() {
|
|
69
|
+
await this.request('/auth/logout', { method: 'GET' });
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Check if email exists
|
|
73
|
+
*/
|
|
74
|
+
async validateEmail(loginId) {
|
|
75
|
+
const res = await this.request('/auth/validate', {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
body: JSON.stringify({ loginId }),
|
|
78
|
+
});
|
|
79
|
+
return res === true;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get current session - returns user if logged in
|
|
83
|
+
* Uses GET /user/preference which returns user info when cookie is valid
|
|
84
|
+
*/
|
|
85
|
+
async getSession() {
|
|
86
|
+
try {
|
|
87
|
+
const res = await this.request('/user/preference', { method: 'GET' });
|
|
88
|
+
if (!res || !res.email)
|
|
89
|
+
return null;
|
|
90
|
+
return {
|
|
91
|
+
id: res.id ?? '',
|
|
92
|
+
loginId: res.email,
|
|
93
|
+
displayName: res.displayName,
|
|
94
|
+
imageUrl: res.imageUrl,
|
|
95
|
+
cellphone: res.cellphone,
|
|
96
|
+
tenants: [],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Login with Google OAuth credential
|
|
105
|
+
*/
|
|
106
|
+
async loginWithGoogle(credential) {
|
|
107
|
+
return this.request('/goauth/login', {
|
|
108
|
+
method: 'POST',
|
|
109
|
+
params: { appName: this.appName },
|
|
110
|
+
body: JSON.stringify({
|
|
111
|
+
tokenId: credential.credential,
|
|
112
|
+
googleId: credential.clientId,
|
|
113
|
+
}),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Register with Google OAuth credential
|
|
118
|
+
*/
|
|
119
|
+
async registerWithGoogle(credential) {
|
|
120
|
+
const res = await this.request('/goauth/register', {
|
|
121
|
+
method: 'POST',
|
|
122
|
+
params: { appName: this.appName },
|
|
123
|
+
body: JSON.stringify({
|
|
124
|
+
tokenId: credential.credential,
|
|
125
|
+
googleId: credential.clientId,
|
|
126
|
+
}),
|
|
127
|
+
});
|
|
128
|
+
return { message: res.message ?? 'Account created' };
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Request password reset email
|
|
132
|
+
*/
|
|
133
|
+
async requestPasswordReset(loginId) {
|
|
134
|
+
await this.request('/auth/password-request', {
|
|
135
|
+
method: 'POST',
|
|
136
|
+
params: { parentUrl: this.parentUrl, appName: this.appName },
|
|
137
|
+
body: JSON.stringify({ loginId }),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Update password with reset token
|
|
142
|
+
*/
|
|
143
|
+
async updatePassword(key, password) {
|
|
144
|
+
await this.request('/auth/password-request', {
|
|
145
|
+
method: 'PUT',
|
|
146
|
+
body: JSON.stringify({ key, password }),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface ProtectedRouteProps {
|
|
3
|
+
children: React.ReactNode;
|
|
4
|
+
/** Redirect to this path when not signed in */
|
|
5
|
+
fallbackRedirectUrl?: string;
|
|
6
|
+
/** Custom loading component */
|
|
7
|
+
loading?: React.ReactNode;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Protects routes - redirects to sign-in when not authenticated
|
|
11
|
+
*/
|
|
12
|
+
export declare function ProtectedRoute({ children, fallbackRedirectUrl, loading, }: ProtectedRouteProps): import("react/jsx-runtime").JSX.Element | null;
|
|
13
|
+
//# sourceMappingURL=ProtectedRoute.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProtectedRoute.d.ts","sourceRoot":"","sources":["../../src/components/ProtectedRoute.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,+CAA+C;IAC/C,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,+BAA+B;IAC/B,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,mBAAgC,EAChC,OAAO,GACR,EAAE,mBAAmB,kDAqBrB"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useBeamarAuth } from '../hooks';
|
|
3
|
+
/**
|
|
4
|
+
* Protects routes - redirects to sign-in when not authenticated
|
|
5
|
+
*/
|
|
6
|
+
export function ProtectedRoute({ children, fallbackRedirectUrl = '/sign-in', loading, }) {
|
|
7
|
+
const { isLoaded, isSignedIn } = useBeamarAuth();
|
|
8
|
+
if (!isLoaded) {
|
|
9
|
+
if (loading)
|
|
10
|
+
return _jsx(_Fragment, { children: loading });
|
|
11
|
+
return (_jsx("div", { style: { display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '200px' }, children: "Loading..." }));
|
|
12
|
+
}
|
|
13
|
+
if (!isSignedIn) {
|
|
14
|
+
const to = fallbackRedirectUrl
|
|
15
|
+
? `${fallbackRedirectUrl}?redirect_url=${encodeURIComponent(window.location.pathname + window.location.search)}`
|
|
16
|
+
: fallbackRedirectUrl;
|
|
17
|
+
window.location.href = to;
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return _jsx(_Fragment, { children: children });
|
|
21
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { LoginCredentials } from '../types';
|
|
3
|
+
declare const defaultStyles: {
|
|
4
|
+
container: {
|
|
5
|
+
display: string;
|
|
6
|
+
flexDirection: "column";
|
|
7
|
+
gap: string;
|
|
8
|
+
maxWidth: string;
|
|
9
|
+
padding: string;
|
|
10
|
+
fontFamily: string;
|
|
11
|
+
};
|
|
12
|
+
input: {
|
|
13
|
+
padding: string;
|
|
14
|
+
fontSize: string;
|
|
15
|
+
border: string;
|
|
16
|
+
borderRadius: string;
|
|
17
|
+
width: string;
|
|
18
|
+
boxSizing: "border-box";
|
|
19
|
+
};
|
|
20
|
+
button: {
|
|
21
|
+
padding: string;
|
|
22
|
+
fontSize: string;
|
|
23
|
+
fontWeight: number;
|
|
24
|
+
border: string;
|
|
25
|
+
borderRadius: string;
|
|
26
|
+
cursor: string;
|
|
27
|
+
backgroundColor: string;
|
|
28
|
+
color: string;
|
|
29
|
+
};
|
|
30
|
+
buttonGoogle: {
|
|
31
|
+
padding: string;
|
|
32
|
+
fontSize: string;
|
|
33
|
+
border: string;
|
|
34
|
+
borderRadius: string;
|
|
35
|
+
cursor: string;
|
|
36
|
+
backgroundColor: string;
|
|
37
|
+
color: string;
|
|
38
|
+
};
|
|
39
|
+
error: {
|
|
40
|
+
color: string;
|
|
41
|
+
fontSize: string;
|
|
42
|
+
};
|
|
43
|
+
link: {
|
|
44
|
+
color: string;
|
|
45
|
+
cursor: string;
|
|
46
|
+
textDecoration: string;
|
|
47
|
+
};
|
|
48
|
+
divider: {
|
|
49
|
+
display: string;
|
|
50
|
+
alignItems: string;
|
|
51
|
+
gap: string;
|
|
52
|
+
color: string;
|
|
53
|
+
};
|
|
54
|
+
dividerLine: {
|
|
55
|
+
flex: number;
|
|
56
|
+
height: number;
|
|
57
|
+
backgroundColor: string;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
export interface SignInProps {
|
|
61
|
+
/** Redirect path after sign in (optional - caller handles navigation) */
|
|
62
|
+
afterSignInUrl?: string;
|
|
63
|
+
/** Custom styles */
|
|
64
|
+
styles?: Partial<typeof defaultStyles>;
|
|
65
|
+
/** Show Google sign in button */
|
|
66
|
+
showGoogle?: boolean;
|
|
67
|
+
/** Link to sign up - e.g. /sign-up */
|
|
68
|
+
signUpUrl?: string;
|
|
69
|
+
/** Link to forgot password - e.g. /forgot-password */
|
|
70
|
+
forgotPasswordUrl?: string;
|
|
71
|
+
/** Called after successful sign in */
|
|
72
|
+
onSuccess?: () => void;
|
|
73
|
+
/** Render prop for custom layout */
|
|
74
|
+
children?: (props: SignInRenderProps) => React.ReactNode;
|
|
75
|
+
}
|
|
76
|
+
export interface SignInRenderProps {
|
|
77
|
+
signIn: (credentials: LoginCredentials) => Promise<void>;
|
|
78
|
+
signInWithGoogle: (credential: {
|
|
79
|
+
credential: string;
|
|
80
|
+
clientId: string;
|
|
81
|
+
}) => Promise<void>;
|
|
82
|
+
error: string | null;
|
|
83
|
+
isLoading: boolean;
|
|
84
|
+
}
|
|
85
|
+
export declare function SignIn({ afterSignInUrl, styles: customStyles, showGoogle, signUpUrl, forgotPasswordUrl, onSuccess, children, }: SignInProps): import("react/jsx-runtime").JSX.Element;
|
|
86
|
+
export {};
|
|
87
|
+
//# sourceMappingURL=SignIn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SignIn.d.ts","sourceRoot":"","sources":["../../src/components/SignIn.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAA;AAEvC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAEhD,QAAA,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwDlB,CAAA;AAED,MAAM,WAAW,WAAW;IAC1B,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,oBAAoB;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC,OAAO,aAAa,CAAC,CAAA;IACtC,iCAAiC;IACjC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,sDAAsD;IACtD,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,KAAK,CAAC,SAAS,CAAA;CACzD;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,CAAC,WAAW,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACxD,gBAAgB,EAAE,CAAC,UAAU,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACzF,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,SAAS,EAAE,OAAO,CAAA;CACnB;AAED,wBAAgB,MAAM,CAAC,EACrB,cAAc,EACd,MAAM,EAAE,YAAiB,EACzB,UAAiB,EACjB,SAAsB,EACtB,iBAAiB,EACjB,SAAS,EACT,QAAQ,GACT,EAAE,WAAW,2CAoHb"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import { useBeamarAuth } from '../hooks';
|
|
4
|
+
const defaultStyles = {
|
|
5
|
+
container: {
|
|
6
|
+
display: 'flex',
|
|
7
|
+
flexDirection: 'column',
|
|
8
|
+
gap: '16px',
|
|
9
|
+
maxWidth: '400px',
|
|
10
|
+
padding: '24px',
|
|
11
|
+
fontFamily: 'system-ui, sans-serif',
|
|
12
|
+
},
|
|
13
|
+
input: {
|
|
14
|
+
padding: '10px 12px',
|
|
15
|
+
fontSize: '16px',
|
|
16
|
+
border: '1px solid #ccc',
|
|
17
|
+
borderRadius: '6px',
|
|
18
|
+
width: '100%',
|
|
19
|
+
boxSizing: 'border-box',
|
|
20
|
+
},
|
|
21
|
+
button: {
|
|
22
|
+
padding: '12px 24px',
|
|
23
|
+
fontSize: '16px',
|
|
24
|
+
fontWeight: 600,
|
|
25
|
+
border: 'none',
|
|
26
|
+
borderRadius: '6px',
|
|
27
|
+
cursor: 'pointer',
|
|
28
|
+
backgroundColor: '#0066cc',
|
|
29
|
+
color: 'white',
|
|
30
|
+
},
|
|
31
|
+
buttonGoogle: {
|
|
32
|
+
padding: '12px 24px',
|
|
33
|
+
fontSize: '16px',
|
|
34
|
+
border: '1px solid #ccc',
|
|
35
|
+
borderRadius: '6px',
|
|
36
|
+
cursor: 'pointer',
|
|
37
|
+
backgroundColor: 'white',
|
|
38
|
+
color: '#333',
|
|
39
|
+
},
|
|
40
|
+
error: {
|
|
41
|
+
color: '#c00',
|
|
42
|
+
fontSize: '14px',
|
|
43
|
+
},
|
|
44
|
+
link: {
|
|
45
|
+
color: '#0066cc',
|
|
46
|
+
cursor: 'pointer',
|
|
47
|
+
textDecoration: 'none',
|
|
48
|
+
},
|
|
49
|
+
divider: {
|
|
50
|
+
display: 'flex',
|
|
51
|
+
alignItems: 'center',
|
|
52
|
+
gap: '12px',
|
|
53
|
+
color: '#666',
|
|
54
|
+
},
|
|
55
|
+
dividerLine: {
|
|
56
|
+
flex: 1,
|
|
57
|
+
height: 1,
|
|
58
|
+
backgroundColor: '#ddd',
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
export function SignIn({ afterSignInUrl, styles: customStyles = {}, showGoogle = true, signUpUrl = '/sign-up', forgotPasswordUrl, onSuccess, children, }) {
|
|
62
|
+
const { signIn, signInWithGoogle, googleClientId } = useBeamarAuth();
|
|
63
|
+
const [email, setEmail] = useState('');
|
|
64
|
+
const [password, setPassword] = useState('');
|
|
65
|
+
const [error, setError] = useState(null);
|
|
66
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
67
|
+
const styles = { ...defaultStyles, ...customStyles };
|
|
68
|
+
const handleSubmit = async (e) => {
|
|
69
|
+
e.preventDefault();
|
|
70
|
+
setError(null);
|
|
71
|
+
setIsLoading(true);
|
|
72
|
+
try {
|
|
73
|
+
await signIn({ email, password });
|
|
74
|
+
onSuccess?.();
|
|
75
|
+
if (afterSignInUrl) {
|
|
76
|
+
window.location.href = afterSignInUrl;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
setError(err instanceof Error ? err.message : 'Sign in failed');
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
setIsLoading(false);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const handleGoogleSuccess = async (credential) => {
|
|
87
|
+
setError(null);
|
|
88
|
+
setIsLoading(true);
|
|
89
|
+
try {
|
|
90
|
+
await signInWithGoogle(credential);
|
|
91
|
+
onSuccess?.();
|
|
92
|
+
if (afterSignInUrl) {
|
|
93
|
+
window.location.href = afterSignInUrl;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
setError(err instanceof Error ? err.message : 'Sign in with Google failed');
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
setIsLoading(false);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
if (children) {
|
|
104
|
+
return (_jsx(_Fragment, { children: children({
|
|
105
|
+
signIn: async (creds) => {
|
|
106
|
+
setError(null);
|
|
107
|
+
setIsLoading(true);
|
|
108
|
+
try {
|
|
109
|
+
await signIn(creds);
|
|
110
|
+
onSuccess?.();
|
|
111
|
+
if (afterSignInUrl)
|
|
112
|
+
window.location.href = afterSignInUrl;
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
setError(err instanceof Error ? err.message : 'Sign in failed');
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
setIsLoading(false);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
signInWithGoogle: handleGoogleSuccess,
|
|
123
|
+
error,
|
|
124
|
+
isLoading,
|
|
125
|
+
}) }));
|
|
126
|
+
}
|
|
127
|
+
return (_jsxs("form", { onSubmit: handleSubmit, style: styles.container, children: [error && _jsx("div", { style: styles.error, children: error }), _jsx("input", { type: "email", placeholder: "Email", value: email, onChange: (e) => setEmail(e.target.value), required: true, disabled: isLoading, style: styles.input }), _jsx("input", { type: "password", placeholder: "Password", value: password, onChange: (e) => setPassword(e.target.value), required: true, disabled: isLoading, style: styles.input }), forgotPasswordUrl && (_jsx("a", { href: forgotPasswordUrl, style: styles.link, children: "Forgot password?" })), _jsx("button", { type: "submit", disabled: isLoading, style: styles.button, children: isLoading ? 'Signing in...' : 'Sign in' }), showGoogle && (_jsxs(_Fragment, { children: [_jsxs("div", { style: styles.divider, children: [_jsx("span", { style: styles.dividerLine }), _jsx("span", { children: "or" }), _jsx("span", { style: styles.dividerLine })] }), _jsx(GoogleSignInButton, { onSuccess: handleGoogleSuccess, disabled: isLoading, clientId: googleClientId })] })), _jsxs("p", { children: ["Don't have an account? ", _jsx("a", { href: signUpUrl, style: styles.link, children: "Sign up" })] })] }));
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Google Sign In button - requires GoogleOAuthProvider wrapper with clientId from config
|
|
131
|
+
*/
|
|
132
|
+
function GoogleSignInButton({ onSuccess, disabled, clientId, }) {
|
|
133
|
+
const [GoogleLogin, setGoogleLogin] = useState(null);
|
|
134
|
+
React.useEffect(() => {
|
|
135
|
+
import('@react-oauth/google').then((mod) => {
|
|
136
|
+
setGoogleLogin(() => mod.GoogleLogin);
|
|
137
|
+
});
|
|
138
|
+
}, []);
|
|
139
|
+
if (!clientId)
|
|
140
|
+
return null;
|
|
141
|
+
if (!GoogleLogin) {
|
|
142
|
+
return (_jsx("button", { type: "button", disabled: true, style: { ...defaultStyles.buttonGoogle, opacity: 0.7 }, children: "Loading..." }));
|
|
143
|
+
}
|
|
144
|
+
return (_jsx(GoogleLogin, { onSuccess: (res) => {
|
|
145
|
+
if (res.credential && res.clientId) {
|
|
146
|
+
onSuccess({ credential: res.credential, clientId: res.clientId });
|
|
147
|
+
}
|
|
148
|
+
}, onError: () => { }, useOneTap: false }));
|
|
149
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface SignInButtonProps {
|
|
3
|
+
children?: React.ReactNode;
|
|
4
|
+
fallbackRedirectUrl?: string;
|
|
5
|
+
signInUrl?: string;
|
|
6
|
+
style?: React.CSSProperties;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Button that redirects to sign-in page (Clerk-style)
|
|
11
|
+
*/
|
|
12
|
+
export declare function SignInButton({ children, signInUrl, fallbackRedirectUrl, style, className, }: SignInButtonProps): import("react/jsx-runtime").JSX.Element | null;
|
|
13
|
+
//# sourceMappingURL=SignInButton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SignInButton.d.ts","sourceRoot":"","sources":["../../src/components/SignInButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAA;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,EAC3B,QAAoB,EACpB,SAAsB,EACtB,mBAAmB,EACnB,KAAK,EACL,SAAS,GACV,EAAE,iBAAiB,kDAanB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useBeamarAuth } from '../hooks';
|
|
3
|
+
/**
|
|
4
|
+
* Button that redirects to sign-in page (Clerk-style)
|
|
5
|
+
*/
|
|
6
|
+
export function SignInButton({ children = 'Sign in', signInUrl = '/sign-in', fallbackRedirectUrl, style, className, }) {
|
|
7
|
+
const { isSignedIn } = useBeamarAuth();
|
|
8
|
+
const to = fallbackRedirectUrl
|
|
9
|
+
? `${signInUrl}?redirect_url=${encodeURIComponent(fallbackRedirectUrl)}`
|
|
10
|
+
: signInUrl;
|
|
11
|
+
if (isSignedIn)
|
|
12
|
+
return null;
|
|
13
|
+
return (_jsx("a", { href: to, style: style, className: className, children: children }));
|
|
14
|
+
}
|