@almadar/cli 1.6.9 → 1.8.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/package.json +6 -6
- package/shells/almadar-shell/LICENSE +21 -0
- package/shells/almadar-shell/README.md +25 -0
- package/shells/almadar-shell/package.json +28 -0
- package/shells/almadar-shell/packages/client/eslint.config.cjs +23 -0
- package/shells/almadar-shell/packages/client/index.html +13 -0
- package/shells/almadar-shell/packages/client/package.json +53 -0
- package/shells/almadar-shell/packages/client/postcss.config.js +6 -0
- package/shells/almadar-shell/packages/client/src/App.tsx +74 -0
- package/shells/almadar-shell/packages/client/src/config/firebase.ts +37 -0
- package/shells/almadar-shell/packages/client/src/features/auth/AuthContext.tsx +139 -0
- package/shells/almadar-shell/packages/client/src/features/auth/authService.ts +83 -0
- package/shells/almadar-shell/packages/client/src/features/auth/components/Login.tsx +218 -0
- package/shells/almadar-shell/packages/client/src/features/auth/components/ProtectedRoute.tsx +27 -0
- package/shells/almadar-shell/packages/client/src/features/auth/components/UserProfile.tsx +68 -0
- package/shells/almadar-shell/packages/client/src/features/auth/components/index.ts +3 -0
- package/shells/almadar-shell/packages/client/src/features/auth/index.ts +13 -0
- package/shells/almadar-shell/packages/client/src/features/auth/types.ts +24 -0
- package/shells/almadar-shell/packages/client/src/index.css +6 -0
- package/shells/almadar-shell/packages/client/src/main.tsx +10 -0
- package/shells/almadar-shell/packages/client/src/navigation/index.ts +55 -0
- package/shells/almadar-shell/packages/client/src/pages/index.ts +12 -0
- package/shells/almadar-shell/packages/client/tailwind.config.js +12 -0
- package/shells/almadar-shell/packages/client/tsconfig.json +33 -0
- package/shells/almadar-shell/packages/client/vite.config.ts +49 -0
- package/shells/almadar-shell/packages/server/eslint.config.cjs +19 -0
- package/shells/almadar-shell/packages/server/package.json +37 -0
- package/shells/almadar-shell/packages/server/src/app.ts +36 -0
- package/shells/almadar-shell/packages/server/src/index.ts +30 -0
- package/shells/almadar-shell/packages/server/src/routes.ts +11 -0
- package/shells/almadar-shell/packages/server/src/types/express.d.ts +15 -0
- package/shells/almadar-shell/packages/server/tsconfig.json +23 -0
- package/shells/almadar-shell/packages/shared/package.json +10 -0
- package/shells/almadar-shell/packages/shared/src/index.ts +2 -0
- package/shells/almadar-shell/pnpm-lock.yaml +9642 -0
- package/shells/almadar-shell/pnpm-workspace.yaml +2 -0
- package/shells/almadar-shell/tsup.config.ts +13 -0
- package/shells/almadar-shell/turbo.json +17 -0
- package/shells/almadar-shell/vitest.config.ts +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@almadar/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Almadar CLI - Compile Almadar schemas to full-stack applications",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
"node": ">=16.0.0"
|
|
50
50
|
},
|
|
51
51
|
"optionalDependencies": {
|
|
52
|
-
"@almadar/cli-darwin-x64": "1.
|
|
53
|
-
"@almadar/cli-darwin-arm64": "1.
|
|
54
|
-
"@almadar/cli-linux-x64": "1.
|
|
55
|
-
"@almadar/cli-linux-arm64": "1.
|
|
56
|
-
"@almadar/cli-windows-x64": "1.
|
|
52
|
+
"@almadar/cli-darwin-x64": "1.8.0",
|
|
53
|
+
"@almadar/cli-darwin-arm64": "1.8.0",
|
|
54
|
+
"@almadar/cli-linux-x64": "1.8.0",
|
|
55
|
+
"@almadar/cli-linux-arm64": "1.8.0",
|
|
56
|
+
"@almadar/cli-windows-x64": "1.8.0"
|
|
57
57
|
}
|
|
58
58
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Almadar Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# @almadar/shell
|
|
2
|
+
|
|
3
|
+
> Minimal full-stack shell template for Almadar applications
|
|
4
|
+
|
|
5
|
+
Part of the [Almadar](https://github.com/almadar-io/almadar) platform.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @almadar/shell
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { /* ... */ } from '@almadar/shell';
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## API
|
|
20
|
+
|
|
21
|
+
<!-- Document public exports here -->
|
|
22
|
+
|
|
23
|
+
## License
|
|
24
|
+
|
|
25
|
+
MIT
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@almadar/shell",
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Minimal full-stack shell template for Almadar applications",
|
|
6
|
+
"packageManager": "pnpm@9.15.9",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "npx concurrently -n client,server -c blue,green \"pnpm --filter @almadar/shell-client dev\" \"pnpm --filter @almadar/shell-server dev\"",
|
|
9
|
+
"build": "pnpm run -r build",
|
|
10
|
+
"typecheck": "turbo run typecheck",
|
|
11
|
+
"lint": "turbo run lint",
|
|
12
|
+
"prepare": "git config core.hooksPath .githooks"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"concurrently": "^8.2.0",
|
|
16
|
+
"turbo": "^2.0.0"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/almadar-io/almadar-shell.git"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const tsParser = require("@typescript-eslint/parser");
|
|
3
|
+
const almadarPlugin = require("@almadar/eslint-plugin");
|
|
4
|
+
|
|
5
|
+
module.exports = [
|
|
6
|
+
{ ignores: ["dist/**", "node_modules/**", "**/*.test.ts", "**/*.test.tsx"] },
|
|
7
|
+
{
|
|
8
|
+
files: ["src/**/*.ts", "src/**/*.tsx"],
|
|
9
|
+
languageOptions: {
|
|
10
|
+
parser: tsParser,
|
|
11
|
+
parserOptions: {
|
|
12
|
+
ecmaFeatures: { jsx: true },
|
|
13
|
+
ecmaVersion: "latest",
|
|
14
|
+
sourceType: "module",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
plugins: { almadar: almadarPlugin },
|
|
18
|
+
rules: {
|
|
19
|
+
"almadar/no-as-any": "error",
|
|
20
|
+
"almadar/no-import-generated": "error",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Almadar App</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@almadar/shell-client",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc && vite build",
|
|
9
|
+
"typecheck": "tsc --noEmit",
|
|
10
|
+
"lint": "eslint src/",
|
|
11
|
+
"preview": "vite preview",
|
|
12
|
+
"test": "vitest run --passWithNoTests",
|
|
13
|
+
"test:watch": "vitest"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@almadar/ui": "^2.1.9",
|
|
17
|
+
"@almadar/evaluator": "^2.0.0",
|
|
18
|
+
"@almadar/patterns": "^2.0.0",
|
|
19
|
+
"@almadar/core": "^2.1.0",
|
|
20
|
+
"firebase": "^11.4.0",
|
|
21
|
+
"@tanstack/react-query": "^5.67.3",
|
|
22
|
+
"react": "^18.3.1",
|
|
23
|
+
"react-dom": "^18.3.1",
|
|
24
|
+
"react-router-dom": "^7.3.0",
|
|
25
|
+
"zustand": "^5.0.3",
|
|
26
|
+
"react-markdown": "^9.0.1",
|
|
27
|
+
"remark-gfm": "^4.0.1",
|
|
28
|
+
"remark-math": "^6.0.0",
|
|
29
|
+
"rehype-katex": "^7.0.1",
|
|
30
|
+
"rehype-raw": "^7.0.0",
|
|
31
|
+
"react-force-graph-2d": "^1.25.5",
|
|
32
|
+
"@monaco-editor/react": "^4.7.0",
|
|
33
|
+
"monaco-editor": "^0.52.2",
|
|
34
|
+
"lucide-react": "^0.344.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@almadar/eslint-plugin": ">=2.3.0",
|
|
38
|
+
"@typescript-eslint/parser": "8.56.0",
|
|
39
|
+
"eslint": "10.0.0",
|
|
40
|
+
"@testing-library/react": "^16.1.0",
|
|
41
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
42
|
+
"@types/react": "^18.3.18",
|
|
43
|
+
"@types/react-dom": "^18.3.5",
|
|
44
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
45
|
+
"autoprefixer": "^10.4.20",
|
|
46
|
+
"jsdom": "^25.0.1",
|
|
47
|
+
"postcss": "^8.5.3",
|
|
48
|
+
"tailwindcss": "^3.4.17",
|
|
49
|
+
"typescript": "^5.7.3",
|
|
50
|
+
"vite": "^6.2.1",
|
|
51
|
+
"vitest": "^2.1.9"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App Entry Point
|
|
3
|
+
*
|
|
4
|
+
* Main application component with compiler-generated content placeholders.
|
|
5
|
+
* The Rust compiler replaces {{PLACEHOLDERS}} with generated code.
|
|
6
|
+
*
|
|
7
|
+
* Navigation works via schema-driven NavigationProvider:
|
|
8
|
+
* - NavigationProvider holds active page state
|
|
9
|
+
* - navigateTo() switches pages and fires INIT with payload
|
|
10
|
+
* - No dependency on react-router for internal navigation
|
|
11
|
+
* - react-router is optional for URL bookmarkability
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
|
15
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
16
|
+
import { ThemeProvider, UISlotProvider } from '@almadar/ui/context';
|
|
17
|
+
import {
|
|
18
|
+
EventBusProvider,
|
|
19
|
+
VerificationProvider,
|
|
20
|
+
} from '@almadar/ui/providers';
|
|
21
|
+
import { NavigationProvider } from '@almadar/ui/renderer';
|
|
22
|
+
import { I18nProvider, createTranslate } from '@almadar/ui/hooks';
|
|
23
|
+
|
|
24
|
+
// {{GENERATED_I18N_IMPORT}}
|
|
25
|
+
// {{GENERATED_IMPORTS}}
|
|
26
|
+
|
|
27
|
+
// Generated schema import (compiler fills this in)
|
|
28
|
+
// {{GENERATED_SCHEMA_IMPORT}}
|
|
29
|
+
const schema = { name: 'app', orbitals: [] }; // Placeholder - replaced by compiler
|
|
30
|
+
|
|
31
|
+
// {{GENERATED_I18N_VALUE}}
|
|
32
|
+
const i18nValue = { locale: 'en', direction: 'ltr' as const, t: createTranslate({}) };
|
|
33
|
+
|
|
34
|
+
const queryClient = new QueryClient({
|
|
35
|
+
defaultOptions: {
|
|
36
|
+
queries: {
|
|
37
|
+
staleTime: 1000 * 60,
|
|
38
|
+
refetchOnWindowFocus: false,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
function App() {
|
|
44
|
+
return (
|
|
45
|
+
<I18nProvider value={i18nValue}>
|
|
46
|
+
<QueryClientProvider client={queryClient}>
|
|
47
|
+
<ThemeProvider>
|
|
48
|
+
<EventBusProvider>
|
|
49
|
+
<VerificationProvider>
|
|
50
|
+
<UISlotProvider>
|
|
51
|
+
<NavigationProvider
|
|
52
|
+
schema={schema}
|
|
53
|
+
updateUrl={true}
|
|
54
|
+
onNavigate={(pageName, path, payload) => {
|
|
55
|
+
console.log('[App] Navigation:', { pageName, path, payload });
|
|
56
|
+
}}
|
|
57
|
+
>
|
|
58
|
+
<BrowserRouter>
|
|
59
|
+
<Routes>
|
|
60
|
+
{/* {{GENERATED_ROUTES}} */}
|
|
61
|
+
<Route path="/" element={<div>Welcome to Almadar</div>} />
|
|
62
|
+
</Routes>
|
|
63
|
+
</BrowserRouter>
|
|
64
|
+
</NavigationProvider>
|
|
65
|
+
</UISlotProvider>
|
|
66
|
+
</VerificationProvider>
|
|
67
|
+
</EventBusProvider>
|
|
68
|
+
</ThemeProvider>
|
|
69
|
+
</QueryClientProvider>
|
|
70
|
+
</I18nProvider>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export default App;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { initializeApp, getApps, getApp, FirebaseApp } from 'firebase/app';
|
|
2
|
+
import { getAuth, Auth } from 'firebase/auth';
|
|
3
|
+
|
|
4
|
+
let app: FirebaseApp;
|
|
5
|
+
let auth: Auth;
|
|
6
|
+
|
|
7
|
+
export async function initializeFirebase(): Promise<void> {
|
|
8
|
+
if (getApps().length > 0) {
|
|
9
|
+
app = getApp();
|
|
10
|
+
auth = getAuth(app);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let config;
|
|
15
|
+
try {
|
|
16
|
+
// On Firebase Hosting, fetch auto-config from reserved URL
|
|
17
|
+
const res = await fetch('/__/firebase/init.json');
|
|
18
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
19
|
+
config = await res.json();
|
|
20
|
+
} catch {
|
|
21
|
+
// Fall back to env vars for local development
|
|
22
|
+
config = {
|
|
23
|
+
apiKey: import.meta.env.VITE_APP_FIREBASE_API_KEY,
|
|
24
|
+
authDomain: import.meta.env.VITE_APP_FIREBASE_AUTH_DOMAIN,
|
|
25
|
+
projectId: import.meta.env.VITE_APP_FIREBASE_PROJECT_ID,
|
|
26
|
+
storageBucket: import.meta.env.VITE_APP_FIREBASE_STORAGE_BUCKET,
|
|
27
|
+
messagingSenderId: import.meta.env.VITE_APP_FIREBASE_MESSAGING_SENDER_ID,
|
|
28
|
+
appId: import.meta.env.VITE_APP_FIREBASE_APP_ID,
|
|
29
|
+
measurementId: import.meta.env.VITE_APP_FIREBASE_MEASUREMENT_ID,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
app = initializeApp(config);
|
|
34
|
+
auth = getAuth(app);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { auth, app };
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import React, { createContext, useContext, useEffect, useState } from 'react';
|
|
2
|
+
import { User } from 'firebase/auth';
|
|
3
|
+
import { auth, initializeFirebase } from '../../config/firebase';
|
|
4
|
+
import { authService } from './authService';
|
|
5
|
+
import { AuthContextType } from './types';
|
|
6
|
+
|
|
7
|
+
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
|
8
|
+
|
|
9
|
+
export const useAuthContext = () => {
|
|
10
|
+
const context = useContext(AuthContext);
|
|
11
|
+
if (context === undefined) {
|
|
12
|
+
throw new Error('useAuthContext must be used within an AuthProvider');
|
|
13
|
+
}
|
|
14
|
+
return context;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
interface AuthProviderProps {
|
|
18
|
+
children: React.ReactNode;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
|
22
|
+
const [user, setUser] = useState<User | null>(null);
|
|
23
|
+
const [loading, setLoading] = useState(true);
|
|
24
|
+
const [error, setError] = useState<string | null>(null);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
let unsubscribe: (() => void) | undefined;
|
|
28
|
+
|
|
29
|
+
initializeFirebase().then(() => {
|
|
30
|
+
unsubscribe = auth.onAuthStateChanged((firebaseUser) => {
|
|
31
|
+
setUser(firebaseUser);
|
|
32
|
+
setLoading(false);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return () => unsubscribe?.();
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
const clearError = () => setError(null);
|
|
40
|
+
|
|
41
|
+
const signInWithGoogle = async () => {
|
|
42
|
+
try {
|
|
43
|
+
setLoading(true);
|
|
44
|
+
clearError();
|
|
45
|
+
await authService.signInWithGoogle();
|
|
46
|
+
} catch (err: unknown) {
|
|
47
|
+
setLoading(false);
|
|
48
|
+
const firebaseErr = err as { code?: string; message?: string };
|
|
49
|
+
const isCancel =
|
|
50
|
+
firebaseErr.code === 'auth/popup-closed-by-user' ||
|
|
51
|
+
firebaseErr.code === 'auth/cancelled-popup-request' ||
|
|
52
|
+
firebaseErr.code === 'auth/popup-blocked';
|
|
53
|
+
if (!isCancel) {
|
|
54
|
+
setError(firebaseErr.message ?? 'Google sign-in failed');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const signInWithEmail = async (email: string, password: string) => {
|
|
60
|
+
try {
|
|
61
|
+
setLoading(true);
|
|
62
|
+
clearError();
|
|
63
|
+
await authService.signInWithEmail(email, password);
|
|
64
|
+
} catch (err: unknown) {
|
|
65
|
+
setLoading(false);
|
|
66
|
+
setError((err as { message?: string }).message ?? 'Sign-in failed');
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const signUpWithEmail = async (email: string, password: string, displayName?: string) => {
|
|
71
|
+
try {
|
|
72
|
+
setLoading(true);
|
|
73
|
+
clearError();
|
|
74
|
+
await authService.signUpWithEmail(email, password, displayName);
|
|
75
|
+
} catch (err: unknown) {
|
|
76
|
+
setLoading(false);
|
|
77
|
+
setError((err as { message?: string }).message ?? 'Sign-up failed');
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const sendSignInLinkToEmail = async (email: string) => {
|
|
82
|
+
try {
|
|
83
|
+
setLoading(true);
|
|
84
|
+
clearError();
|
|
85
|
+
const actionCodeSettings = {
|
|
86
|
+
url: `${window.location.origin}/login`,
|
|
87
|
+
handleCodeInApp: true,
|
|
88
|
+
};
|
|
89
|
+
await authService.sendSignInLinkToEmail(email, actionCodeSettings);
|
|
90
|
+
setLoading(false);
|
|
91
|
+
} catch (err: unknown) {
|
|
92
|
+
setLoading(false);
|
|
93
|
+
setError((err as { message?: string }).message ?? 'Failed to send sign-in link');
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const signInWithEmailLink = async (email: string, emailLink: string) => {
|
|
98
|
+
try {
|
|
99
|
+
setLoading(true);
|
|
100
|
+
clearError();
|
|
101
|
+
await authService.signInWithEmailLink(email, emailLink);
|
|
102
|
+
} catch (err: unknown) {
|
|
103
|
+
setLoading(false);
|
|
104
|
+
setError((err as { message?: string }).message ?? 'Email link sign-in failed');
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const isSignInWithEmailLink = (emailLink: string): boolean => {
|
|
109
|
+
return authService.isSignInWithEmailLink(emailLink);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const signOut = async () => {
|
|
113
|
+
try {
|
|
114
|
+
setLoading(true);
|
|
115
|
+
await authService.signOut();
|
|
116
|
+
setUser(null);
|
|
117
|
+
setLoading(false);
|
|
118
|
+
} catch (err: unknown) {
|
|
119
|
+
setLoading(false);
|
|
120
|
+
setError((err as { message?: string }).message ?? 'Sign-out failed');
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const value: AuthContextType = {
|
|
125
|
+
user,
|
|
126
|
+
loading,
|
|
127
|
+
error,
|
|
128
|
+
signInWithGoogle,
|
|
129
|
+
signOut,
|
|
130
|
+
signInWithEmail,
|
|
131
|
+
signUpWithEmail,
|
|
132
|
+
sendSignInLinkToEmail,
|
|
133
|
+
signInWithEmailLink,
|
|
134
|
+
isSignInWithEmailLink,
|
|
135
|
+
clearError,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
|
139
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {
|
|
2
|
+
signInWithPopup,
|
|
3
|
+
GoogleAuthProvider,
|
|
4
|
+
signInWithEmailAndPassword,
|
|
5
|
+
createUserWithEmailAndPassword,
|
|
6
|
+
updateProfile,
|
|
7
|
+
signOut as firebaseSignOut,
|
|
8
|
+
sendSignInLinkToEmail as firebaseSendSignInLinkToEmail,
|
|
9
|
+
isSignInWithEmailLink as firebaseIsSignInWithEmailLink,
|
|
10
|
+
signInWithEmailLink as firebaseSignInWithEmailLink,
|
|
11
|
+
ActionCodeSettings,
|
|
12
|
+
} from 'firebase/auth';
|
|
13
|
+
import { auth } from '../../config/firebase';
|
|
14
|
+
|
|
15
|
+
const googleProvider = new GoogleAuthProvider();
|
|
16
|
+
|
|
17
|
+
export const authService = {
|
|
18
|
+
// Google Sign In
|
|
19
|
+
signInWithGoogle: async () => {
|
|
20
|
+
try {
|
|
21
|
+
const result = await signInWithPopup(auth, googleProvider);
|
|
22
|
+
return result.user;
|
|
23
|
+
} catch (error) {
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
// Email/Password Sign In
|
|
29
|
+
signInWithEmail: async (email: string, password: string) => {
|
|
30
|
+
try {
|
|
31
|
+
const result = await signInWithEmailAndPassword(auth, email, password);
|
|
32
|
+
return result.user;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
// Email/Password Sign Up
|
|
39
|
+
signUpWithEmail: async (email: string, password: string, displayName?: string) => {
|
|
40
|
+
try {
|
|
41
|
+
const result = await createUserWithEmailAndPassword(auth, email, password);
|
|
42
|
+
|
|
43
|
+
if (displayName) {
|
|
44
|
+
await updateProfile(result.user, { displayName });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return result.user;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
// Sign Out
|
|
54
|
+
signOut: async () => {
|
|
55
|
+
try {
|
|
56
|
+
await firebaseSignOut(auth);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
// Email Link Authentication
|
|
63
|
+
sendSignInLinkToEmail: async (email: string, actionCodeSettings: ActionCodeSettings) => {
|
|
64
|
+
try {
|
|
65
|
+
await firebaseSendSignInLinkToEmail(auth, email, actionCodeSettings);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
isSignInWithEmailLink: (emailLink: string): boolean => {
|
|
72
|
+
return firebaseIsSignInWithEmailLink(auth, emailLink);
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
signInWithEmailLink: async (email: string, emailLink: string) => {
|
|
76
|
+
try {
|
|
77
|
+
const result = await firebaseSignInWithEmailLink(auth, email, emailLink);
|
|
78
|
+
return result.user;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
};
|