sitepaige-mcp-server 0.7.14 → 1.0.2

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 (40) hide show
  1. package/README.md +94 -42
  2. package/components/form.tsx +133 -6
  3. package/components/login.tsx +173 -21
  4. package/components/menu.tsx +128 -3
  5. package/components/testimonial.tsx +1 -1
  6. package/defaultapp/api/Auth/route.ts +105 -3
  7. package/defaultapp/api/Auth/signup/route.ts +143 -0
  8. package/defaultapp/api/Auth/verify-email/route.ts +98 -0
  9. package/defaultapp/db-password-auth.ts +325 -0
  10. package/defaultapp/storage/email.ts +162 -0
  11. package/dist/blueprintWriter.js +15 -1
  12. package/dist/blueprintWriter.js.map +1 -1
  13. package/dist/components/form.tsx +133 -6
  14. package/dist/components/login.tsx +173 -21
  15. package/dist/components/menu.tsx +128 -3
  16. package/dist/components/testimonial.tsx +1 -1
  17. package/dist/defaultapp/api/Auth/route.ts +105 -3
  18. package/dist/defaultapp/api/Auth/signup/route.ts +143 -0
  19. package/dist/defaultapp/api/Auth/verify-email/route.ts +98 -0
  20. package/dist/defaultapp/db-password-auth.ts +325 -0
  21. package/dist/defaultapp/storage/email.ts +162 -0
  22. package/dist/generators/apis.js +1 -0
  23. package/dist/generators/apis.js.map +1 -1
  24. package/dist/generators/defaultapp.js +2 -2
  25. package/dist/generators/defaultapp.js.map +1 -1
  26. package/dist/generators/env-example-template.txt +27 -0
  27. package/dist/generators/images.js +38 -13
  28. package/dist/generators/images.js.map +1 -1
  29. package/dist/generators/pages.js +1 -1
  30. package/dist/generators/pages.js.map +1 -1
  31. package/dist/generators/sql.js +19 -0
  32. package/dist/generators/sql.js.map +1 -1
  33. package/dist/generators/views.js +17 -2
  34. package/dist/generators/views.js.map +1 -1
  35. package/dist/index.js +15 -116
  36. package/dist/index.js.map +1 -1
  37. package/dist/sitepaige.js +20 -127
  38. package/dist/sitepaige.js.map +1 -1
  39. package/manifest.json +5 -24
  40. package/package.json +3 -2
package/README.md CHANGED
@@ -8,11 +8,12 @@ Quickly transform your ideas into web applications with Sitepaige. Generate site
8
8
  - **Seamless authentication system** powered by OAuth - your users can sign in with one click
9
9
  - **All frontend components, pages, and views** ready to use
10
10
 
11
- ## Step 2: Backend Completion (50 credits - optional)
12
- - **Production-ready database architecture** (SQLite, PostgreSQL, or MySQL) - no setup headaches
11
+ ## Step 2: Backend Completion (FREE)
12
+ - **Production-ready database architecture** (PostgreSQL, SQLite, or MySQL) - simple environment configuration
13
13
  - **RESTful API endpoints** with complete input/output schemas and step-by-step implementation guidance
14
14
  - **Comprehensive [ARCHITECTURE.md](EXAMPLE_ARCHITECTURE.md) documentation** that maps out your entire codebase for your coding agent to finish the site - no guesswork required
15
15
  - **SQL models and migrations** ready for production
16
+ - **Environment configuration template** (`.env.example`) with all database options pre-configured
16
17
 
17
18
  ## Prerequisites
18
19
 
@@ -140,73 +141,117 @@ Generate the frontend of a web application from natural language prompts:
140
141
 
141
142
  The tool will:
142
143
  - Return immediately with a job ID and project ID
143
- - Generate the frontend asynchronously (typically takes 2-3 minutes)
144
- - Automatically write frontend files when generation is complete
145
- - **Cost**: FREE for your first project, then 12 credits
144
+ - Generate the complete site asynchronously (typically takes 8-10 minutes)
145
+ - Automatically write all files when generation is complete
146
+ - **Cost**: FREE for your first project, then credits based on your plan
147
+ - **Duration**: Takes approximately 8-10 minutes to complete
146
148
 
