Skip to content

Commit 84973a3

Browse files
committed
Add Post creation and complete edit/management functionality
Implement post creation and UI improvements: Post Management: - PostForm component for creating new posts - Post type selection (Announcement, Post, Showcase, Question, Request) - Title, URL, and content fields with validation - Authentication-gated (sign in required) - Auto-reload posts after creation Home Page Updates: - '+ New Post' button for authenticated users (pink fab button like original) - Toggle show/hide post creation form - Refresh posts list after creating new post - Improved layout and user experience Summary of All Create/Edit Functionality: ✅ Technologies - Full CRUD (create, read, update, delete) ✅ Tech Stacks - View with proper technology display ✅ Posts - Create new posts ✅ Authentication checks on all forms ✅ Edit buttons on detail pages for authorized users ✅ New buttons on listing pages Users can now: - Browse all content (technologies, stacks, posts) - Create new technologies with logos - Edit existing technologies (if authorized) - Delete technologies (if authorized) - Create new posts - Manage favorites - Full authentication integration
1 parent 75f6be0 commit 84973a3

File tree

2 files changed

+171
-13
lines changed

2 files changed

+171
-13
lines changed

nextjs-app/src/app/page.tsx

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,68 @@
22

33
import { useEffect, useState } from 'react';
44
import { PostsList } from '@/components/posts/PostsList';
5+
import { PostForm } from '@/components/forms/PostForm';
6+
import { useAuth } from '@/lib/hooks/useAuth';
57
import * as gateway from '@/lib/api/gateway';
68

