@promptbook/cli 0.103.0-48 → 0.103.0-49
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/apps/agents-server/README.md +1 -1
- package/apps/agents-server/TODO.txt +6 -5
- package/apps/agents-server/config.ts +130 -0
- package/apps/agents-server/next.config.ts +1 -1
- package/apps/agents-server/public/fonts/OpenMoji-black-glyf.woff2 +0 -0
- package/apps/agents-server/public/fonts/download-font.js +22 -0
- package/apps/agents-server/src/app/[agentName]/[...rest]/page.tsx +6 -0
- package/apps/agents-server/src/app/[agentName]/page.tsx +1 -0
- package/apps/agents-server/src/app/actions.ts +37 -2
- package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +68 -0
- package/apps/agents-server/src/app/agents/[agentName]/AgentQrCode.tsx +55 -0
- package/apps/agents-server/src/app/agents/[agentName]/AgentUrlCopy.tsx +4 -5
- package/apps/agents-server/src/app/agents/[agentName]/CopyField.tsx +44 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +8 -8
- package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +100 -24
- package/apps/agents-server/src/app/agents/[agentName]/api/feedback/route.ts +54 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/route.ts +6 -6
- package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/systemMessage/route.ts +3 -3
- package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +6 -7
- package/apps/agents-server/src/app/agents/[agentName]/book/BookEditorWrapper.tsx +4 -5
- package/apps/agents-server/src/app/agents/[agentName]/book/page.tsx +9 -2
- package/apps/agents-server/src/app/agents/[agentName]/book+chat/AgentBookAndChat.tsx +23 -0
- package/apps/agents-server/src/app/agents/[agentName]/book+chat/{AgentBookAndChatComponent.tsx → AgentBookAndChatComponent.tsx.todo} +4 -4
- package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx +28 -17
- package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx.todo +21 -0
- package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatWrapper.tsx +34 -4
- package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +4 -1
- package/apps/agents-server/src/app/agents/[agentName]/generateAgentMetadata.ts +42 -0
- package/apps/agents-server/src/app/agents/[agentName]/page.tsx +109 -106
- package/apps/agents-server/src/app/agents/page.tsx +1 -1
- package/apps/agents-server/src/app/api/auth/login/route.ts +65 -0
- package/apps/agents-server/src/app/api/auth/logout/route.ts +7 -0
- package/apps/agents-server/src/app/api/metadata/route.ts +116 -0
- package/apps/agents-server/src/app/api/upload/route.ts +7 -3
- package/apps/agents-server/src/app/api/users/[username]/route.ts +75 -0
- package/apps/agents-server/src/app/api/users/route.ts +71 -0
- package/apps/agents-server/src/app/globals.css +35 -1
- package/apps/agents-server/src/app/layout.tsx +43 -23
- package/apps/agents-server/src/app/metadata/MetadataClient.tsx +271 -0
- package/apps/agents-server/src/app/metadata/page.tsx +13 -0
- package/apps/agents-server/src/app/not-found.tsx +5 -0
- package/apps/agents-server/src/app/page.tsx +84 -46
- package/apps/agents-server/src/components/Auth/AuthControls.tsx +123 -0
- package/apps/agents-server/src/components/ErrorPage/ErrorPage.tsx +33 -0
- package/apps/agents-server/src/components/ForbiddenPage/ForbiddenPage.tsx +15 -0
- package/apps/agents-server/src/components/Header/Header.tsx +146 -0
- package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +27 -0
- package/apps/agents-server/src/components/LoginDialog/LoginDialog.tsx +40 -0
- package/apps/agents-server/src/components/LoginForm/LoginForm.tsx +109 -0
- package/apps/agents-server/src/components/NotFoundPage/NotFoundPage.tsx +17 -0
- package/apps/agents-server/src/components/UsersList/UsersList.tsx +190 -0
- package/apps/agents-server/src/components/VercelDeploymentCard/VercelDeploymentCard.tsx +60 -0
- package/apps/agents-server/src/database/$getTableName.ts +18 -0
- package/apps/agents-server/src/database/$provideSupabase.ts +2 -2
- package/apps/agents-server/src/database/$provideSupabaseForServer.ts +3 -3
- package/apps/agents-server/src/database/getMetadata.ts +31 -0
- package/apps/agents-server/src/database/metadataDefaults.ts +32 -0
- package/apps/agents-server/src/database/schema.sql +81 -33
- package/apps/agents-server/src/database/schema.ts +35 -1
- package/apps/agents-server/src/middleware.ts +162 -0
- package/apps/agents-server/src/tools/$provideAgentCollectionForServer.ts +11 -7
- package/apps/agents-server/src/tools/$provideCdnForServer.ts +1 -1
- package/apps/agents-server/src/tools/$provideExecutionToolsForServer.ts +11 -13
- package/apps/agents-server/src/tools/$provideOpenAiAssistantExecutionToolsForServer.ts +7 -7
- package/apps/agents-server/src/tools/$provideServer.ts +39 -0
- package/apps/agents-server/src/utils/auth.ts +33 -0
- package/apps/agents-server/src/utils/cdn/utils/nameToSubfolderPath.ts +1 -1
- package/apps/agents-server/src/utils/getCurrentUser.ts +32 -0
- package/apps/agents-server/src/utils/isIpAllowed.ts +101 -0
- package/apps/agents-server/src/utils/isUserAdmin.ts +31 -0
- package/apps/agents-server/src/utils/session.ts +50 -0
- package/apps/agents-server/tailwind.config.ts +2 -0
- package/esm/index.es.js +147 -31
- package/esm/index.es.js.map +1 -1
- package/esm/typings/servers.d.ts +1 -0
- package/esm/typings/src/_packages/types.index.d.ts +2 -0
- package/esm/typings/src/_packages/utils.index.d.ts +2 -0
- package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +12 -2
- package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +14 -8
- package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabaseOptions.d.ts +10 -0
- package/esm/typings/src/commitments/MESSAGE/InitialMessageCommitmentDefinition.d.ts +28 -0
- package/esm/typings/src/commitments/index.d.ts +2 -1
- package/esm/typings/src/config.d.ts +1 -0
- package/esm/typings/src/errors/DatabaseError.d.ts +2 -2
- package/esm/typings/src/errors/WrappedError.d.ts +2 -2
- package/esm/typings/src/execution/ExecutionTask.d.ts +2 -2
- package/esm/typings/src/execution/LlmExecutionTools.d.ts +6 -1
- package/esm/typings/src/llm-providers/_common/register/$provideLlmToolsForWizardOrCli.d.ts +2 -2
- package/esm/typings/src/llm-providers/agent/Agent.d.ts +11 -3
- package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +6 -1
- package/esm/typings/src/llm-providers/agent/RemoteAgent.d.ts +6 -2
- package/esm/typings/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +6 -1
- package/esm/typings/src/remote-server/startAgentServer.d.ts +2 -2
- package/esm/typings/src/utils/color/Color.d.ts +7 -0
- package/esm/typings/src/utils/color/Color.test.d.ts +1 -0
- package/esm/typings/src/utils/environment/$getGlobalScope.d.ts +2 -2
- package/esm/typings/src/utils/misc/computeHash.d.ts +11 -0
- package/esm/typings/src/utils/misc/computeHash.test.d.ts +1 -0
- package/esm/typings/src/utils/organization/$sideEffect.d.ts +2 -2
- package/esm/typings/src/utils/organization/$side_effect.d.ts +2 -2
- package/esm/typings/src/utils/organization/TODO_USE.d.ts +2 -2
- package/esm/typings/src/utils/organization/keepUnused.d.ts +2 -2
- package/esm/typings/src/utils/organization/preserve.d.ts +3 -3
- package/esm/typings/src/utils/organization/really_any.d.ts +7 -0
- package/esm/typings/src/utils/serialization/asSerializable.d.ts +2 -2
- package/esm/typings/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/umd/index.umd.js +147 -31
- package/umd/index.umd.js.map +1 -1
- package/apps/agents-server/config.ts.todo +0 -38
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
|
|
6
|
+
type AuthControlsProps = {
|
|
7
|
+
initialUser: { username: string; isAdmin: boolean } | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function AuthControls({ initialUser }: AuthControlsProps) {
|
|
11
|
+
const router = useRouter();
|
|
12
|
+
const [user, setUser] = useState(initialUser);
|
|
13
|
+
const [isLoginOpen, setIsLoginOpen] = useState(false);
|
|
14
|
+
const [username, setUsername] = useState('');
|
|
15
|
+
const [password, setPassword] = useState('');
|
|
16
|
+
const [error, setError] = useState<string | null>(null);
|
|
17
|
+
|
|
18
|
+
const handleLogin = async (e: React.FormEvent) => {
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
setError(null);
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const response = await fetch('/api/auth/login', {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: { 'Content-Type': 'application/json' },
|
|
26
|
+
body: JSON.stringify({ username, password }),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
const data = await response.json();
|
|
31
|
+
throw new Error(data.error || 'Login failed');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Reload page to reflect state
|
|
35
|
+
window.location.reload();
|
|
36
|
+
} catch (err) {
|
|
37
|
+
setError(err instanceof Error ? err.message : 'An error occurred');
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const handleLogout = async () => {
|
|
42
|
+
try {
|
|
43
|
+
await fetch('/api/auth/logout', { method: 'POST' });
|
|
44
|
+
window.location.reload();
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error('Logout failed', err);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
if (user) {
|
|
51
|
+
return (
|
|
52
|
+
<div className="flex items-center space-x-4">
|
|
53
|
+
<span className="text-gray-600">
|
|
54
|
+
Logged in as <strong>{user.username}</strong>
|
|
55
|
+
{user.isAdmin && <span className="ml-2 bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded">Admin</span>}
|
|
56
|
+
</span>
|
|
57
|
+
<button
|
|
58
|
+
onClick={handleLogout}
|
|
59
|
+
className="bg-gray-200 text-gray-700 px-4 py-2 rounded hover:bg-gray-300 transition-colors"
|
|
60
|
+
>
|
|
61
|
+
Logout
|
|
62
|
+
</button>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div>
|
|
69
|
+
{!isLoginOpen ? (
|
|
70
|
+
<button
|
|
71
|
+
onClick={() => setIsLoginOpen(true)}
|
|
72
|
+
className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition-colors"
|
|
73
|
+
>
|
|
74
|
+
Login
|
|
75
|
+
</button>
|
|
76
|
+
) : (
|
|
77
|
+
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|
78
|
+
<div className="bg-white p-6 rounded-lg shadow-xl w-full max-w-md">
|
|
79
|
+
<h2 className="text-2xl font-bold mb-4">Login</h2>
|
|
80
|
+
{error && <div className="bg-red-100 text-red-700 p-3 rounded mb-4">{error}</div>}
|
|
81
|
+
<form onSubmit={handleLogin} className="space-y-4">
|
|
82
|
+
<div>
|
|
83
|
+
<label className="block text-gray-700 mb-1">Username</label>
|
|
84
|
+
<input
|
|
85
|
+
type="text"
|
|
86
|
+
value={username}
|
|
87
|
+
onChange={(e) => setUsername(e.target.value)}
|
|
88
|
+
className="w-full p-2 border border-gray-300 rounded"
|
|
89
|
+
required
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
<div>
|
|
93
|
+
<label className="block text-gray-700 mb-1">Password</label>
|
|
94
|
+
<input
|
|
95
|
+
type="password"
|
|
96
|
+
value={password}
|
|
97
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
98
|
+
className="w-full p-2 border border-gray-300 rounded"
|
|
99
|
+
required
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
<div className="flex justify-end space-x-2">
|
|
103
|
+
<button
|
|
104
|
+
type="button"
|
|
105
|
+
onClick={() => setIsLoginOpen(false)}
|
|
106
|
+
className="px-4 py-2 text-gray-600 hover:text-gray-800"
|
|
107
|
+
>
|
|
108
|
+
Cancel
|
|
109
|
+
</button>
|
|
110
|
+
<button
|
|
111
|
+
type="submit"
|
|
112
|
+
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
|
|
113
|
+
>
|
|
114
|
+
Login
|
|
115
|
+
</button>
|
|
116
|
+
</div>
|
|
117
|
+
</form>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
)}
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
type ErrorPageProps = {
|
|
4
|
+
/**
|
|
5
|
+
* The title of the error page (e.g. "404 Not Found")
|
|
6
|
+
*/
|
|
7
|
+
title: string;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The message to display to the user
|
|
11
|
+
*/
|
|
12
|
+
message: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Optional children to display below the message (e.g. a button or form)
|
|
16
|
+
*/
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A standard layout for error pages (404, 403, 500, etc.)
|
|
22
|
+
*/
|
|
23
|
+
export function ErrorPage({ title, message, children }: ErrorPageProps) {
|
|
24
|
+
return (
|
|
25
|
+
<div className="min-h-screen flex items-center justify-center bg-gray-100">
|
|
26
|
+
<div className="bg-white p-8 rounded-lg shadow-md max-w-md w-full">
|
|
27
|
+
<h1 className="text-3xl font-bold text-red-600 mb-4 text-center">{title}</h1>
|
|
28
|
+
<p className="text-gray-700 mb-6 text-center">{message}</p>
|
|
29
|
+
{children}
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useRouter } from 'next/navigation';
|
|
4
|
+
import { ErrorPage } from '../ErrorPage/ErrorPage';
|
|
5
|
+
import { LoginForm } from '../LoginForm/LoginForm';
|
|
6
|
+
|
|
7
|
+
export function ForbiddenPage() {
|
|
8
|
+
const router = useRouter();
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<ErrorPage title="403 Forbidden" message="You do not have permission to access this page.">
|
|
12
|
+
<LoginForm onSuccess={() => router.refresh()} />
|
|
13
|
+
</ErrorPage>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import promptbookLogoBlueTransparent from '@/public/logo-blue-white-256.png';
|
|
4
|
+
import { logoutAction } from '@/src/app/actions';
|
|
5
|
+
import { ArrowRight, LogIn, LogOut, Menu, X } from 'lucide-react';
|
|
6
|
+
import Image from 'next/image';
|
|
7
|
+
import Link from 'next/link';
|
|
8
|
+
import { useState } from 'react';
|
|
9
|
+
import { just } from '../../../../../src/utils/organization/just';
|
|
10
|
+
import { LoginDialog } from '../LoginDialog/LoginDialog';
|
|
11
|
+
|
|
12
|
+
type HeaderProps = {
|
|
13
|
+
/**
|
|
14
|
+
* Is the user an admin
|
|
15
|
+
*/
|
|
16
|
+
isAdmin?: boolean;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The name of the server
|
|
20
|
+
*/
|
|
21
|
+
serverName: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The URL of the logo displayed in the heading bar
|
|
25
|
+
*/
|
|
26
|
+
serverLogoUrl: string | null;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/* TODO: [🐱🚀] Make this Agents server native */
|
|
30
|
+
|
|
31
|
+
export function Header(props: HeaderProps) {
|
|
32
|
+
const { isAdmin = false, serverName, serverLogoUrl } = props;
|
|
33
|
+
|
|
34
|
+
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
|
35
|
+
const [isLoginOpen, setIsLoginOpen] = useState(false);
|
|
36
|
+
|
|
37
|
+
const handleLogout = async () => {
|
|
38
|
+
await logoutAction();
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<header className="fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-md border-b border-gray-200 h-[60px]">
|
|
43
|
+
<LoginDialog isOpen={isLoginOpen} onClose={() => setIsLoginOpen(false)} />
|
|
44
|
+
<div className="container mx-auto px-4">
|
|
45
|
+
<div className="flex items-center justify-between h-16">
|
|
46
|
+
{/* Logo <- TODO: This should be <h1>*/}
|
|
47
|
+
<Link href="/" className="flex items-center gap-3 hover:opacity-80 transition-opacity">
|
|
48
|
+
<Image
|
|
49
|
+
src={serverLogoUrl || promptbookLogoBlueTransparent}
|
|
50
|
+
alt={serverName}
|
|
51
|
+
width={32}
|
|
52
|
+
height={32}
|
|
53
|
+
className="w-8 h-8 object-contain"
|
|
54
|
+
/>
|
|
55
|
+
<span className="text-xl text-gray-900">{serverName}</span>
|
|
56
|
+
</Link>
|
|
57
|
+
|
|
58
|
+
{/* Desktop Navigation */}
|
|
59
|
+
{/* Desktop Navigation */}
|
|
60
|
+
<nav className="hidden md:flex items-center gap-8">
|
|
61
|
+
{isAdmin && (
|
|
62
|
+
<Link
|
|
63
|
+
href="/metadata"
|
|
64
|
+
className="text-gray-600 hover:text-gray-900 transition-colors cursor-pointer"
|
|
65
|
+
>
|
|
66
|
+
Metadata
|
|
67
|
+
</Link>
|
|
68
|
+
)}
|
|
69
|
+
|
|
70
|
+
{just(false /* TODO: [🧠] Figure out what to do with theese links */) && (
|
|
71
|
+
<Link
|
|
72
|
+
href="https://ptbk.io/#try-it-yourself"
|
|
73
|
+
target="_blank"
|
|
74
|
+
className="text-gray-600 hover:text-gray-900 transition-colors cursor-pointer"
|
|
75
|
+
>
|
|
76
|
+
Try it yourself
|
|
77
|
+
</Link>
|
|
78
|
+
)}
|
|
79
|
+
</nav>
|
|
80
|
+
|
|
81
|
+
{/* CTA Button & Mobile Menu Toggle */}
|
|
82
|
+
<div className="flex items-center gap-4">
|
|
83
|
+
{just(false /* TODO: [🧠] Figure out what to do with call to action */) && (
|
|
84
|
+
<Link href="https://ptbk.io/?modal=get-started" target="_blank" className="hidden md:block">
|
|
85
|
+
<button className="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 h-10 px-4 py-2 bg-promptbook-blue-dark text-white hover:bg-promptbook-blue-dark/90">
|
|
86
|
+
Get Started
|
|
87
|
+
<ArrowRight className="ml-2 w-4 h-4" />
|
|
88
|
+
</button>
|
|
89
|
+
</Link>
|
|
90
|
+
)}
|
|
91
|
+
|
|
92
|
+
{!isAdmin ? (
|
|
93
|
+
<button
|
|
94
|
+
onClick={() => {
|
|
95
|
+
setIsLoginOpen(true);
|
|
96
|
+
setIsMenuOpen(false);
|
|
97
|
+
}}
|
|
98
|
+
className="w-full inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium h-10 px-4 py-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100"
|
|
99
|
+
>
|
|
100
|
+
Log in
|
|
101
|
+
<LogIn className="ml-2 w-4 h-4" />
|
|
102
|
+
</button>
|
|
103
|
+
) : (
|
|
104
|
+
<button
|
|
105
|
+
onClick={() => {
|
|
106
|
+
handleLogout();
|
|
107
|
+
setIsMenuOpen(false);
|
|
108
|
+
}}
|
|
109
|
+
className="w-full inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium h-10 px-4 py-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100"
|
|
110
|
+
>
|
|
111
|
+
Log out
|
|
112
|
+
<LogOut className="ml-2 w-4 h-4" />
|
|
113
|
+
</button>
|
|
114
|
+
)}
|
|
115
|
+
|
|
116
|
+
{/* Mobile Menu Toggle */}
|
|
117
|
+
{just(false /* TODO: [🧠] Figure out whether we want a menu */) && (
|
|
118
|
+
<button
|
|
119
|
+
className="md:hidden p-2 text-gray-600 hover:text-gray-900"
|
|
120
|
+
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
|
121
|
+
>
|
|
122
|
+
{isMenuOpen ? <X size={24} /> : <Menu size={24} />}
|
|
123
|
+
</button>
|
|
124
|
+
)}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
{/* Mobile Navigation */}
|
|
129
|
+
{just(false /* TODO: [🧠] Figure out whether we want a menu */) && isMenuOpen && (
|
|
130
|
+
<div className="md:hidden py-4 border-t border-gray-100 animate-in slide-in-from-top-2">
|
|
131
|
+
<nav className="flex flex-col gap-4">
|
|
132
|
+
<Link
|
|
133
|
+
href="https://ptbk.io/#try-it-yourself"
|
|
134
|
+
target="_blank"
|
|
135
|
+
className="text-gray-600 hover:text-gray-900 transition-colors py-2"
|
|
136
|
+
onClick={() => setIsMenuOpen(false)}
|
|
137
|
+
>
|
|
138
|
+
Try it yourself
|
|
139
|
+
</Link>
|
|
140
|
+
</nav>
|
|
141
|
+
</div>
|
|
142
|
+
)}
|
|
143
|
+
</div>
|
|
144
|
+
</header>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { usePathname } from 'next/navigation';
|
|
4
|
+
import { Header } from '../Header/Header';
|
|
5
|
+
|
|
6
|
+
type LayoutWrapperProps = {
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
isAdmin: boolean;
|
|
9
|
+
serverName: string;
|
|
10
|
+
serverLogoUrl: string | null;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function LayoutWrapper({ children, isAdmin, serverName, serverLogoUrl }: LayoutWrapperProps) {
|
|
14
|
+
const pathname = usePathname();
|
|
15
|
+
const isHeaderHidden = pathname?.includes('/chat');
|
|
16
|
+
|
|
17
|
+
if (isHeaderHidden) {
|
|
18
|
+
return <main className={`pt-0`}>{children}</main>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
<Header isAdmin={isAdmin} serverName={serverName} serverLogoUrl={serverLogoUrl} />
|
|
24
|
+
<main className={`pt-[60px]`}>{children}</main>
|
|
25
|
+
</>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { X } from 'lucide-react';
|
|
4
|
+
import { LoginForm } from '../LoginForm/LoginForm';
|
|
5
|
+
|
|
6
|
+
type LoginDialogProps = {
|
|
7
|
+
isOpen: boolean;
|
|
8
|
+
onClose: () => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function LoginDialog(props: LoginDialogProps) {
|
|
12
|
+
const { isOpen, onClose } = props;
|
|
13
|
+
|
|
14
|
+
if (!isOpen) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm animate-in fade-in duration-200">
|
|
20
|
+
<div className="relative w-full max-w-md bg-white rounded-lg shadow-lg border border-gray-200 p-6 animate-in zoom-in-95 duration-200">
|
|
21
|
+
<button
|
|
22
|
+
onClick={onClose}
|
|
23
|
+
className="absolute top-4 right-4 text-gray-400 hover:text-gray-500 transition-colors"
|
|
24
|
+
>
|
|
25
|
+
<X className="w-5 h-5" />
|
|
26
|
+
<span className="sr-only">Close</span>
|
|
27
|
+
</button>
|
|
28
|
+
|
|
29
|
+
<div className="mb-6">
|
|
30
|
+
<h2 className="text-xl font-semibold text-gray-900">Log in</h2>
|
|
31
|
+
<p className="text-sm text-gray-500 mt-1">
|
|
32
|
+
Enter your credentials to access the admin area
|
|
33
|
+
</p>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<LoginForm onSuccess={onClose} />
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { loginAction } from '@/src/app/actions';
|
|
4
|
+
import { Loader2, Lock, User } from 'lucide-react';
|
|
5
|
+
import { useState } from 'react';
|
|
6
|
+
|
|
7
|
+
type LoginFormProps = {
|
|
8
|
+
onSuccess?: () => void;
|
|
9
|
+
className?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function LoginForm(props: LoginFormProps) {
|
|
13
|
+
const { onSuccess, className } = props;
|
|
14
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
15
|
+
const [error, setError] = useState<string | null>(null);
|
|
16
|
+
|
|
17
|
+
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
|
18
|
+
event.preventDefault();
|
|
19
|
+
setIsLoading(true);
|
|
20
|
+
setError(null);
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const formData = new FormData(event.currentTarget);
|
|
24
|
+
const result = await loginAction(formData);
|
|
25
|
+
|
|
26
|
+
if (result.success) {
|
|
27
|
+
if (onSuccess) {
|
|
28
|
+
onSuccess();
|
|
29
|
+
}
|
|
30
|
+
} else {
|
|
31
|
+
setError(result.message || 'An error occurred');
|
|
32
|
+
}
|
|
33
|
+
} catch (error) {
|
|
34
|
+
setError('An unexpected error occurred');
|
|
35
|
+
console.error(error);
|
|
36
|
+
} finally {
|
|
37
|
+
setIsLoading(false);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<form onSubmit={handleSubmit} className={`space-y-4 ${className || ''}`}>
|
|
43
|
+
<div className="space-y-2">
|
|
44
|
+
<label
|
|
45
|
+
htmlFor="username"
|
|
46
|
+
className="text-sm font-medium text-gray-700 block"
|
|
47
|
+
>
|
|
48
|
+
Username
|
|
49
|
+
</label>
|
|
50
|
+
<div className="relative">
|
|
51
|
+
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400">
|
|
52
|
+
<User className="w-4 h-4" />
|
|
53
|
+
</div>
|
|
54
|
+
<input
|
|
55
|
+
id="username"
|
|
56
|
+
name="username"
|
|
57
|
+
type="text"
|
|
58
|
+
required
|
|
59
|
+
className="block w-full pl-10 h-10 rounded-md border border-gray-300 bg-white px-3 py-2 text-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-promptbook-blue focus:border-transparent disabled:opacity-50"
|
|
60
|
+
placeholder="Enter your username"
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div className="space-y-2">
|
|
66
|
+
<label
|
|
67
|
+
htmlFor="password"
|
|
68
|
+
className="text-sm font-medium text-gray-700 block"
|
|
69
|
+
>
|
|
70
|
+
Password
|
|
71
|
+
</label>
|
|
72
|
+
<div className="relative">
|
|
73
|
+
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400">
|
|
74
|
+
<Lock className="w-4 h-4" />
|
|
75
|
+
</div>
|
|
76
|
+
<input
|
|
77
|
+
id="password"
|
|
78
|
+
name="password"
|
|
79
|
+
type="password"
|
|
80
|
+
required
|
|
81
|
+
className="block w-full pl-10 h-10 rounded-md border border-gray-300 bg-white px-3 py-2 text-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-promptbook-blue focus:border-transparent disabled:opacity-50"
|
|
82
|
+
placeholder="Enter your password"
|
|
83
|
+
/>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
{error && (
|
|
88
|
+
<div className="p-3 text-sm text-red-500 bg-red-50 border border-red-200 rounded-md">
|
|
89
|
+
{error}
|
|
90
|
+
</div>
|
|
91
|
+
)}
|
|
92
|
+
|
|
93
|
+
<button
|
|
94
|
+
type="submit"
|
|
95
|
+
disabled={isLoading}
|
|
96
|
+
className="w-full inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 bg-promptbook-blue-dark text-white hover:bg-promptbook-blue-dark/90 focus:outline-none focus:ring-2 focus:ring-promptbook-blue focus:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none transition-colors"
|
|
97
|
+
>
|
|
98
|
+
{isLoading ? (
|
|
99
|
+
<>
|
|
100
|
+
<Loader2 className="mr-2 w-4 h-4 animate-spin" />
|
|
101
|
+
Logging in...
|
|
102
|
+
</>
|
|
103
|
+
) : (
|
|
104
|
+
'Log in'
|
|
105
|
+
)}
|
|
106
|
+
</button>
|
|
107
|
+
</form>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Link from 'next/link';
|
|
2
|
+
import { ErrorPage } from '../ErrorPage/ErrorPage';
|
|
3
|
+
|
|
4
|
+
export function NotFoundPage() {
|
|
5
|
+
return (
|
|
6
|
+
<ErrorPage title="404 Not Found" message="The page you are looking for does not exist.">
|
|
7
|
+
<div className="flex justify-center">
|
|
8
|
+
<Link
|
|
9
|
+
href="/"
|
|
10
|
+
className="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
|
|
11
|
+
>
|
|
12
|
+
Go Home
|
|
13
|
+
</Link>
|
|
14
|
+
</div>
|
|
15
|
+
</ErrorPage>
|
|
16
|
+
);
|
|
17
|
+
}
|