@promptbook/cli 0.103.0-47 → 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.
Files changed (133) hide show
  1. package/apps/agents-server/README.md +1 -1
  2. package/apps/agents-server/TODO.txt +6 -5
  3. package/apps/agents-server/config.ts +130 -0
  4. package/apps/agents-server/next.config.ts +1 -1
  5. package/apps/agents-server/public/fonts/OpenMoji-black-glyf.woff2 +0 -0
  6. package/apps/agents-server/public/fonts/download-font.js +22 -0
  7. package/apps/agents-server/src/app/[agentName]/[...rest]/page.tsx +6 -0
  8. package/apps/agents-server/src/app/[agentName]/page.tsx +1 -0
  9. package/apps/agents-server/src/app/actions.ts +37 -2
  10. package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +68 -0
  11. package/apps/agents-server/src/app/agents/[agentName]/AgentQrCode.tsx +55 -0
  12. package/apps/agents-server/src/app/agents/[agentName]/AgentUrlCopy.tsx +4 -5
  13. package/apps/agents-server/src/app/agents/[agentName]/CopyField.tsx +44 -0
  14. package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +8 -8
  15. package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +100 -18
  16. package/apps/agents-server/src/app/agents/[agentName]/api/feedback/route.ts +54 -0
  17. package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/route.ts +6 -6
  18. package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/systemMessage/route.ts +3 -3
  19. package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +6 -7
  20. package/apps/agents-server/src/app/agents/[agentName]/book/BookEditorWrapper.tsx +6 -7
  21. package/apps/agents-server/src/app/agents/[agentName]/book/page.tsx +9 -2
  22. package/apps/agents-server/src/app/agents/[agentName]/book+chat/AgentBookAndChat.tsx +23 -0
  23. package/apps/agents-server/src/app/agents/[agentName]/book+chat/{AgentBookAndChatComponent.tsx → AgentBookAndChatComponent.tsx.todo} +6 -8
  24. package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx +28 -17
  25. package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx.todo +21 -0
  26. package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatWrapper.tsx +34 -4
  27. package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +4 -1
  28. package/apps/agents-server/src/app/agents/[agentName]/generateAgentMetadata.ts +42 -0
  29. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +111 -108
  30. package/apps/agents-server/src/app/agents/page.tsx +1 -1
  31. package/apps/agents-server/src/app/api/auth/login/route.ts +65 -0
  32. package/apps/agents-server/src/app/api/auth/logout/route.ts +7 -0
  33. package/apps/agents-server/src/app/api/metadata/route.ts +116 -0
  34. package/apps/agents-server/src/app/api/upload/route.ts +7 -7
  35. package/apps/agents-server/src/app/api/users/[username]/route.ts +75 -0
  36. package/apps/agents-server/src/app/api/users/route.ts +71 -0
  37. package/apps/agents-server/src/app/globals.css +35 -1
  38. package/apps/agents-server/src/app/layout.tsx +43 -23
  39. package/apps/agents-server/src/app/metadata/MetadataClient.tsx +271 -0
  40. package/apps/agents-server/src/app/metadata/page.tsx +13 -0
  41. package/apps/agents-server/src/app/not-found.tsx +5 -0
  42. package/apps/agents-server/src/app/page.tsx +84 -46
  43. package/apps/agents-server/src/components/Auth/AuthControls.tsx +123 -0
  44. package/apps/agents-server/src/components/ErrorPage/ErrorPage.tsx +33 -0
  45. package/apps/agents-server/src/components/ForbiddenPage/ForbiddenPage.tsx +15 -0
  46. package/apps/agents-server/src/components/Header/Header.tsx +146 -0
  47. package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +27 -0
  48. package/apps/agents-server/src/components/LoginDialog/LoginDialog.tsx +40 -0
  49. package/apps/agents-server/src/components/LoginForm/LoginForm.tsx +109 -0
  50. package/apps/agents-server/src/components/NotFoundPage/NotFoundPage.tsx +17 -0
  51. package/apps/agents-server/src/components/UsersList/UsersList.tsx +190 -0
  52. package/apps/agents-server/src/components/VercelDeploymentCard/VercelDeploymentCard.tsx +60 -0
  53. package/apps/agents-server/src/database/$getTableName.ts +18 -0
  54. package/apps/agents-server/src/database/$provideSupabase.ts +29 -0
  55. package/apps/agents-server/src/{supabase/getSupabaseForBrowser.ts → database/$provideSupabaseForBrowser.ts} +9 -5
  56. package/apps/agents-server/src/{supabase/getSupabaseForServer.ts → database/$provideSupabaseForServer.ts} +7 -7
  57. package/apps/agents-server/src/{supabase/getSupabaseForWorker.ts → database/$provideSupabaseForWorker.ts} +5 -4
  58. package/apps/agents-server/src/database/getMetadata.ts +31 -0
  59. package/apps/agents-server/src/database/metadataDefaults.ts +32 -0
  60. package/apps/agents-server/src/database/schema.sql +179 -0
  61. package/apps/agents-server/src/database/schema.ts +251 -0
  62. package/apps/agents-server/src/middleware.ts +162 -0
  63. package/apps/agents-server/src/tools/$provideAgentCollectionForServer.ts +14 -10
  64. package/apps/agents-server/src/tools/$provideCdnForServer.ts +1 -1
  65. package/apps/agents-server/src/tools/$provideExecutionToolsForServer.ts +11 -13
  66. package/apps/agents-server/src/tools/$provideOpenAiAssistantExecutionToolsForServer.ts +7 -7
  67. package/apps/agents-server/src/tools/$provideServer.ts +39 -0
  68. package/apps/agents-server/src/utils/auth.ts +33 -0
  69. package/apps/agents-server/src/utils/cdn/utils/getUserFileCdnKey.ts +2 -1
  70. package/apps/agents-server/src/utils/cdn/utils/nameToSubfolderPath.ts +1 -1
  71. package/apps/agents-server/src/utils/getCurrentUser.ts +32 -0
  72. package/apps/agents-server/src/utils/isIpAllowed.ts +101 -0
  73. package/apps/agents-server/src/utils/isUserAdmin.ts +31 -0
  74. package/apps/agents-server/src/utils/session.ts +50 -0
  75. package/apps/agents-server/tailwind.config.ts +2 -0
  76. package/esm/index.es.js +310 -49
  77. package/esm/index.es.js.map +1 -1
  78. package/esm/typings/servers.d.ts +1 -0
  79. package/esm/typings/src/_packages/core.index.d.ts +6 -0
  80. package/esm/typings/src/_packages/types.index.d.ts +4 -0
  81. package/esm/typings/src/_packages/utils.index.d.ts +2 -0
  82. package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +17 -3
  83. package/esm/typings/src/book-2.0/agent-source/AgentSourceParseResult.d.ts +2 -1
  84. package/esm/typings/src/book-2.0/agent-source/computeAgentHash.d.ts +8 -0
  85. package/esm/typings/src/book-2.0/agent-source/computeAgentHash.test.d.ts +1 -0
  86. package/esm/typings/src/book-2.0/agent-source/createDefaultAgentName.d.ts +8 -0
  87. package/esm/typings/src/book-2.0/agent-source/normalizeAgentName.d.ts +9 -0
  88. package/esm/typings/src/book-2.0/agent-source/normalizeAgentName.test.d.ts +1 -0
  89. package/esm/typings/src/book-2.0/agent-source/parseAgentSourceWithCommitments.d.ts +1 -1
  90. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +14 -8
  91. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabaseOptions.d.ts +10 -0
  92. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentsDatabaseSchema.d.ts +57 -32
  93. package/esm/typings/src/commitments/MESSAGE/InitialMessageCommitmentDefinition.d.ts +28 -0
  94. package/esm/typings/src/commitments/index.d.ts +2 -1
  95. package/esm/typings/src/config.d.ts +1 -0
  96. package/esm/typings/src/errors/DatabaseError.d.ts +2 -2
  97. package/esm/typings/src/errors/WrappedError.d.ts +2 -2
  98. package/esm/typings/src/execution/ExecutionTask.d.ts +2 -2
  99. package/esm/typings/src/execution/LlmExecutionTools.d.ts +6 -1
  100. package/esm/typings/src/llm-providers/_common/register/$provideLlmToolsForWizardOrCli.d.ts +2 -2
  101. package/esm/typings/src/llm-providers/_common/utils/assertUniqueModels.d.ts +12 -0
  102. package/esm/typings/src/llm-providers/agent/Agent.d.ts +17 -4
  103. package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +10 -1
  104. package/esm/typings/src/llm-providers/agent/RemoteAgent.d.ts +6 -2
  105. package/esm/typings/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +30 -4
  106. package/esm/typings/src/llm-providers/openai/openai-models.test.d.ts +4 -0
  107. package/esm/typings/src/remote-server/startAgentServer.d.ts +2 -2
  108. package/esm/typings/src/remote-server/startRemoteServer.d.ts +1 -2
  109. package/esm/typings/src/transpilers/openai-sdk/register.d.ts +1 -1
  110. package/esm/typings/src/types/typeAliases.d.ts +6 -0
  111. package/esm/typings/src/utils/color/Color.d.ts +7 -0
  112. package/esm/typings/src/utils/color/Color.test.d.ts +1 -0
  113. package/esm/typings/src/utils/environment/$getGlobalScope.d.ts +2 -2
  114. package/esm/typings/src/utils/misc/computeHash.d.ts +11 -0
  115. package/esm/typings/src/utils/misc/computeHash.test.d.ts +1 -0
  116. package/esm/typings/src/utils/normalization/normalize-to-kebab-case.d.ts +2 -0
  117. package/esm/typings/src/utils/normalization/normalizeTo_PascalCase.d.ts +3 -0
  118. package/esm/typings/src/utils/normalization/normalizeTo_camelCase.d.ts +2 -0
  119. package/esm/typings/src/utils/normalization/titleToName.d.ts +2 -0
  120. package/esm/typings/src/utils/organization/$sideEffect.d.ts +2 -2
  121. package/esm/typings/src/utils/organization/$side_effect.d.ts +2 -2
  122. package/esm/typings/src/utils/organization/TODO_USE.d.ts +2 -2
  123. package/esm/typings/src/utils/organization/keepUnused.d.ts +2 -2
  124. package/esm/typings/src/utils/organization/preserve.d.ts +3 -3
  125. package/esm/typings/src/utils/organization/really_any.d.ts +7 -0
  126. package/esm/typings/src/utils/serialization/asSerializable.d.ts +2 -2
  127. package/esm/typings/src/version.d.ts +1 -1
  128. package/package.json +1 -1
  129. package/umd/index.umd.js +311 -50
  130. package/umd/index.umd.js.map +1 -1
  131. package/apps/agents-server/config.ts.todo +0 -312
  132. package/apps/agents-server/src/supabase/TODO.txt +0 -1
  133. package/apps/agents-server/src/supabase/getSupabase.ts +0 -25
@@ -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
+ }