Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/Http/Controllers/ForumController.php
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ public function store(Request $request): RedirectResponse

$validated = $request->validate([
'title' => ['required', 'string', 'min:5', 'max:255', new CleanText],
'body' => ['required', 'string', 'min:10', 'max:30000', new CleanText],
'body' => ['nullable', 'string', 'max:30000', new CleanText],
'curriculum' => ['required', 'in:hsc,ssc'],
'subject_id' => ['nullable', 'exists:subjects,id'],
'node_id' => ['nullable', 'exists:nodes,id'],
Expand Down Expand Up @@ -251,7 +251,7 @@ public function store(Request $request): RedirectResponse
'node_id' => $nodeId,
'curriculum' => $validated['curriculum'],
'title' => $validated['title'],
'body' => $validated['body'],
'body' => $validated['body'] ?? null,
'image_path' => $imagePath,
'is_locked' => false,
'moderation_status' => $moderationStatus,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('forum_posts', function (Blueprint $table) {
$table->text('body')->nullable()->change();
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('forum_posts', function (Blueprint $table) {
$table->text('body')->nullable(false)->change();
});
}
};
10 changes: 6 additions & 4 deletions resources/js/pages/Forum/Create.vue
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ const removeImage = () => {

const handleOpenModal = () => {
// Basic frontend checks before modal
if (!form.title.trim() || !form.body.trim()) {
if (!form.title.trim()) {
form.post('/forum'); // trigger inertia validation errors if fields are empty

return;
Expand Down Expand Up @@ -335,14 +335,16 @@ const submit = () => {
for="body"
class="mb-2 block text-xs font-bold tracking-wider text-slate-700 uppercase dark:text-gray-300"
>
Question Details <span class="text-rose-500">*</span>
Question Details
<span class="text-xs font-normal text-slate-400"
>(Optional)</span
>
</label>
<textarea
id="body"
v-model="form.body"
required
rows="6"
placeholder="আপনার সমস্যাটি বিস্তারিতভাবে লিখুন যাতে অন্যরা সহজেই বুঝতে পারে..."
placeholder="আপনার সমস্যাটি বিস্তারিতভাবে লিখুন (প্রযোজ্য ক্ষেত্রে)..."
class="w-full rounded-xl border border-slate-200 bg-white p-3.5 text-sm text-slate-900 shadow-2xs transition placeholder:text-slate-400 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none dark:border-gray-800 dark:bg-gray-900 dark:text-gray-100 dark:placeholder:text-gray-500"
></textarea>
<p
Expand Down
3 changes: 2 additions & 1 deletion resources/js/pages/Forum/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ interface ForumPost {
curriculum: 'hsc' | 'ssc';
title: string;
slug: string;
body: string;
body?: string | null;
image_path?: string | null;
image_url?: string | null;
is_answered: boolean;
Expand Down Expand Up @@ -720,6 +720,7 @@ const timeAgo = formatTimeAgo;

<!-- Preview snippet -->
<p
v-if="post.body"
class="mt-1 line-clamp-2 text-xs leading-relaxed text-slate-600 sm:text-sm dark:text-gray-300"
>
{{ post.body }}
Expand Down
18 changes: 11 additions & 7 deletions resources/js/pages/Forum/Show.vue
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ interface ForumPost {
curriculum: 'hsc' | 'ssc';
title: string;
slug: string;
body: string;
body?: string | null;
image_path?: string | null;
image_url?: string | null;
is_answered: boolean;
Expand Down Expand Up @@ -288,21 +288,21 @@ const openReportModal = (
id: number,
title: string | undefined,
authorName: string | undefined,
contentPreview: string,
contentPreview?: string | null,
) => {
if (!requireAuth('Please sign in to report this content.')) {
return;
}

const preview = contentPreview || '';

reportingTarget.value = {
type,
id,
title,
authorName: authorName || 'Anonymous',
contentPreview:
contentPreview.length > 200
? contentPreview.slice(0, 200) + '...'
: contentPreview,
preview.length > 200 ? preview.slice(0, 200) + '...' : preview,
};
reportReason.value = 'Inappropriate content or conduct';
reportSuccessMessage.value = null;
Expand Down Expand Up @@ -581,7 +581,10 @@ function parseMentions(
<template>
<Head>
<title>{{ post.title }} — HSCStack Forum</title>
<meta name="description" :content="post.body.slice(0, 160)" />
<meta
name="description"
:content="post.body ? post.body.slice(0, 160) : post.title"
/>
</Head>

<main class="mx-auto max-w-4xl px-3.5 py-3.5 sm:px-6 sm:py-8">
Expand Down Expand Up @@ -794,6 +797,7 @@ function parseMentions(

<!-- Question Body -->
<div
v-if="post.body"
class="prose dark:prose-invert mt-4 max-w-none text-sm leading-relaxed whitespace-pre-wrap text-slate-800 sm:text-base dark:text-gray-200"
>
{{ post.body }}
Expand Down Expand Up @@ -982,7 +986,7 @@ function parseMentions(
post.id,
post.title,
post.user?.name,
post.body,
post.body || post.title,
)
"
class="inline-flex cursor-pointer items-center gap-1.5 rounded-xl border border-slate-200/80 bg-white px-3 py-1.5 text-xs font-semibold text-slate-600 shadow-2xs transition hover:border-amber-300 hover:bg-amber-50 hover:text-amber-700 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-400 dark:hover:border-amber-900/60 dark:hover:bg-amber-950/40 dark:hover:text-amber-300"
Expand Down
22 changes: 22 additions & 0 deletions tests/Feature/ForumTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,28 @@
$response->assertRedirect("/forum/questions/{$post->slug}");
});

test('authenticated user can create a question without details', function () {
$user = User::factory()->create();

$response = $this->actingAs($user)->post('/forum', [
'title' => 'Self-contained title question without any details?',
'curriculum' => 'hsc',
]);

$post = ForumPost::where('title', 'Self-contained title question without any details?')->first();
expect($post)->not->toBeNull()
->and($post->body)->toBeNull();

$response->assertRedirect("/forum/questions/{$post->slug}");

$showResponse = $this->get("/forum/questions/{$post->slug}");
$showResponse->assertOk()
->assertInertia(fn ($page) => $page
->component('Forum/Show')
->where('post.body', null)
);
});

test('user can post answer and reply with 1-level nesting enforced', function () {
$author = User::factory()->create();
$replier = User::factory()->create();
Expand Down