Post deletion, profile link
This commit is contained in:
@@ -59,6 +59,7 @@ export default function ProfilePage() {
|
||||
bio: '',
|
||||
avatarUrl: '',
|
||||
headerUrl: '',
|
||||
website: '',
|
||||
});
|
||||
const [saveError, setSaveError] = useState<string | null>(null);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
@@ -112,6 +113,7 @@ export default function ProfilePage() {
|
||||
bio: user.bio || '',
|
||||
avatarUrl: user.avatarUrl || '',
|
||||
headerUrl: user.headerUrl || '',
|
||||
website: user.website || '',
|
||||
});
|
||||
}
|
||||
}, [user, currentUser]);
|
||||
@@ -353,6 +355,26 @@ export default function ProfilePage() {
|
||||
<span>Joined {formatDate(user.createdAt || new Date().toISOString())}</span>
|
||||
</div>
|
||||
|
||||
{user.website && (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
marginTop: '4px',
|
||||
color: 'var(--accent)',
|
||||
fontSize: '14px',
|
||||
}}>
|
||||
<Link
|
||||
href={user.website.startsWith('http') ? user.website : `https://${user.website}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ color: 'inherit', textDecoration: 'none' }}
|
||||
>
|
||||
{user.website.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, '')}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ display: 'flex', gap: '16px', marginTop: '12px' }}>
|
||||
<button
|
||||
onClick={() => setActiveTab('following')}
|
||||
@@ -406,6 +428,16 @@ export default function ProfilePage() {
|
||||
style={{ minHeight: '80px', resize: 'vertical' }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label style={{ fontSize: '12px', color: 'var(--foreground-tertiary)' }}>Website</label>
|
||||
<input
|
||||
className="input"
|
||||
placeholder="https://example.com"
|
||||
value={profileForm.website}
|
||||
onChange={(e) => setProfileForm({ ...profileForm, website: e.target.value })}
|
||||
maxLength={100}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label style={{ fontSize: '12px', color: 'var(--foreground-tertiary)' }}>Avatar</label>
|
||||
<div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
|
||||
|
||||
@@ -9,6 +9,7 @@ const updateProfileSchema = z.object({
|
||||
bio: z.string().max(160).optional().nullable(),
|
||||
avatarUrl: z.string().url().optional().nullable(),
|
||||
headerUrl: z.string().url().optional().nullable(),
|
||||
website: z.string().url().or(z.string().length(0)).optional().nullable(),
|
||||
});
|
||||
|
||||
export async function GET() {
|
||||
@@ -31,6 +32,7 @@ export async function GET() {
|
||||
displayName: session.user.displayName,
|
||||
avatarUrl: session.user.avatarUrl,
|
||||
bio: session.user.bio,
|
||||
website: session.user.website,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -54,6 +56,7 @@ export async function PATCH(request: Request) {
|
||||
bio?: string | null;
|
||||
avatarUrl?: string | null;
|
||||
headerUrl?: string | null;
|
||||
website?: string | null;
|
||||
updatedAt?: Date;
|
||||
} = {};
|
||||
|
||||
@@ -61,6 +64,7 @@ export async function PATCH(request: Request) {
|
||||
if (data.bio !== undefined) updateData.bio = data.bio === '' ? null : data.bio;
|
||||
if (data.avatarUrl !== undefined) updateData.avatarUrl = data.avatarUrl === '' ? null : data.avatarUrl;
|
||||
if (data.headerUrl !== undefined) updateData.headerUrl = data.headerUrl === '' ? null : data.headerUrl;
|
||||
if (data.website !== undefined) updateData.website = data.website === '' ? null : data.website;
|
||||
|
||||
if (Object.keys(updateData).length === 0) {
|
||||
return NextResponse.json({
|
||||
@@ -71,6 +75,7 @@ export async function PATCH(request: Request) {
|
||||
avatarUrl: currentUser.avatarUrl,
|
||||
bio: currentUser.bio,
|
||||
headerUrl: currentUser.headerUrl,
|
||||
website: currentUser.website,
|
||||
followersCount: currentUser.followersCount,
|
||||
followingCount: currentUser.followingCount,
|
||||
postsCount: currentUser.postsCount,
|
||||
@@ -94,6 +99,7 @@ export async function PATCH(request: Request) {
|
||||
avatarUrl: updatedUser.avatarUrl,
|
||||
bio: updatedUser.bio,
|
||||
headerUrl: updatedUser.headerUrl,
|
||||
website: updatedUser.website,
|
||||
followersCount: updatedUser.followersCount,
|
||||
followingCount: updatedUser.followingCount,
|
||||
postsCount: updatedUser.postsCount,
|
||||
|
||||
@@ -94,3 +94,59 @@ export async function GET(
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const { requireAuth } = await import('@/lib/auth');
|
||||
const user = await requireAuth();
|
||||
const { id } = await params;
|
||||
|
||||
const post = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, id),
|
||||
});
|
||||
|
||||
if (!post) {
|
||||
return NextResponse.json({ error: 'Post not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
if (post.userId !== user.id) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 403 });
|
||||
}
|
||||
|
||||
// 1. If it's a reply, decrement parent's repliesCount
|
||||
if (post.replyToId) {
|
||||
const parentPost = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, post.replyToId),
|
||||
});
|
||||
if (parentPost && parentPost.repliesCount > 0) {
|
||||
await db.update(posts)
|
||||
.set({ repliesCount: parentPost.repliesCount - 1 })
|
||||
.where(eq(posts.id, post.replyToId));
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Delete the post (cascades to media, likes, notifications)
|
||||
await db.delete(posts).where(eq(posts.id, id));
|
||||
|
||||
// 3. Decrement user's postsCount
|
||||
if (user.postsCount > 0) {
|
||||
await db.update(users)
|
||||
.set({ postsCount: user.postsCount - 1 })
|
||||
.where(eq(users.id, user.id));
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Delete post error:', error);
|
||||
if (error instanceof Error && error.message === 'Authentication required') {
|
||||
return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
|
||||
}
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to delete post' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,6 +373,10 @@ a.btn-primary:visited {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.post-action.delete-action:hover {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
/* User rows (followers/following) */
|
||||
.user-row {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user