147
- **Note**: This generates only the frontend (pages, components, views, styles). To add backend functionality, use `complete_backend`. Frontend will
148
- require further generation after backend is completed
149
+ **Note**: This generates the complete web application including both frontend (pages, components, views, styles) and backend (models, APIs, database migrations)
149
150
 
150
- ### 2. Complete Backend (Optional)
151
+ ### 2. Check Status
151
152
 
152
- Add backend functionality to your generated site:
153
+ Monitor the progress of site generation using the job ID or project ID.
154
+ If a job has been completed but the files are not written, this function will write the files to disk.
155
+ If you are stalled out, use 'check the status of sitepaige' to get the latest status
153
156
 
154
- ```
155
- "Complete the backend for the project using the project ID from generate_site"
156
- ```
157
+ ## Database Configuration
157
158
 
158
- The tool will:
159
- - Add database models and SQL migrations
160
- - Generate API routes with full implementation
161
- - Create comprehensive ARCHITECTURE.md documentation
162
- - Preserve all frontend files (no overwrites)
163
- - **Cost**: 50 credits
159
+ The generated sites support PostgreSQL, MySQL, and SQLite databases. Here's how to configure PostgreSQL for your generated application:
164
160
 
165
- ### 3. Check Status
161
+ ### Setting Up PostgreSQL Environment Variables
166
162
 
167
- Monitor the progress of site generation using the job ID or project ID.
168
- If a job has been completed but the files are not written, this function will write the files to disk.
169
- If you are stalled out, use 'check the status of sitepaige' to get the latest status
163
+ After your site is generated, you'll need to configure the database connection. The generated site includes a `.env.example` file with all available options.
164
+
165
+ 1. **Copy the environment file**:
166
+ ```bash
167
+ cd /path/to/your/generated/site
168
+ cp .env.example .env
169
+ ```
170
+
171
+ 2. **Configure PostgreSQL**:
172
+ ```env
173
+ DATABASE_TYPE=postgres
174
+ DB_HOST=localhost # Default: localhost
175
+ DB_PORT=5432 # Default: 5432
176
+ DB_USER=postgres # Default: postgres
177
+ DB_PASSWORD=yourpassword # Required - no default
178
+ DB_NAME=app # Default: app
179
+ ```
180
+
181
+ **Important Notes**:
182
+ - The implementation uses only the generic `DB_*` variables
183
+ - `DB_PASSWORD` is required and has no default value
184
+ - Do not use `POSTGRES_*` or `DATABASE_URL` variables - they are not used by the implementation
185
+
186
+ 3. **SSL Configuration**:
187
+ - PostgreSQL: SSL is always enabled with `rejectUnauthorized: false` (accepts self-signed certificates)
188
+ - MySQL: No SSL configuration by default
189
+
190
+ 4. **Run database migrations**:
191
+ ```bash
192
+ npm run migrate
193
+ ```
170
194
 
171
- ## Cost
195
+ ### Alternative Database Options
172
196
 
173
- ### Frontend Generation (generate_site)
174
- - **First project**: FREE
175
- - **Additional projects**: 12 credits each
197
+ **MySQL Configuration**:
198
+ ```env
199
+ DATABASE_TYPE=mysql
200
+ DB_HOST=localhost # Default: localhost
201
+ DB_PORT=3306 # Default: 3306
202
+ DB_USER=root # Default: root
203
+ DB_PASSWORD=yourpassword # Required - no default
204
+ DB_NAME=app # Default: app
205
+ ```
206
+ **Note**: MySQL uses only the generic `DB_*` variables. Do not use `MYSQL_*` or `DATABASE_URL` variables.
207
+
208
+ **SQLite Configuration** (Default - No setup required):
209
+ ```env
210
+ DATABASE_TYPE=sqlite
211
+ DATABASE_URL=./data/app.db
212
+ # Or use SQLITE_DIR to specify the directory:
213
+ # SQLITE_DIR=./data
214
+ # SQLite creates the database file automatically
215
+ ```
176
216
 