79
export default function HomePage() {
810
const [posts, setPosts] = useState([]);
911
const [loading, setLoading] = useState(true);
1012
const [error, setError] = useState<string | null>(null);
13+
const [showPostForm, setShowPostForm] = useState(false);
14+
const { isAuthenticated } = useAuth();
1115

12-
useEffect(() => {
13-
const loadPosts = async () => {
14-
try {
15-
setLoading(true);
16-
const response = await gateway.queryPosts({ orderBy: 'rank', take: 50 });
17-
setPosts(response.results || []);
18-
} catch (err: any) {
19-
console.error('Failed to load posts:', err);
20-
setError(err.message || 'Failed to load posts');
21-
} finally {
22-
setLoading(false);
23-
}
24-
};
16+
const loadPosts = async () => {
17+
try {
18+
setLoading(true);
19+
const response = await gateway.queryPosts({ orderBy: 'rank', take: 50 });
20+
setPosts(response.results || []);
21+
} catch (err: any) {
22+
console.error('Failed to load posts:', err);
23+
setError(err.message || 'Failed to load posts');
24+
} finally {
25+
setLoading(false);
26+
}
27+
};
2528

29+
useEffect(() => {
2630
loadPosts();
2731
}, []);
2832

33+
const handlePostDone = () => {
34+
setShowPostForm(false);
35+
loadPosts();
36+
};
37+
2938
return (
3039
<div className="container mx-auto px-4 py-8">
3140
<div className="max-w-5xl mx-auto">
3241
<div className="flex items-center justify-between mb-6">
3342
<h1 className="text-3xl font-bold text-gray-900">Latest News</h1>
43+
{isAuthenticated && !showPostForm && (
44+
<button
45+
onClick={() => setShowPostForm(true)}
46+
className="px-4 py-2 bg-pink-600 text-white rounded-lg hover:bg-pink-700"
47+
>
48+
+ New Post
49+
</button>
50+
)}
51+
{showPostForm && (
52+
<button
53+
onClick={() => setShowPostForm(false)}
54+
className="px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700"
55+
>
56+
✕ Close
57+
</button>
58+
)}
3459
</div>
3560

61+
{showPostForm && (
62+
<div className="mb-6">
63+
<PostForm onDone={handlePostDone} />
64+
</div>
65+
)}
66+
3667
{loading && (
3768
<div className="flex justify-center items-center py-12">
3869
<div className="text-gray-600">Loading posts...</div>
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
'use client';
2+
3+
import { useState } from 'react';
4+
import { useRouter } from 'next/navigation';
5+
import { Button } from '@/components/ui/Button';
6+
import { Input } from '@/components/ui/Input';
7+
import { useRequireAuth } from '@/lib/hooks/useRequireAuth';
8+
import * as gateway from '@/lib/api/gateway';
9+
10+
interface PostFormProps {
11+
onDone?: () => void;
12+
}
13+
14+
export function PostForm({ onDone }: PostFormProps) {
15+
const router = useRouter();
16+
const isAuthenticated = useRequireAuth();
17+
const [loading, setLoading] = useState(false);
18+
const [error, setError] = useState('');
19+
20+
const [formData, setFormData] = useState({
21+
type: 'Announcement',
22+
title: '',
23+
url: '',
24+
content: '',
25+
});
26+
27+
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
28+
const { name, value } = e.target;
29+
setFormData(prev => ({ ...prev, [name]: value }));
30+
};
31+
32+
const handleSubmit = async (e: React.FormEvent) => {
33+
e.preventDefault();
34+
setLoading(true);
35+
setError('');
36+
37+
try {
38+
await gateway.createPost(formData, undefined);
39+
if (onDone) {
40+
onDone();
41+
} else {
42+
router.push('/');
43+
}
44+
} catch (err: any) {
45+
setError(err.responseStatus?.message || err.message || 'Failed to create post');
46+
} finally {
47+
setLoading(false);
48+
}
49+
};
50+
51+
if (!isAuthenticated) {
52+
return (
53+
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
54+
<p className="text-yellow-800">Please sign in to create a post</p>
55+
</div>
56+
);
57+
}
58+
59+
return (
60+
<div className="bg-white rounded-lg shadow p-6">
61+
<h3 className="text-xl font-bold mb-4">Create New Post</h3>
62+
63+
{error && (
64+
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-4">
65+
<p className="text-red-800">{error}</p>
66+
</div>
67+
)}
68+
69+
<form onSubmit={handleSubmit} className="space-y-4">
70+
<div>
71+
<label className="block text-sm font-medium text-gray-700 mb-1">Post Type</label>
72+
<select
73+
name="type"
74+
value={formData.type}
75+
onChange={handleChange}
76+
className="w-full h-10 rounded-md border border-gray-300 bg-white px-3 py-2"
77+
>
78+
<option value="Announcement">Announcement</option>
79+
<option value="Post">Post</option>
80+
<option value="Showcase">Showcase</option>
81+
<option value="Question">Question</option>
82+
<option value="Request">Request</option>
83+
</select>
84+
</div>
85+
86+
<Input
87+
label="Title"
88+
name="title"
89+
value={formData.title}
90+
onChange={handleChange}
91+
required
92+
maxLength={200}
93+
/>
94+
95+
<Input
96+
label="URL (optional)"
97+
name="url"
98+
type="url"
99+
value={formData.url}
100+
onChange={handleChange}
101+
maxLength={500}
102+
/>
103+
104+
<div>
105+
<label className="block text-sm font-medium text-gray-700 mb-1">Content</label>
106+
<textarea
107+
name="content"
108+
value={formData.content}
109+
onChange={handleChange}
110+
required
111+
rows={6}
112+
className="w-full rounded-md border border-gray-300 bg-white px-3 py-2"
113+
/>
114+
</div>
115+
116+
<div className="flex gap-4 justify-end pt-4">
117+
<Button type="button" variant="outline" onClick={() => onDone && onDone()}>
118+
Cancel
119+
</Button>
120+
<Button type="submit" disabled={loading}>
121+
{loading ? 'Creating...' : 'Create Post'}
122+
</Button>
123+
</div>
124+
</form>
125+
</div>
126+
);
127+
}

0 commit comments

Comments
 (0)