postbase 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/.github/workflows/test.yml +74 -0
- package/CLA.md +60 -0
- package/CONTRIBUTORS.md +35 -0
- package/LICENSE +661 -0
- package/README.md +211 -0
- package/admin/404.html +33 -0
- package/admin/README.md +21 -0
- package/admin/index.html +15 -0
- package/admin/jsconfig.json +20 -0
- package/admin/lib/postbase.js +222 -0
- package/admin/package-lock.json +3746 -0
- package/admin/package.json +27 -0
- package/admin/public/assets/img/admin-ui.png +0 -0
- package/admin/public/assets/img/blank-profile-picture-960_720.webp +0 -0
- package/admin/public/assets/img/chart-active-users.png +0 -0
- package/admin/public/assets/img/icon-transparent.png +0 -0
- package/admin/src/App.jsx +48 -0
- package/admin/src/auth.js +11 -0
- package/admin/src/common/formatDateTime.js +18 -0
- package/admin/src/components/AuthPanel.jsx +88 -0
- package/admin/src/components/Header.jsx +67 -0
- package/admin/src/main.jsx +6 -0
- package/admin/src/pages/Dashboard.jsx +24 -0
- package/admin/src/pages/Home.jsx +52 -0
- package/admin/src/pages/Login.jsx +10 -0
- package/admin/src/pages/authentication/Users.jsx +199 -0
- package/admin/src/pages/firestore/Database.jsx +29 -0
- package/admin/src/pages/storage/files.jsx +29 -0
- package/admin/src/postbase.js +15 -0
- package/admin/src/styles.css +3 -0
- package/admin/tailwind.config.cjs +11 -0
- package/admin/template.env +2 -0
- package/admin/vite.config.js +21 -0
- package/assets/img/HomePageScreenshot.png +0 -0
- package/assets/img/better-auth-logo-dark.136b122f.png +0 -0
- package/assets/img/better-auth-logo-light.4b03f444.png +0 -0
- package/assets/img/expresjs.png +0 -0
- package/assets/img/icon-transparent.png +0 -0
- package/assets/img/icon.png +0 -0
- package/assets/img/letsencrypt-logo-horizontal.png +0 -0
- package/assets/img/logo.png +0 -0
- package/assets/img/node.js_logo.png +0 -0
- package/assets/img/nodejsLight.svg +39 -0
- package/assets/img/postgres.png +0 -0
- package/backend/README.md +49 -0
- package/backend/admin/auth.js +9 -0
- package/backend/app.js +68 -0
- package/backend/auth.js +92 -0
- package/backend/env.js +12 -0
- package/backend/lib/postbase/adminClient.js +520 -0
- package/backend/lib/postbase/compat/admin.js +44 -0
- package/backend/lib/postbase/db.js +17 -0
- package/backend/lib/postbase/genericRouter.js +603 -0
- package/backend/lib/postbase/local-storage.js +56 -0
- package/backend/lib/postbase/metadataCache.js +32 -0
- package/backend/lib/postbase/middlewares/auth.js +57 -0
- package/backend/lib/postbase/migrations/1765239687559_rtdb-nodes.js +93 -0
- package/backend/lib/postbase/package-lock.json +5873 -0
- package/backend/lib/postbase/package.json +19 -0
- package/backend/lib/postbase/rtdb/router.js +190 -0
- package/backend/lib/postbase/rtdb/rulesEngine.js +63 -0
- package/backend/lib/postbase/rtdb/ws.js +84 -0
- package/backend/lib/postbase/rulesEngine.js +62 -0
- package/backend/lib/postbase/storage.js +130 -0
- package/backend/lib/postbase/tests/README.md +22 -0
- package/backend/lib/postbase/tests/db.js +9 -0
- package/backend/lib/postbase/tests/rtdb.rest.test.js +46 -0
- package/backend/lib/postbase/tests/rtdb.ws.test.js +113 -0
- package/backend/lib/postbase/tests/rules.js +26 -0
- package/backend/lib/postbase/tests/testServer.js +46 -0
- package/backend/lib/postbase/websocket.js +131 -0
- package/backend/local.js +6 -0
- package/backend/main.js +20 -0
- package/backend/middlewares/auth_middleware.js +10 -0
- package/backend/migrations/1762137399366-init.sql +98 -0
- package/backend/migrations/1762137399367_init_jsonb_schema.js +68 -0
- package/backend/migrations/1762149999999_enable_realtime_changes.js +48 -0
- package/backend/migrations/1765224247654_rtdb-nodes.js +93 -0
- package/backend/package-lock.json +2374 -0
- package/backend/package.json +27 -0
- package/backend/postbase_db_rules.js +128 -0
- package/backend/postbase_rtdb_rules.js +27 -0
- package/backend/postbase_storage_rules.js +45 -0
- package/backend/template.env +10 -0
- package/backend-systemd/README.md +39 -0
- package/backend-systemd/your_website.com.service +12 -0
- package/frontend/404.html +33 -0
- package/frontend/README.md +25 -0
- package/frontend/index.html +15 -0
- package/frontend/jsconfig.json +20 -0
- package/frontend/lib/postbase/auth.js +132 -0
- package/frontend/lib/postbase/compat/firebase/app.js +3 -0
- package/frontend/lib/postbase/compat/firebase/auth.js +115 -0
- package/frontend/lib/postbase/compat/firebase/database.js +11 -0
- package/frontend/lib/postbase/compat/firebase/firestore/lite.js +61 -0
- package/frontend/lib/postbase/compat/firebase/storage.js +10 -0
- package/frontend/lib/postbase/db.js +657 -0
- package/frontend/lib/postbase/package-lock.json +6284 -0
- package/frontend/lib/postbase/package.json +17 -0
- package/frontend/lib/postbase/rtdb.js +108 -0
- package/frontend/lib/postbase/storage.js +293 -0
- package/frontend/lib/postbase/tests/rtdb.client.test.js +88 -0
- package/frontend/lib/postbase/tests/waitFor.js +13 -0
- package/frontend/lib/postbase/utils.js +1 -0
- package/frontend/package-lock.json +2977 -0
- package/frontend/package.json +24 -0
- package/frontend/src/App.jsx +38 -0
- package/frontend/src/auth.js +52 -0
- package/frontend/src/components/AuthPanel.jsx +85 -0
- package/frontend/src/components/Header.jsx +54 -0
- package/frontend/src/main.jsx +5 -0
- package/frontend/src/pages/Dashboard.jsx +24 -0
- package/frontend/src/pages/Home.jsx +178 -0
- package/frontend/src/pages/Login.jsx +10 -0
- package/frontend/src/postbase.js +14 -0
- package/frontend/src/styles.css +1 -0
- package/frontend/tailwind.config.cjs +11 -0
- package/frontend/template.env +2 -0
- package/frontend/vite.config.js +18 -0
- package/git/hooks/README.md +31 -0
- package/git/hooks/post-receive +26 -0
- package/nginx/README.md +84 -0
- package/nginx/apt/www.your_website.com.conf +80 -0
- package/nginx/homebrew/www.your_website.com.conf +80 -0
- package/nginx/letsencrypt/README +14 -0
- package/package.json +8 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "postbase-admin",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"author": "Umair Ashraf",
|
|
6
|
+
"url": "http://github.com/umrashrf/postbase",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "vite",
|
|
9
|
+
"build": "vite build",
|
|
10
|
+
"preview": "vite preview"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@postbase/frontend": "file:../frontend/lib/postbase",
|
|
14
|
+
"@tailwindcss/vite": "^4.1.11",
|
|
15
|
+
"better-auth": "^1.3.34",
|
|
16
|
+
"flowbite": "^4.0.1",
|
|
17
|
+
"moment": "^2.30.1",
|
|
18
|
+
"preact": "^10.27.0",
|
|
19
|
+
"preact-iso": "^2.9.1",
|
|
20
|
+
"preact-router": "^4.1.2",
|
|
21
|
+
"tailwindcss": "^4.1.11"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@preact/preset-vite": "^2.9.3",
|
|
25
|
+
"vite": "^6.0.4"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useEffect, useState } from 'preact/hooks';
|
|
2
|
+
import { Router } from 'preact-router';
|
|
3
|
+
import Header from './components/Header';
|
|
4
|
+
import Home from './pages/Home';
|
|
5
|
+
import Dashboard from './pages/Dashboard';
|
|
6
|
+
import Login from './pages/Login';
|
|
7
|
+
import { getSession } from './auth';
|
|
8
|
+
import Users from './pages/authentication/Users';
|
|
9
|
+
import Database from './pages/firestore/Database';
|
|
10
|
+
import Files from './pages/storage/files';
|
|
11
|
+
|
|
12
|
+
export default function App() {
|
|
13
|
+
const [user, setUser] = useState(null);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
(async () => {
|
|
17
|
+
const { data } = await getSession();
|
|
18
|
+
if (data && data.hasOwnProperty('user')) {
|
|
19
|
+
setUser(data.user);
|
|
20
|
+
}
|
|
21
|
+
})();
|
|
22
|
+
}, []);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className="bg-[#131312] text-white">
|
|
26
|
+
<div class="flex">
|
|
27
|
+
<Header user={user} />
|
|
28
|
+
|
|
29
|
+
<div class="w-full">
|
|
30
|
+
<Router>
|
|
31
|
+
<Home path="/" user={user} />
|
|
32
|
+
<Dashboard path="/dashboard" user={user} />
|
|
33
|
+
<Login path="/login" user={user} />
|
|
34
|
+
<Users path="/project/0/authentication/users" user={user} />
|
|
35
|
+
<Database path="/project/0/firestore/databases/-default-/data" user={user} />
|
|
36
|
+
<Files path="/project/0/storage" user={user} />
|
|
37
|
+
</Router>
|
|
38
|
+
|
|
39
|
+
<footer className="bg-[#131312] text-white py-6">
|
|
40
|
+
<div className="container mx-auto text-center text-sm">
|
|
41
|
+
© {new Date().getFullYear()} Postbase Demo
|
|
42
|
+
</div>
|
|
43
|
+
</footer>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createAuthClient } from 'better-auth/client';
|
|
2
|
+
import { adminClient } from "better-auth/client/plugins"
|
|
3
|
+
|
|
4
|
+
export const authClient = createAuthClient({
|
|
5
|
+
baseURL: import.meta.env.VITE_API_BASE + '/auth', // Specify if on a different domain/path,
|
|
6
|
+
plugins: [
|
|
7
|
+
adminClient()
|
|
8
|
+
],
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export const { signUp, signIn, signOut, getSession } = authClient;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import moment from 'moment';
|
|
2
|
+
|
|
3
|
+
export const formatDateTime = (datetime, timezoneOffset) => {
|
|
4
|
+
const mDate = moment(datetime);
|
|
5
|
+
let today = moment();
|
|
6
|
+
|
|
7
|
+
if (timezoneOffset) {
|
|
8
|
+
today = today.utcOffset(timezoneOffset);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (mDate.isSame(today, "day")) {
|
|
12
|
+
return "Today, " + mDate.format("MMM D") + " at " + mDate.format("hh:mma");
|
|
13
|
+
} else if (mDate.isSame(today.clone().add(1, "day"), "day")) {
|
|
14
|
+
return "Tomorrow, " + mDate.format("MMM D") + " at " + mDate.format("hh:mma");
|
|
15
|
+
} else {
|
|
16
|
+
return mDate.format("ddd, MMM D") + " at " + mDate.format("hh:mma");
|
|
17
|
+
}
|
|
18
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { useState } from 'preact/hooks';
|
|
2
|
+
import { signUp, signIn, signOut } from '../auth';
|
|
3
|
+
|
|
4
|
+
export default function AuthPanel({ user }) {
|
|
5
|
+
const [email, setEmail] = useState('');
|
|
6
|
+
const [password, setPassword] = useState('');
|
|
7
|
+
|
|
8
|
+
const loginWithGoogle = async () => {
|
|
9
|
+
alert("Finish setting up Sign in with Google. Learn more at https://www.better-auth.com/docs/authentication/google");
|
|
10
|
+
await signIn.social({
|
|
11
|
+
provider: "google",
|
|
12
|
+
callbackURL: import.meta.env.VITE_FRONTEND_URL + '/dashboard'
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div class="px-4">
|
|
18
|
+
<h2 class="text-3xl py-4">Login</h2>
|
|
19
|
+
<div className="text-white border rounded-lg p-4">
|
|
20
|
+
<h3 className="font-semibold">Account</h3>
|
|
21
|
+
{!user ? (
|
|
22
|
+
<div className="mt-3 space-y-3">
|
|
23
|
+
<button onClick={loginWithGoogle} className="w-full bg-red-600 py-2 rounded">Sign in with Google</button>
|
|
24
|
+
|
|
25
|
+
<div className="flex gap-2">
|
|
26
|
+
<input className="flex-1 border p-2 rounded bg-transparent" type="text" placeholder="email" value={email} onInput={e => setEmail(e.target.value)} />
|
|
27
|
+
<input className="flex-1 border p-2 rounded bg-transparent" type="password" placeholder="password" value={password} onInput={e => setPassword(e.target.value)} />
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div className="flex gap-2">
|
|
31
|
+
<button className="px-3 py-2 border rounded"
|
|
32
|
+
onClick={async () => {
|
|
33
|
+
alert("Setup PostgreSQL (migration, connection string). Learn more at https://www.better-auth.com/docs/installation#create-database-tables");
|
|
34
|
+
await signUp.email(
|
|
35
|
+
{ email, password, name: email.split('@', 1)[0], callbackURL: "/dashboard" },
|
|
36
|
+
{
|
|
37
|
+
onRequest: (ctx) => {
|
|
38
|
+
//show loading
|
|
39
|
+
},
|
|
40
|
+
onSuccess: (ctx) => {
|
|
41
|
+
//redirect to the dashboard or sign in page
|
|
42
|
+
window.location = import.meta.env.VITE_FRONTEND_URL;
|
|
43
|
+
},
|
|
44
|
+
onError: (ctx) => {
|
|
45
|
+
// display the error message
|
|
46
|
+
console.error('Sign up failed', ctx);
|
|
47
|
+
alert('Sign up failed');
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}}>
|
|
51
|
+
Sign up
|
|
52
|
+
</button>
|
|
53
|
+
<button className="px-3 py-2 border rounded"
|
|
54
|
+
onClick={async () => {
|
|
55
|
+
alert("Setup PostgreSQL (migration, connection string). Learn more at https://www.better-auth.com/docs/installation#create-database-tables");
|
|
56
|
+
await signIn.email({ email, password, callbackURL: '/dashboard' });
|
|
57
|
+
}}>
|
|
58
|
+
Login
|
|
59
|
+
</button>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
) : (
|
|
63
|
+
<div className="mt-3">
|
|
64
|
+
<div className="flex items-center gap-3">
|
|
65
|
+
<img src={user.image} className="w-10 h-10 rounded-full" />
|
|
66
|
+
<div>
|
|
67
|
+
<div className="font-medium">{user.name || user.email}</div>
|
|
68
|
+
<div className="text-sm text-gray-500">{user.email}</div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div className="mt-3 flex gap-2">
|
|
73
|
+
<a href="/dashboard" className="px-3 py-2 border rounded">Dashboard</a>
|
|
74
|
+
<a href="/billing" className="px-3 py-2 border rounded">Billing</a>
|
|
75
|
+
<button onClick={() => signOut({
|
|
76
|
+
fetchOptions: {
|
|
77
|
+
onSuccess: () => {
|
|
78
|
+
location.href = import.meta.env.VITE_FRONTEND_URL;
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
})} className="px-3 py-2 border rounded cursor-pointer">Logout</button>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
)}
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useState, useEffect } from 'preact/hooks';
|
|
2
|
+
import { signOut } from '../auth';
|
|
3
|
+
|
|
4
|
+
export default function Header({ user }) {
|
|
5
|
+
const [menu, setMenu] = useState(false);
|
|
6
|
+
const [scrolled, setScrolled] = useState(false);
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const onScroll = () => setScrolled(window.scrollY > 30);
|
|
10
|
+
window.addEventListener('scroll', onScroll);
|
|
11
|
+
return () => window.removeEventListener('scroll', onScroll);
|
|
12
|
+
}, []);
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<header className={`max-w-[50px] md:max-w-[200px] min-h-screen z-40 transition-all bg-[#212020] text-white ${scrolled ? 'shadow' : ''} backdrop-blur`}>
|
|
16
|
+
<div className="max-w-6xl mx-auto px-2 md:px-4 py-4 flex gap-8 flex-col justify-between">
|
|
17
|
+
<a href="/" className="text-2xl font-extrabold">
|
|
18
|
+
<img src="/assets/img/icon-transparent.png" class="max-w-[40px] md:w-auto" />
|
|
19
|
+
<span class="hidden md:inline">Postbase Admin</span>
|
|
20
|
+
</a>
|
|
21
|
+
<nav className="flex flex-col gap-2 text-sm font-medium">
|
|
22
|
+
<a href="/project/0/authentication/users" className="hover:text-blue-600">
|
|
23
|
+
<span class="md:hidden">Auth</span>
|
|
24
|
+
<span class="hidden md:inline">Authentication</span>
|
|
25
|
+
</a>
|
|
26
|
+
<a href="/project/0/firestore/databases/-default-/data" className="hover:text-blue-600">
|
|
27
|
+
<span class="md:hidden">DB</span>
|
|
28
|
+
<span class="hidden md:inline">Firestore</span>
|
|
29
|
+
</a>
|
|
30
|
+
<a href="/project/0/storage" className="hover:text-blue-600">
|
|
31
|
+
<span class="md:hidden">Store</span>
|
|
32
|
+
<span class="hidden md:inline">Storage</span>
|
|
33
|
+
</a>
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<div className="flex items-center gap-4">
|
|
37
|
+
{user ? (
|
|
38
|
+
<div className="relative">
|
|
39
|
+
<button onClick={() => setMenu(!menu)} className="flex items-center gap-2 px-3 py-1 border rounded-md hover:bg-gray-500">
|
|
40
|
+
<img src={user.image} className="w-8 h-8 rounded-full" alt="profile" />
|
|
41
|
+
<span className="hidden sm:inline text-sm">{user.name || user.email}</span>
|
|
42
|
+
</button>
|
|
43
|
+
{menu && (
|
|
44
|
+
<div className="absolute right-0 mt-2 border rounded-md shadow-lg w-48 z-50">
|
|
45
|
+
<a href="/dashboard" className="block px-4 py-2 hover:bg-gray-500">Dashboard</a>
|
|
46
|
+
<a href="/billing" className="block px-4 py-2 hover:bg-gray-500">Billing</a>
|
|
47
|
+
<button onClick={() => signOut({
|
|
48
|
+
fetchOptions: {
|
|
49
|
+
onSuccess: () => {
|
|
50
|
+
location.href = import.meta.env.VITE_FRONTEND_URL;
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
})} className="w-full text-left px-4 py-2 hover:bg-gray-700 cursor-pointer">Logout</button>
|
|
54
|
+
</div>
|
|
55
|
+
)}
|
|
56
|
+
</div>
|
|
57
|
+
) : (
|
|
58
|
+
<a href="/login" className="bg-blue-600 text-white px-3 md:px-4 py-2 rounded-md font-medium hover:bg-blue-700 transition">
|
|
59
|
+
<span class="md:hidden">></span>
|
|
60
|
+
<span class="hidden md:inline">Sign In</span>
|
|
61
|
+
</a>
|
|
62
|
+
)}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</header>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { authClient } from '../auth';
|
|
2
|
+
|
|
3
|
+
export default function Dashboard({ user }) {
|
|
4
|
+
if (!user) {
|
|
5
|
+
return (
|
|
6
|
+
<div className="container mx-auto py-10 text-center">
|
|
7
|
+
<p className="text-gray-600">Please sign in to view your dashboard.</p>
|
|
8
|
+
</div>
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className="container mx-auto py-10">
|
|
14
|
+
<h1 className="text-2xl font-semibold mb-6">Dashboard</h1>
|
|
15
|
+
<div className="mt-2">
|
|
16
|
+
<button className="w-full bg-red-600 text-white py-2 rounded cursor-pointer"
|
|
17
|
+
onClick={async e => {
|
|
18
|
+
await authClient.deleteUser();
|
|
19
|
+
location.href = '/';
|
|
20
|
+
}}>Close Account</button>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import AuthPanel from '../components/AuthPanel';
|
|
2
|
+
|
|
3
|
+
export default function Home({ user }) {
|
|
4
|
+
return (
|
|
5
|
+
<main className="">
|
|
6
|
+
{/* HERO */}
|
|
7
|
+
<section className="bg-[#131312] text-white pt-24 pb-32">
|
|
8
|
+
<div className="max-w-6xl mx-auto px-6 grid md:grid-cols-2 gap-10 items-center">
|
|
9
|
+
<div>
|
|
10
|
+
<h1 className="text-5xl font-extrabold leading-tight">
|
|
11
|
+
Active Users
|
|
12
|
+
</h1>
|
|
13
|
+
|
|
14
|
+
<img src="assets/img/chart-active-users.png" />
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div className="relative">
|
|
18
|
+
<div>
|
|
19
|
+
<h1 className="text-5xl font-extrabold leading-tight">
|
|
20
|
+
Live Users
|
|
21
|
+
</h1>
|
|
22
|
+
|
|
23
|
+
<img src="assets/img/chart-active-users.png" />
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</section>
|
|
28
|
+
|
|
29
|
+
<section className="bg-[#131312] text-white pt-24 pb-32">
|
|
30
|
+
<div className="max-w-6xl mx-auto px-6 grid md:grid-cols-2 gap-10 items-center">
|
|
31
|
+
<div>
|
|
32
|
+
<h1 className="text-5xl font-extrabold leading-tight">
|
|
33
|
+
Active Users
|
|
34
|
+
</h1>
|
|
35
|
+
|
|
36
|
+
<img src="assets/img/chart-active-users.png" />
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div className="relative">
|
|
40
|
+
<div>
|
|
41
|
+
<h1 className="text-5xl font-extrabold leading-tight">
|
|
42
|
+
Live Users
|
|
43
|
+
</h1>
|
|
44
|
+
|
|
45
|
+
<img src="assets/img/chart-active-users.png" />
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</section>
|
|
50
|
+
</main>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { useEffect, useState } from "preact/hooks";
|
|
2
|
+
import { authClient } from "../../auth";
|
|
3
|
+
import { formatDateTime } from "../../common/formatDateTime";
|
|
4
|
+
|
|
5
|
+
export default function Users({ user }) {
|
|
6
|
+
const [users, setUsers] = useState(null);
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
(async () => {
|
|
10
|
+
const { data, error } = await authClient.admin.listUsers({
|
|
11
|
+
query: {
|
|
12
|
+
limit: 100,
|
|
13
|
+
offset: 0,
|
|
14
|
+
sortBy: "createdAt",
|
|
15
|
+
sortDirection: "desc",
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
setUsers(data.users);
|
|
19
|
+
})();
|
|
20
|
+
}, []);
|
|
21
|
+
|
|
22
|
+
return <div class="p-4">
|
|
23
|
+
<h2 class="text-3xl">Authentication</h2>
|
|
24
|
+
|
|
25
|
+
<div class="text-sm font-medium text-center text-body border-b border-default">
|
|
26
|
+
<ul class="flex flex-wrap -mb-px" data-tabs-toggle="#default-styled-tab-content" data-tabs-active-classes="text-purple hover:text-purple border-purple" data-tabs-inactive-classes="dark:border-transparent text-body hover:text-fg-brand border-default hover:border-brand">
|
|
27
|
+
<li class="me-2">
|
|
28
|
+
<a href="#" class="inline-block p-4 border-b border-transparent rounded-t-base hover:text-fg-brand hover:border-brand" aria-current="page">Users</a>
|
|
29
|
+
</li>
|
|
30
|
+
<li class="me-2">
|
|
31
|
+
<a href="#" class="inline-block p-4 text-fg-brand border-b border-brand rounded-t-base active">Sign-in method</a>
|
|
32
|
+
</li>
|
|
33
|
+
<li class="me-2">
|
|
34
|
+
<a href="#" class="inline-block p-4 border-b border-transparent rounded-t-base hover:text-fg-brand hover:border-brand">Templates</a>
|
|
35
|
+
</li>
|
|
36
|
+
<li class="me-2">
|
|
37
|
+
<a href="#" class="inline-block p-4 border-b border-transparent rounded-t-base hover:text-fg-brand hover:border-brand">Usage</a>
|
|
38
|
+
</li>
|
|
39
|
+
<li>
|
|
40
|
+
<a class="inline-block p-4 text-fg-disabled rounded-t-base cursor-not-allowed dark:text-body">Settings</a>
|
|
41
|
+
</li>
|
|
42
|
+
</ul>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div>
|
|
46
|
+
{users && users.length > 0 &&
|
|
47
|
+
<div class="relative overflow-x-auto bg-neutral-primary-soft shadow-xs rounded-base border border-default">
|
|
48
|
+
<div class="flex items-center justify-between flex-column md:flex-row flex-wrap space-y-4 md:space-y-0 p-4">
|
|
49
|
+
<div>
|
|
50
|
+
<button id="dropdownDefaultButton2" data-dropdown-toggle="dropdown-2" class="inline-flex items-center justify-center text-body bg-neutral-secondary-medium box-border border border-default-medium hover:bg-neutral-tertiary-medium hover:text-heading focus:ring-4 focus:ring-neutral-tertiary shadow-xs font-medium leading-5 rounded-base text-sm px-3 py-2 focus:outline-none" type="button">
|
|
51
|
+
Action
|
|
52
|
+
<svg class="w-4 h-4 ms-1.5 -me-0.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 9-7 7-7-7" /></svg>
|
|
53
|
+
</button>
|
|
54
|
+
<div id="dropdown-2" class="z-10 hidden bg-neutral-primary-medium border border-default-medium rounded-base shadow-lg w-32">
|
|
55
|
+
<ul class="p-2 text-sm text-body font-medium" aria-labelledby="dropdownDefaultButton2">
|
|
56
|
+
<li>
|
|
57
|
+
<a href="#" class="inline-flex items-center w-full p-2 hover:bg-neutral-tertiary-medium hover:text-heading rounded">Reward</a>
|
|
58
|
+
</li>
|
|
59
|
+
<li>
|
|
60
|
+
<a href="#" class="inline-flex items-center w-full p-2 hover:bg-neutral-tertiary-medium hover:text-heading rounded">Promote</a>
|
|
61
|
+
</li>
|
|
62
|
+
<li>
|
|
63
|
+
<a href="#" class="inline-flex items-center w-full p-2 hover:bg-neutral-tertiary-medium hover:text-heading rounded">Archive</a>
|
|
64
|
+
</li>
|
|
65
|
+
<li>
|
|
66
|
+
<a href="#" class="inline-flex items-center w-full p-2 text-fg-danger hover:bg-neutral-tertiary-medium rounded">Delete</a>
|
|
67
|
+
</li>
|
|
68
|
+
</ul>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
<label for="input-group-1" class="sr-only">Search</label>
|
|
72
|
+
<div class="relative">
|
|
73
|
+
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
|
|
74
|
+
<svg class="w-4 h-4 text-body" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m21 21-3.5-3.5M17 10a7 7 0 1 1-14 0 7 7 0 0 1 14 0Z" /></svg>
|
|
75
|
+
</div>
|
|
76
|
+
<input type="text" id="input-group-1" class="block w-full max-w-96 ps-9 pe-3 py-2 bg-neutral-secondary-medium border border-default-medium text-heading text-sm rounded-base focus:ring-brand focus:border-brand shadow-xs placeholder:text-body" placeholder="Search" />
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
<table class="w-full text-sm text-left rtl:text-right text-body">
|
|
80
|
+
<thead class="text-sm text-body bg-neutral-secondary-medium border-b border-t border-default-medium">
|
|
81
|
+
<tr>
|
|
82
|
+
<th scope="col" class="p-4">
|
|
83
|
+
<div class="flex items-center">
|
|
84
|
+
<input id="table-checkbox-51" type="checkbox" value="" class="w-4 h-4 border border-default-medium rounded-xs bg-neutral-secondary-medium focus:ring-2 focus:ring-brand-soft" />
|
|
85
|
+
<label for="table-checkbox-51" class="sr-only">Table checkbox</label>
|
|
86
|
+
</div>
|
|
87
|
+
</th>
|
|
88
|
+
<th scope="col" class="px-6 py-3 font-medium">
|
|
89
|
+
Name
|
|
90
|
+
</th>
|
|
91
|
+
<th scope="col" class="px-6 py-3 font-medium">
|
|
92
|
+
Providers
|
|
93
|
+
</th>
|
|
94
|
+
<th scope="col" class="px-6 py-3 font-medium">
|
|
95
|
+
Created
|
|
96
|
+
</th>
|
|
97
|
+
<th scope="col" class="px-6 py-3 font-medium">
|
|
98
|
+
Signed In
|
|
99
|
+
</th>
|
|
100
|
+
<th scope="col" class="px-6 py-3 font-medium">
|
|
101
|
+
User UID
|
|
102
|
+
</th>
|
|
103
|
+
<th scope="col" class="px-6 py-3 font-medium">
|
|
104
|
+
</th>
|
|
105
|
+
</tr>
|
|
106
|
+
</thead>
|
|
107
|
+
<tbody>
|
|
108
|
+
{users.map(u => <tr class="bg-neutral-primary-soft border-b border-default hover:bg-neutral-secondary-medium">
|
|
109
|
+
<td class="w-4 p-4">
|
|
110
|
+
<div class="flex items-center">
|
|
111
|
+
<input id="table-checkbox-52" type="checkbox" value="" class="w-4 h-4 border border-default-medium rounded-xs bg-neutral-secondary-medium focus:ring-2 focus:ring-brand-soft" />
|
|
112
|
+
<label for="table-checkbox-52" class="sr-only">Table checkbox</label>
|
|
113
|
+
</div>
|
|
114
|
+
</td>
|
|
115
|
+
<td scope="row" class="flex items-center px-6 py-4 text-heading whitespace-nowrap">
|
|
116
|
+
<img class="w-10 h-10 rounded-full" src={u.image} alt="Jese image" />
|
|
117
|
+
<div class="ps-3">
|
|
118
|
+
<div class="text-base font-semibold">{u.name}</div>
|
|
119
|
+
<div class="font-normal text-body">{u.email}</div>
|
|
120
|
+
</div>
|
|
121
|
+
</td>
|
|
122
|
+
<td class="px-6 py-4">
|
|
123
|
+
{u.image.includes('googleusercontent.com') ? 'Google' : 'Email'}
|
|
124
|
+
</td>
|
|
125
|
+
<td class="px-6 py-4">
|
|
126
|
+
<div class="flex items-center">
|
|
127
|
+
<div class="h-2.5 w-2.5 rounded-full bg-success me-2"></div> {formatDateTime(u.createdAt)}
|
|
128
|
+
</div>
|
|
129
|
+
</td>
|
|
130
|
+
<td class="px-6 py-4">
|
|
131
|
+
<div class="flex items-center">
|
|
132
|
+
<div class="h-2.5 w-2.5 rounded-full bg-success me-2"></div> {formatDateTime(u.updatedAt)}
|
|
133
|
+
</div>
|
|
134
|
+
</td>
|
|
135
|
+
<td class="px-6 py-4">
|
|
136
|
+
<div class="flex items-center">
|
|
137
|
+
<div class="h-2.5 w-2.5 rounded-full bg-success me-2"></div> {u.id}
|
|
138
|
+
</div>
|
|
139
|
+
</td>
|
|
140
|
+
<td class="px-6 py-4">
|
|
141
|
+
<a href="#" type="button" data-modal-target="editUserModal" data-modal-show="editUserModal" class="font-medium text-fg-brand hover:underline">Edit user</a>
|
|
142
|
+
</td>
|
|
143
|
+
</tr>)}
|
|
144
|
+
</tbody>
|
|
145
|
+
</table>
|
|
146
|
+
|
|
147
|
+
<div id="editUserModal" tabindex="-1" aria-hidden="true" class="hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full">
|
|
148
|
+
<div class="relative p-4 w-full max-w-md max-h-full">
|
|
149
|
+
|
|
150
|
+
<div class="relative bg-neutral-primary-soft border border-default rounded-base shadow-sm p-4 md:p-6">
|
|
151
|
+
|
|
152
|
+
<div class="flex items-center justify-between border-b border-default pb-4 md:pb-5">
|
|
153
|
+
<h3 class="text-lg font-medium text-heading">
|
|
154
|
+
Create new product
|
|
155
|
+
</h3>
|
|
156
|
+
<button type="button" class="text-body bg-transparent hover:bg-neutral-tertiary hover:text-heading rounded-base text-sm w-9 h-9 ms-auto inline-flex justify-center items-center" data-modal-hide="editUserModal">
|
|
157
|
+
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 17.94 6M18 18 6.06 6" /></svg>
|
|
158
|
+
<span class="sr-only">Close modal</span>
|
|
159
|
+
</button>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<form action="#">
|
|
163
|
+
<div class="grid gap-4 grid-cols-2 py-4 md:py-6">
|
|
164
|
+
<div class="col-span-2">
|
|
165
|
+
<label for="name" class="block mb-2.5 text-sm font-medium text-heading">Name</label>
|
|
166
|
+
<input type="text" name="name" id="name" class="bg-neutral-secondary-medium border border-default-medium text-heading text-sm rounded-base focus:ring-brand focus:border-brand block w-full px-3 py-2.5 shadow-xs placeholder:text-body" placeholder="Bonnie Green" required="" />
|
|
167
|
+
</div>
|
|
168
|
+
<div class="col-span-2 sm:col-span-1">
|
|
169
|
+
<label for="position" class="block mb-2.5 text-sm font-medium text-heading">Position</label>
|
|
170
|
+
<input type="text" name="position" id="position" class="bg-neutral-secondary-medium border border-default-medium text-heading text-sm rounded-base focus:ring-brand focus:border-brand block w-full px-3 py-2.5 shadow-xs placeholder:text-body" placeholder="React Developer" required="" />
|
|
171
|
+
</div>
|
|
172
|
+
<div class="col-span-2 sm:col-span-1">
|
|
173
|
+
<label for="category" class="block mb-2.5 text-sm font-medium text-heading">Status</label>
|
|
174
|
+
<select id="category" class="block w-full px-3 py-2.5 bg-neutral-secondary-medium border border-default-medium text-heading text-sm rounded-base focus:ring-brand focus:border-brand px-3 py-2.5 shadow-xs placeholder:text-body">
|
|
175
|
+
<option selected="">Online</option>
|
|
176
|
+
<option value="offline">Offline</option>
|
|
177
|
+
<option value="archived">Archived</option>
|
|
178
|
+
</select>
|
|
179
|
+
</div>
|
|
180
|
+
<div class="col-span-2">
|
|
181
|
+
<label for="biography" class="block mb-2.5 text-sm font-medium text-heading">Biography</label>
|
|
182
|
+
<textarea id="biography" rows="4" class="block bg-neutral-secondary-medium border border-default-medium text-heading text-sm rounded-base focus:ring-brand focus:border-brand block w-full p-3.5 shadow-xs placeholder:text-body" placeholder="Write a short biography here"></textarea>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
<div class="flex items-center space-x-4 border-t border-default pt-4 md:pt-6">
|
|
186
|
+
<button type="submit" class="inline-flex items-center text-white bg-brand hover:bg-brand-strong box-border border border-transparent focus:ring-4 focus:ring-brand-medium shadow-xs font-medium leading-5 rounded-base text-sm px-4 py-2.5 focus:outline-none">
|
|
187
|
+
Update user
|
|
188
|
+
</button>
|
|
189
|
+
<button data-modal-hide="crud-modal" type="button" class="text-body bg-neutral-secondary-medium box-border border border-default-medium hover:bg-neutral-tertiary-medium hover:text-heading focus:ring-4 focus:ring-neutral-tertiary shadow-xs font-medium leading-5 rounded-base text-sm px-4 py-2.5 focus:outline-none">Cancel</button>
|
|
190
|
+
</div>
|
|
191
|
+
</form>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
}
|
|
197
|
+
</div>
|
|
198
|
+
</div>;
|
|
199
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export default function Database({ user }) {
|
|
2
|
+
return <div class="p-4">
|
|
3
|
+
<h2 class="text-3xl">Database</h2>
|
|
4
|
+
|
|
5
|
+
<div class="text-sm font-medium text-center text-body border-b border-default">
|
|
6
|
+
<ul class="flex flex-wrap -mb-px" data-tabs-toggle="#default-styled-tab-content" data-tabs-active-classes="text-purple hover:text-purple border-purple" data-tabs-inactive-classes="dark:border-transparent text-body hover:text-fg-brand border-default hover:border-brand">
|
|
7
|
+
<li class="me-2">
|
|
8
|
+
<a href="#" class="inline-block p-4 border-b border-transparent rounded-t-base hover:text-fg-brand hover:border-brand" aria-current="page">Data</a>
|
|
9
|
+
</li>
|
|
10
|
+
<li class="me-2">
|
|
11
|
+
<a href="#" class="inline-block p-4 text-fg-brand border-b border-brand rounded-t-base active">Rules</a>
|
|
12
|
+
</li>
|
|
13
|
+
<li class="me-2">
|
|
14
|
+
<a href="#" class="inline-block p-4 border-b border-transparent rounded-t-base hover:text-fg-brand hover:border-brand">Indexes</a>
|
|
15
|
+
</li>
|
|
16
|
+
<li class="me-2">
|
|
17
|
+
<a href="#" class="inline-block p-4 border-b border-transparent rounded-t-base hover:text-fg-brand hover:border-brand">Disaster Recovery</a>
|
|
18
|
+
</li>
|
|
19
|
+
<li>
|
|
20
|
+
<a class="inline-block p-4 text-fg-disabled rounded-t-base cursor-not-allowed dark:text-body">Usage</a>
|
|
21
|
+
</li>
|
|
22
|
+
<li>
|
|
23
|
+
<a class="inline-block p-4 text-fg-disabled rounded-t-base cursor-not-allowed dark:text-body">Query Insights</a>
|
|
24
|
+
</li>
|
|
25
|
+
</ul>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
</div>;
|
|
29
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export default function Files({ user }) {
|
|
2
|
+
return <div class="p-4">
|
|
3
|
+
<h2 class="text-3xl">Files</h2>
|
|
4
|
+
|
|
5
|
+
<div class="text-sm font-medium text-center text-body border-b border-default">
|
|
6
|
+
<ul class="flex flex-wrap -mb-px" data-tabs-toggle="#default-styled-tab-content" data-tabs-active-classes="text-purple hover:text-purple border-purple" data-tabs-inactive-classes="dark:border-transparent text-body hover:text-fg-brand border-default hover:border-brand">
|
|
7
|
+
<li class="me-2">
|
|
8
|
+
<a href="#" class="inline-block p-4 border-b border-transparent rounded-t-base hover:text-fg-brand hover:border-brand" aria-current="page">Data</a>
|
|
9
|
+
</li>
|
|
10
|
+
<li class="me-2">
|
|
11
|
+
<a href="#" class="inline-block p-4 text-fg-brand border-b border-brand rounded-t-base active">Rules</a>
|
|
12
|
+
</li>
|
|
13
|
+
<li class="me-2">
|
|
14
|
+
<a href="#" class="inline-block p-4 border-b border-transparent rounded-t-base hover:text-fg-brand hover:border-brand">Indexes</a>
|
|
15
|
+
</li>
|
|
16
|
+
<li class="me-2">
|
|
17
|
+
<a href="#" class="inline-block p-4 border-b border-transparent rounded-t-base hover:text-fg-brand hover:border-brand">Disaster Recovery</a>
|
|
18
|
+
</li>
|
|
19
|
+
<li>
|
|
20
|
+
<a class="inline-block p-4 text-fg-disabled rounded-t-base cursor-not-allowed dark:text-body">Usage</a>
|
|
21
|
+
</li>
|
|
22
|
+
<li>
|
|
23
|
+
<a class="inline-block p-4 text-fg-disabled rounded-t-base cursor-not-allowed dark:text-body">Query Insights</a>
|
|
24
|
+
</li>
|
|
25
|
+
</ul>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
</div>;
|
|
29
|
+
}
|