177
- ### Backend Completion (complete_backend)
178
- - **All projects**: 50 credits each
217
+ ### Database Setup Requirements
179
218
 
180
- ### Total for a Complete Site
181
- - **First complete site**: 50 credits (free frontend + 50 credit backend)
182
- - **Additional complete sites**: 62 credits (12 credit frontend + 50 credit backend)
219
+ Before running your generated site with PostgreSQL:
183
220
 
184
- Purchase credits at https://sitepaige.com
221
+ 1. **Install PostgreSQL** if not already installed
222
+ 2. **Create a database** for your application:
223
+ ```sql
224
+ CREATE DATABASE your_app_name;
225
+ ```
226
+ 3. **Create a user** (optional, if not using default postgres user):
227
+ ```sql
228
+ CREATE USER your_username WITH PASSWORD 'your_password';
229
+ GRANT ALL PRIVILEGES ON DATABASE your_app_name TO your_username;
230
+ ```
185
231
 
186
232
  ## Example Workflow
187
233
 
188
234
  Here's a typical workflow for generating a complete site:
189
235
 
190
- 1. **Generate the frontend**:
236
+ 1. **Generate the complete site**:
191
237
  ```
192
238
  "Use sitepaige to create a task management app with user authentication in /Users/me/projects/taskapp"
193
239
  ```
194
- This returns a project ID (e.g., `proj_abc123`) and starts frontend generation.
240
+ This returns a job ID and project ID, and starts the complete site generation (frontend + backend).
195
241
 
196
242
  2. **Check status** (optional):
197
243
  ```
198
244
  "Check the status of the sitepaige generation"
199
245
  ```
200
246
 
201
- 3. **Complete the backend** (after frontend is done):
202
- ```
203
- "Complete the backend for project proj_abc123"
204
- ```
205
- This adds database models, API routes, and full documentation.
206
-
247
+ 3. **Configure the database**:
248
+ - Navigate to the generated site directory
249
+ - Copy `.env.example` to `.env`
250
+ - Configure your database settings (see Database Configuration section above)
251
+
207
252
  4. **Start developing**:
208
- - Frontend is ready immediately after step 1
209
- - Full backend functionality available after step 3
253
+ - Complete site (frontend + backend) is ready after generation completes (8-10 minutes)
254
+ - Run `npm run migrate` to set up the database
210
255
  - Use the generated ARCHITECTURE.md as a guide
211
256
 
212
257
  ## Troubleshooting
@@ -216,6 +261,7 @@ Here's a typical workflow for generating a complete site:
216
261
  1. **"Insufficient credits" error**
217
262
  - Sign up for a SitePaige API key at https://sitepaige.com
218
263
  - Add the API key to your environment configuration
264
+ - Purchase credits, if needed
219
265
 
220
266
  2. **API "401 Unauthorized Error" Error**
221
267
  - Verify your API key is correctly set in the environment
@@ -226,6 +272,12 @@ Here's a typical workflow for generating a complete site:
226
272
  - Ensure the target directory is within `SITEPAIGE_ALLOWED_ROOTS`
227
273
  - Check that the directory exists and you have write permissions
228
274
 
275
+ 4. **Database connection errors**
276
+ - For PostgreSQL: Ensure the database service is running (`pg_ctl status` or `systemctl status postgresql`)
277
+ - Verify the connection details (default PostgreSQL port is 5432)
278
+ - Check that the database and user exist with proper permissions
279
+ - For cloud databases, ensure SSL settings are configured correctly
280
+
229
281
 
230
282
  ## License
231
283
 
@@ -9,6 +9,133 @@ interface RFormProps {
9
9
  design: Design;
10
10
  }
11
11
 
