Skip to content

Commit 04be5d0

Browse files
committed
Form fixes
1 parent eb8a160 commit 04be5d0

3 files changed

Lines changed: 124 additions & 91 deletions

File tree

TechStacks.Client/src/components/forms/PostForm.tsx

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
import { useState, useEffect } from 'react';
44
import { useRouter } from 'next/navigation';
55
import Link from 'next/link';
6-
import { PrimaryButton, ErrorSummary, TextInput, MarkdownInput, SelectInput, FileInput, ConfirmDelete, CloseButton, SecondaryButton, Combobox } from '@servicestack/react';
76
import { useRequireAuth } from '@/lib/hooks/useRequireAuth';
87
import routes from '@/lib/utils/routes';
98
import * as gateway from '@/lib/api/gateway';
10-
import { ResponseStatus } from '@servicestack/client';
9+
import { ResponseStatus, toFormData } from '@servicestack/client';
10+
import {
11+
PrimaryButton, ErrorSummary, TextInput, MarkdownInput, SelectInput, FileInput, ConfirmDelete,
12+
CloseButton, SecondaryButton, Combobox, useClient, ApiStateContext
13+
} from '@servicestack/react';
14+
import { CreatePost, UpdatePost } from '@/shared/dtos';
1115

1216
interface PostFormProps {
1317
postId?: number;
@@ -19,6 +23,7 @@ const NEWS_CATEGORY_ID = 55;
1923
export function PostForm({ postId, onDone }: PostFormProps) {
2024
const router = useRouter();
2125
const isAuthenticated = useRequireAuth();
26+
const client = useClient()
2227
const [loading, setLoading] = useState(false);
2328
const [error, setError] = useState<ResponseStatus>();
2429
const [loadingPost, setLoadingPost] = useState(!!postId);
@@ -133,25 +138,27 @@ export function PostForm({ postId, onDone }: PostFormProps) {
133138
categoryId: NEWS_CATEGORY_ID,
134139
};
135140

141+
let api;
136142
if (postId) {
137143
// Update existing post
138-
await gateway.updatePost({ ...postData, id: postId }, iconFile);
144+
//await gateway.updatePost({ ...postData, id: postId }, iconFile);
145+
const body = toFormData({ ...postData, id: postId, icon: iconFile });
146+
api = await client.apiForm(new UpdatePost(), body);
139147
} else {
140148
// Create new post
141-
await gateway.createPost(postData, iconFile);
149+
//await gateway.createPost(postData, iconFile);
150+
const body = toFormData({ ...postData, icon: iconFile });
151+
api = await client.apiForm(new CreatePost(), body);
142152
}
143153

144-
if (onDone) {
145-
onDone();
154+
if (api.succeeded) {
155+
if (onDone) {
156+
onDone();
157+
} else {
158+
router.push('/');
159+
}
146160
} else {
147-
router.push('/');
148-
}
149-
} catch (err: any) {
150-
console.error(`Failed to ${postId ? 'update' : 'create'} post:`, err);
151-
if (err.responseStatus) {
152-
setError(err.responseStatus);
153-
} else {
154-
setError({ message: err.message || 'Failed to create post' });
161+
setError(api.error);
155162
}
156163
} finally {
157164
setLoading(false);
@@ -199,16 +206,16 @@ export function PostForm({ postId, onDone }: PostFormProps) {
199206
}
200207

201208
return (
209+
<ApiStateContext.Provider value={client}>
202210
<form onSubmit={handleSubmit}>
203-
<ErrorSummary except={['type', 'title', 'url', 'content', 'technologyIds']} status={error} />
204211
<div className="relative shadow sm:overflow-hidden sm:rounded-md">
205212
{postId && (<CloseButton onClose={onDone} />)}
206213
<div className="space-y-6 py-6 px-4 sm:p-6 bg-white dark:bg-black">
207214
<h3 className="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">
208215
{postId ? 'Edit Post' : 'Create New Post'}
209216
</h3>
210217
<fieldset>
211-
<ErrorSummary except={['type', 'title', 'url', 'content', 'technologyIds']} className="mb-4" />
218+
<ErrorSummary except={['type', 'title', 'url', 'content', 'technologyIds']} status={error} className="mb-4" />
212219
<div className="grid grid-cols-6 gap-6">
213220
<div className="col-span-3">
214221
<SelectInput
@@ -292,5 +299,6 @@ export function PostForm({ postId, onDone }: PostFormProps) {
292299
</div>
293300
</div>
294301
</form>
302+
</ApiStateContext.Provider>
295303
);
296304
}

TechStacks.Client/src/components/forms/TechStackForm.tsx

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
import { useState, useEffect } from 'react';
44
import { useRouter } from 'next/navigation';
55
import Link from 'next/link';
6-
import { TextInput, TextareaInput, MarkdownInput, CheckboxInput, ConfirmDelete, PrimaryButton, Combobox, CloseButton } from '@servicestack/react';
6+
import {
7+
TextInput, TextareaInput, MarkdownInput, CheckboxInput, ConfirmDelete, PrimaryButton,
8+
Combobox, CloseButton, useClient, ApiStateContext, ErrorSummary, SecondaryButton, FileInput
9+
} from '@servicestack/react';
10+
import { ResponseStatus, toFormData } from '@servicestack/client';
711
import { useRequireAuth } from '@/lib/hooks/useRequireAuth';
812
import * as gateway from '@/lib/api/gateway';
913
import routes from '@/lib/utils/routes';
10-
import { ResponseStatus } from '@servicestack/client';
14+
import { CreateTechnologyStack, UpdateTechnologyStack } from '@/shared/dtos';
1115

1216
interface TechStackFormProps {
1317
slug?: string;
@@ -17,6 +21,7 @@ interface TechStackFormProps {
1721
export function TechStackForm({ slug, onDone }: TechStackFormProps) {
1822
const router = useRouter();
1923
const isAuthenticated = useRequireAuth();
24+
const client = useClient();
2025
const [loading, setLoading] = useState(false);
2126
const [error, setError] = useState<ResponseStatus>();
2227
const [technologies, setTechnologies] = useState<Record<string, string>>({});
@@ -126,7 +131,6 @@ export function TechStackForm({ slug, onDone }: TechStackFormProps) {
126131
const handleSubmit = async (e: React.FormEvent) => {
127132
e.preventDefault();
128133
setLoading(true);
129-
setError(undefined);
130134

131135
try {
132136
// Convert technologyIds from strings to numbers for the API
@@ -135,22 +139,23 @@ export function TechStackForm({ slug, onDone }: TechStackFormProps) {
135139
technologyIds: formData.technologyIds.map(id => parseInt(id, 10))
136140
};
137141

142+
let api;
138143
if (isUpdate) {
139-
await gateway.updateTechStack(submitData, screenshotFile || undefined);
144+
const body = toFormData({ ...submitData, screenshot: screenshotFile || undefined });
145+
api = await client.apiForm(new UpdateTechnologyStack(), body);
140146
} else {
141-
await gateway.createTechStack(submitData, screenshotFile || undefined);
147+
const body = toFormData({ ...submitData, screenshot: screenshotFile || undefined });
148+
api = await client.apiForm(new CreateTechnologyStack(), body);
142149
}
143150

144-
if (onDone) {
145-
onDone();
151+
if (api.succeeded) {
152+
if (onDone) {
153+
onDone();
154+
} else {
155+
router.push(routes.stack(formData.slug));
156+
}
146157
} else {
147-
router.push(routes.stack(formData.slug));
148-
}
149-
} catch (err: any) {
150-
if (err.responseStatus) {
151-
setError(err.responseStatus);
152-
} else {
153-
setError({ message: err.message || 'Failed to save tech stack' });
158+
setError(api.error);
154159
}
155160
} finally {
156161
setLoading(false);
@@ -189,19 +194,16 @@ export function TechStackForm({ slug, onDone }: TechStackFormProps) {
189194
}
190195

191196
return (
197+
<ApiStateContext.Provider value={client}>
192198
<form onSubmit={handleSubmit}>
193199
<div className="relative shadow sm:overflow-hidden sm:rounded-md">
200+
{slug && onDone && (<CloseButton onClose={onDone} />)}
194201
<div className="space-y-6 py-6 px-4 sm:p-6 bg-white dark:bg-black">
195-
<CloseButton onClose={onDone} />
196202
<h2 className="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">
197203
{isUpdate ? 'Edit Tech Stack' : 'Add New Tech Stack'}
198204
</h2>
199-
200-
{error && (
201-
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-6">
202-
<p className="text-red-800">{error.message || 'An error occurred'}</p>
203-
</div>
204-
)}
205+
<fieldset>
206+
<ErrorSummary except={['name', 'slug', 'vendorName', 'description', 'appUrl', 'technologyIds']} status={error} className="mb-4" />
205207

206208
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
207209
<div className="space-y-4">
@@ -262,16 +264,18 @@ export function TechStackForm({ slug, onDone }: TechStackFormProps) {
262264
</div>
263265
</div>
264266

265-
<TextareaInput
266-
id="description"
267-
label="Description"
268-
value={formData.description}
269-
onChange={handleChange('description')}
270-
required
271-
rows={4}
272-
maxLength={500}
273-
help={`${formData.description.length}/500`}
274-
/>
267+
<div className="my-4">
268+
<TextareaInput
269+
id="description"
270+
label="Description"
271+
value={formData.description}
272+
onChange={handleChange('description')}
273+
required
274+
rows={4}
275+
maxLength={500}
276+
help={`${formData.description.length}/500`}
277+
/>
278+
</div>
275279

276280
<MarkdownInput
277281
id="details"
@@ -300,12 +304,15 @@ export function TechStackForm({ slug, onDone }: TechStackFormProps) {
300304
/>
301305
</div>
302306

303-
<CheckboxInput
304-
id="isLocked"
305-
label="Prevent others from editing this Tech Stack?"
306-
value={formData.isLocked as any}
307-
onChange={handleChange('isLocked')}
308-
/>
307+
<div className="border-t border-gray-200 dark:border-gray-700 pt-6 mt-6">
308+
<CheckboxInput
309+
id="isLocked"
310+
label="Prevent others from editing this Tech Stack?"
311+
value={formData.isLocked as any}
312+
onChange={handleChange('isLocked')}
313+
/>
314+
</div>
315+
</fieldset>
309316
</div>
310317

311318
<div className="bg-gray-50 dark:bg-gray-800 px-4 py-3 text-right sm:px-6">
@@ -317,16 +324,18 @@ export function TechStackForm({ slug, onDone }: TechStackFormProps) {
317324
</ConfirmDelete>
318325
)}
319326
</div>
320-
<div>
327+
<div className="flex gap-4">
328+
{onDone && <SecondaryButton onClick={onDone}>Cancel</SecondaryButton>}
321329
<PrimaryButton type="submit" disabled={loading}>
322-
{loading ? 'Saving...' : isUpdate ? 'Update' : 'Create'}
330+
{loading ? (isUpdate ? 'Updating...' : 'Creating...') : (isUpdate ? 'Update Tech Stack' : 'Create Tech Stack')}
323331
</PrimaryButton>
324332
</div>
325333
</div>
326334
</div>
327335

328336
</div>
329337
</form>
338+
</ApiStateContext.Provider>
330339
);
331340
}
332341

0 commit comments

Comments
 (0)