12
+ // Translation function for form submit text
13
+ const getFormTranslation = (key: string, websiteLanguage: string) => {
14
+ const translations: Record<string, Record<string, string>> = {
15
+ 'Submit': {
16
+ 'English': 'Submit',
17
+ 'Spanish': 'Enviar',
18
+ 'French': 'Soumettre',
19
+ 'German': 'Absenden',
20
+ 'Portuguese': 'Enviar',
21
+ 'Italian': 'Invia',
22
+ 'Dutch': 'Versturen',
23
+ 'Chinese': '提交',
24
+ 'Japanese': '送信',
25
+ 'Korean': '제출',
26
+ 'Russian': 'Отправить',
27
+ 'Arabic': 'إرسال',
28
+ 'Hindi': 'सबमिट करें'
29
+ },
30
+ 'Submitting...': {
31
+ 'English': 'Submitting...',
32
+ 'Spanish': 'Enviando...',
33
+ 'French': 'Envoi en cours...',
34
+ 'German': 'Wird gesendet...',
35
+ 'Portuguese': 'Enviando...',
36
+ 'Italian': 'Invio in corso...',
37
+ 'Dutch': 'Bezig met versturen...',
38
+ 'Chinese': '提交中...',
39
+ 'Japanese': '送信中...',
40
+ 'Korean': '제출 중...',
41
+ 'Russian': 'Отправка...',
42
+ 'Arabic': 'جاري الإرسال...',
43
+ 'Hindi': 'सबमिट हो रहा है...'
44
+ },
45
+ 'Submit Another Response': {
46
+ 'English': 'Submit Another Response',
47
+ 'Spanish': 'Enviar otra respuesta',
48
+ 'French': 'Soumettre une autre réponse',
49
+ 'German': 'Weitere Antwort senden',
50
+ 'Portuguese': 'Enviar outra resposta',
51
+ 'Italian': 'Invia un\'altra risposta',
52
+ 'Dutch': 'Nog een reactie versturen',
53
+ 'Chinese': '提交另一个回复',
54
+ 'Japanese': '別の回答を送信',
55
+ 'Korean': '다른 응답 제출',
56
+ 'Russian': 'Отправить еще один ответ',
57
+ 'Arabic': 'إرسال رد آخر',
58
+ 'Hindi': 'एक और प्रतिक्रिया सबमिट करें'
59
+ },
60
+ 'Thank You!': {
61
+ 'English': 'Thank You!',
62
+ 'Spanish': '¡Gracias!',
63
+ 'French': 'Merci!',
64
+ 'German': 'Vielen Dank!',
65
+ 'Portuguese': 'Obrigado!',
66
+ 'Italian': 'Grazie!',
67
+ 'Dutch': 'Bedankt!',
68
+ 'Chinese': '谢谢!',
69
+ 'Japanese': 'ありがとうございます!',
70
+ 'Korean': '감사합니다!',
71
+ 'Russian': 'Спасибо!',
72
+ 'Arabic': 'شكراً لك!',
73
+ 'Hindi': 'धन्यवाद!'
74
+ },
75
+ 'Your form has been submitted successfully. We\'ll get back to you soon.': {
76
+ 'English': 'Your form has been submitted successfully. We\'ll get back to you soon.',
77
+ 'Spanish': 'Tu formulario ha sido enviado exitosamente. Nos pondremos en contacto contigo pronto.',
78
+ 'French': 'Votre formulaire a été soumis avec succès. Nous vous contacterons bientôt.',
79
+ 'German': 'Ihr Formular wurde erfolgreich gesendet. Wir werden uns bald bei Ihnen melden.',
80
+ 'Portuguese': 'Seu formulário foi enviado com sucesso. Entraremos em contato em breve.',
81
+ 'Italian': 'Il tuo modulo è stato inviato con successo. Ti contatteremo presto.',
82
+ 'Dutch': 'Uw formulier is succesvol verzonden. We nemen binnenkort contact met u op.',
83
+ 'Chinese': '您的表单已成功提交。我们会尽快与您联系。',
84
+ 'Japanese': 'フォームが正常に送信されました。近日中にご連絡いたします。',
85
+ 'Korean': '양식이 성공적으로 제출되었습니다. 곧 연락드리겠습니다.',
86
+ 'Russian': 'Ваша форма успешно отправлена. Мы свяжемся с вами в ближайшее время.',
87
+ 'Arabic': 'تم إرسال النموذج بنجاح. سنتواصل معك قريباً.',
88
+ 'Hindi': 'आपका फॉर्म सफलतापूर्वक सबमिट किया गया है। हम जल्द ही आपसे संपर्क करेंगे।'
89
+ },
90
+ 'Form secured by FormSubmit': {
91
+ 'English': 'Form secured by FormSubmit',
92
+ 'Spanish': 'Formulario protegido por FormSubmit',
93
+ 'French': 'Formulaire sécurisé par FormSubmit',
94
+ 'German': 'Formular gesichert durch FormSubmit',
95
+ 'Portuguese': 'Formulário protegido por FormSubmit',
96
+ 'Italian': 'Modulo protetto da FormSubmit',
97
+ 'Dutch': 'Formulier beveiligd door FormSubmit',
98
+ 'Chinese': '表单由FormSubmit保护',
99
+ 'Japanese': 'FormSubmitによって保護されたフォーム',
100
+ 'Korean': 'FormSubmit로 보호된 양식',
101
+ 'Russian': 'Форма защищена FormSubmit',
102
+ 'Arabic': 'النموذج محمي بواسطة FormSubmit',
103
+ 'Hindi': 'फॉर्म FormSubmit द्वारा सुरक्षित'
104
+ }
105
+ };
106
+
107
+ // Determine which language to use
108
+ let language = 'English'; // Default
109
+ if (websiteLanguage) {
110
+ // Extract the main language from the input
111
+ const langMap: Record<string, string> = {
112
+ 'English': 'English',
113
+ 'Spanish': 'Spanish',
114
+ 'French': 'French',
115
+ 'German': 'German',
116
+ 'Portuguese': 'Portuguese',
117
+ 'Italian': 'Italian',
118
+ 'Dutch': 'Dutch',
119
+ 'Chinese': 'Chinese',
120
+ 'Japanese': 'Japanese',
121
+ 'Korean': 'Korean',
122
+ 'Russian': 'Russian',
123
+ 'Arabic': 'Arabic',
124
+ 'Hindi': 'Hindi'
125
+ };
126
+
127
+ // Find the matching language
128
+ for (const [langKey, langValue] of Object.entries(langMap)) {
129
+ if (websiteLanguage.includes(langKey)) {
130
+ language = langValue;
131
+ break;
132
+ }
133
+ }
134
+ }
135
+
136
+ return translations[key]?.[language] || key;
137
+ };
138
+
12
139
  interface FormField {
13
140
  id: string;
14
141
  name: string;
@@ -85,9 +212,9 @@ export default function RForm({ name, custom_view_description, design }: RFormPr
85
212
  d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
86
213
  />
87
214
  </svg>
88
- <h3 className="text-2xl font-bold text-green-800 mb-2">Thank You!</h3>
215
+ <h3 className="text-2xl font-bold text-green-800 mb-2">{getFormTranslation('Thank You!', design.websiteLanguage)}</h3>
89
216
  <p className="text-green-700">
90
- Your form has been submitted successfully. We'll get back to you soon.
217
+ {getFormTranslation('Your form has been submitted successfully. We\'ll get back to you soon.', design.websiteLanguage)}
91
218
  </p>
92
219
  <button
93
220
  onClick={() => {
@@ -97,7 +224,7 @@ export default function RForm({ name, custom_view_description, design }: RFormPr
97
224
  }}
98
225
  className="mt-6 px-6 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors"
99
226
  >
100
- Submit Another Response
227
+ {getFormTranslation('Submit Another Response', design.websiteLanguage)}
101
228
  </button>
102
229
  </div>
103
230
  </div>
@@ -249,10 +376,10 @@ export default function RForm({ name, custom_view_description, design }: RFormPr
249
376
  d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
250
377
  />
251
378
  </svg>
252
- Submitting...
379
+ {getFormTranslation('Submitting...', design.websiteLanguage)}
253
380
  </span>
254
381
  ) : (
255
- 'Submit'
382
+ getFormTranslation('Submit', design.websiteLanguage)
256
383
  )}
257
384
  </button>
258
385
  </div>
@@ -260,7 +387,7 @@ export default function RForm({ name, custom_view_description, design }: RFormPr
260
387
 
261
388
  {/* FormSubmit.co attribution (optional but nice to have) */}
262
389
  <div className="mt-6 text-center text-xs text-gray-400">
263
- <p>Form secured by FormSubmit</p>
390
+ <p>{getFormTranslation('Form secured by FormSubmit', design.websiteLanguage)}</p>
264
391
  </div>
265
392
  </div>
266
393
  );
@@ -9,11 +9,17 @@ checked in the system build settings. It is safe to modify this file without it
9
9
  import React, { useState } from 'react';
10
10
 
11
11
  interface LoginProps {
12
- providers: ('apple' | 'facebook' | 'github' | 'google')[];
12
+ providers: ('apple' | 'facebook' | 'github' | 'google' | 'username')[];
13
13
  }
14
14
 
15
15
  export default function Login({ providers }: LoginProps) {
16
16
  const [error, setError] = useState<string | null>(null);
17
+ const [isSignup, setIsSignup] = useState(false);
18
+ const [email, setEmail] = useState('');
19
+ const [password, setPassword] = useState('');
20
+ const [confirmPassword, setConfirmPassword] = useState('');
21
+ const [isLoading, setIsLoading] = useState(false);
22
+ const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
17
23
 
18
24
 
19
25
  const handleProviderLogin = async (provider: string) => {
@@ -43,37 +49,183 @@ export default function Login({ providers }: LoginProps) {
43
49
 
44
50
  };
45
51
 
52
+ const handleUsernamePasswordAuth = async (e: React.FormEvent) => {
53
+ e.preventDefault();
54
+ setError(null);
55
+ setMessage(null);
56
+ setIsLoading(true);
57
+
58
+ try {
59
+ if (isSignup) {
60
+ // Handle signup
61
+ if (password !== confirmPassword) {
62
+ setError('Passwords do not match');
63
+ setIsLoading(false);
64
+ return;
65
+ }
66
+
67
+ const response = await fetch('/api/Auth/signup', {
68
+ method: 'POST',
69
+ headers: { 'Content-Type': 'application/json' },
70
+ body: JSON.stringify({ email, password })
71
+ });
72
+
73
+ const data = await response.json();
74
+
75
+ if (response.ok) {
76
+ setMessage({ type: 'success', text: 'Verification email sent! Please check your inbox.' });
77
+ setIsSignup(false);
78
+ setPassword('');
79
+ setConfirmPassword('');
80
+ } else {
81
+ setError(data.error || 'Signup failed');
82
+ }
83
+ } else {
84
+ // Handle login
85
+ const response = await fetch('/api/Auth', {
86
+ method: 'POST',
87
+ headers: { 'Content-Type': 'application/json' },
88
+ body: JSON.stringify({ email, password, provider: 'username' })
89
+ });
90
+
91
+ const data = await response.json();
92
+
93
+ if (response.ok) {
94
+ // Redirect to home or dashboard
95
+ window.location.href = '/';
96
+ } else {
97
+ setError(data.error || 'Login failed');
98
+ }
99
+ }
100
+ } catch (err) {
101
+ setError('An unexpected error occurred');
102
+ } finally {
103
+ setIsLoading(false);
104
+ }
105
+ };
106
+
107
+ const showUsernamePasswordForm = providers?.includes('username');
108
+ const oauthProviders = providers?.filter(p => p !== 'username') || [];
109
+
46
110
  return (
47
111
  <div className="flex flex-col items-center justify-center min-h-[500px] p-4">
48
112
  <div className="w-full max-w-md space-y-8">
49
113
  <div className="text-center">
50
- <h2 className="text-3xl font-bold">Sign in to your account</h2>
114
+ <h2 className="text-3xl font-bold">{isSignup ? 'Create an account' : 'Sign in to your account'}</h2>
51
115
  </div>
52
116
 
53
- {(
54
- <div className="mt-6">
55
- <div className="relative">
56
- <div className="absolute inset-0 flex items-center">
57
- <div className="w-full border-t"></div>
117
+ {showUsernamePasswordForm && (
118
+ <form onSubmit={handleUsernamePasswordAuth} className="mt-8 space-y-6">
119
+ <div className="space-y-4">
120
+ <div>
121
+ <label htmlFor="email" className="block text-sm font-medium text-gray-700">
122
+ Email address
123
+ </label>
124
+ <input
125
+ id="email"
126
+ name="email"
127
+ type="email"
128
+ autoComplete="email"
129
+ required
130
+ value={email}
131
+ onChange={(e) => setEmail(e.target.value)}
132
+ className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
133
+ />
58
134
  </div>
59
- <div className="relative flex justify-center text-sm">
60
- <span className="px-2 bg-white text-gray-500">Continue with</span>
135
+
136
+ <div>
137
+ <label htmlFor="password" className="block text-sm font-medium text-gray-700">
138
+ Password
139
+ </label>
140
+ <input
141
+ id="password"
142
+ name="password"
143
+ type="password"
144
+ autoComplete={isSignup ? 'new-password' : 'current-password'}
145
+ required
146
+ value={password}
147
+ onChange={(e) => setPassword(e.target.value)}
148
+ className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
149
+ />
61
150
  </div>
62
- </div>
63
151
 
64
- <div className="mt-6 grid grid-cols-2 gap-3">
65
- {providers?.map(provider =>
66
- (
67
- <button
68
- key={provider}
69
- onClick={() => handleProviderLogin(provider)}
70
- className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 transition duration-150 classButtonRounding classButtonBackground classButtonFontType classButtonFontSize"
71
- >
72
- {provider}
73
- </button>
74
- )
152
+ {isSignup && (
153
+ <div>
154
+ <label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700">
155
+ Confirm Password
156
+ </label>
157
+ <input
158
+ id="confirmPassword"
159
+ name="confirmPassword"
160
+ type="password"
161
+ autoComplete="new-password"
162
+ required
163
+ value={confirmPassword}
164
+ onChange={(e) => setConfirmPassword(e.target.value)}
165
+ className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
166
+ />
167
+ </div>
75
168
  )}
76
169
  </div>
170
+
171
+ <div>
172
+ <button
173
+ type="submit"
174
+ disabled={isLoading}
175
+ className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50"
176
+ >
177
+ {isLoading ? 'Processing...' : (isSignup ? 'Sign up' : 'Sign in')}
178
+ </button>
179
+ </div>
180
+
181
+ <div className="text-center">
182
+ <button
183
+ type="button"
184
+ onClick={() => {
185
+ setIsSignup(!isSignup);
186
+ setError(null);
187
+ setMessage(null);
188
+ }}
189
+ className="text-sm text-indigo-600 hover:text-indigo-500"
190
+ >
191
+ {isSignup ? 'Already have an account? Sign in' : "Don't have an account? Sign up"}
192
+ </button>
193
+ </div>
194
+ </form>
195
+ )}
196
+
197
+ {oauthProviders.length > 0 && (
198
+ <div className="mt-6">
199
+ {showUsernamePasswordForm && (
200
+ <>
201
+ <div className="relative">
202
+ <div className="absolute inset-0 flex items-center">
203
+ <div className="w-full border-t"></div>
204
+ </div>
205
+ <div className="relative flex justify-center text-sm">
206
+ <span className="px-2 bg-white text-gray-500">Or continue with</span>
207
+ </div>
208
+ </div>
209
+ </>
210
+ )}
211
+
212
+ <div className={`mt-6 grid grid-cols-${Math.min(oauthProviders.length, 2)} gap-3`}>
213
+ {oauthProviders.map(provider => (
214
+ <button
215
+ key={provider}
216
+ onClick={() => handleProviderLogin(provider)}
217
+ className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 transition duration-150 classButtonRounding classButtonBackground classButtonFontType classButtonFontSize"
218
+ >
219
+ {provider}
220
+ </button>
221
+ ))}
222
+ </div>
223
+ </div>
224
+ )}
225
+
226
+ {message && (
227
+ <div className={`mt-4 text-center ${message.type === 'success' ? 'text-green-600' : 'text-red-600'}`}>
228
+ {message.text}
77
229
  </div>
78
230
  )}
79
231