diff --git a/app/Http/Controllers/Crew/FeedbackController.php b/app/Http/Controllers/Crew/FeedbackController.php new file mode 100644 index 00000000..d08b51db --- /dev/null +++ b/app/Http/Controllers/Crew/FeedbackController.php @@ -0,0 +1,41 @@ +cannot('viewAny', Feedback::class)) { + abort(403); + } + + $feedbackReports = Feedback::all(); + + return view('crew.feedback.index', compact('feedbackReports')); + } + + /** + * Display the specified resource. + * @param Feedback $feedback + * @return View + */ + public function show(Feedback $feedback) : View + { + if (Auth::user()->cannot('view', $feedback)) { + abort(403); + } + + return view('crew.feedback.show', compact('feedback')); + } +} diff --git a/app/Http/Controllers/Hub/ParticipantController.php b/app/Http/Controllers/Hub/ParticipantController.php index b5a79ba8..57ce8472 100644 --- a/app/Http/Controllers/Hub/ParticipantController.php +++ b/app/Http/Controllers/Hub/ParticipantController.php @@ -3,6 +3,8 @@ namespace App\Http\Controllers\Hub; use App\Http\Controllers\Controller; +use App\Models\Feedback; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\View\View; @@ -19,4 +21,34 @@ public function programme(): View return view('myhub.programme', compact('presentations')); } + + /** + * Show the form for creating a new resource. + */ + public function createFeedback() + { + if (Auth::user()->cannot('create', Feedback::class)) { + abort(403); + } + + return view('myhub.feedback'); + } + + /** + * Store a newly created resource in storage. + */ + public function storeFeedback(Request $request) + { + if (Auth::user()->cannot('create', Feedback::class)) { + abort(403); + } + + $validated = $request->validate(Feedback::$rules); + Feedback::create(array_merge( + $validated, + ['reported_by_id' => Auth::user()->id] + )); + + return redirect(route('dashboard'))->banner('You successfully submitted your feedback!'); + } } diff --git a/app/Livewire/Crew/AddTeam.php b/app/Livewire/Crew/AddTeam.php new file mode 100644 index 00000000..e7922108 --- /dev/null +++ b/app/Livewire/Crew/AddTeam.php @@ -0,0 +1,63 @@ +user = $user; + $this->crew_team = $user->crew_team; + } + + /** + * Saves the tag for the user + */ + public function save() + { + $validated = $this->validate([ + 'crew_team' => 'required|in:organization,website' + ]); + + $this->user->update([ + 'crew_team' => $this->crew_team + ]); + + return redirect()->to(route('moderator.crew.index')); + } + + /** + * Removes the set team of the crew user + * + * @return \Illuminate\Http\RedirectResponse + */ + public function removeTeam() + { + $this->user->update([ + 'crew_team' => null + ]); + + return redirect()->to(route('moderator.crew.index')); + } + + /** + * Renders the component + * @return View + */ + public function render(): View + { + return view('livewire.crew.add-team'); + } +} diff --git a/app/Mail/FeedbackReceivedMailable.php b/app/Mail/FeedbackReceivedMailable.php new file mode 100644 index 00000000..48842a04 --- /dev/null +++ b/app/Mail/FeedbackReceivedMailable.php @@ -0,0 +1,54 @@ +feedback->type}", + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + markdown: 'emails.feedback-received', + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Models/Feedback.php b/app/Models/Feedback.php new file mode 100644 index 00000000..8eeacecf --- /dev/null +++ b/app/Models/Feedback.php @@ -0,0 +1,32 @@ + 'required|in:organization,website', + 'title' => 'required|max:255|string', + 'content' => 'required|max:1500|string', + ]; + + /** + * Establishes connection to the user who gave the feedback + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function reportedBy() + { + return $this->belongsTo(User::class, 'reported_by_id'); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index ac185fd8..2643fa56 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -38,7 +38,8 @@ class User extends Authenticatable implements MustVerifyEmail 'email', 'password', 'company_id', - 'institution' + 'institution', + 'crew_team' ]; /** @@ -85,6 +86,15 @@ public function company(): BelongsTo return $this->belongsTo(Company::class); } + /** + * Establishes the relationship between the user and the feedback given by them + * @return HasMany + */ + public function feedback() + { + return $this->hasMany(Feedback::class); + } + /** * Hides a many-to-many relationship with presentations * and implements relationship with linking table UserPresentation @@ -321,7 +331,7 @@ public function mainRoles() * @param Builder $query * @return void */ - public function scopeSendEmailPreference(Builder $query) : void + public function scopeSendEmailPreference(Builder $query): void { $query->where('receive_emails', '=', 1); } diff --git a/app/Observers/FeedbackObserver.php b/app/Observers/FeedbackObserver.php new file mode 100644 index 00000000..33a84075 --- /dev/null +++ b/app/Observers/FeedbackObserver.php @@ -0,0 +1,22 @@ +type)->get(); + foreach ($crew as $user) { + Mail::to($user->email)->send(new FeedbackReceivedMailable($feedback)); + } + } +} diff --git a/app/Policies/FeedbackPolicy.php b/app/Policies/FeedbackPolicy.php new file mode 100644 index 00000000..f37ae8d8 --- /dev/null +++ b/app/Policies/FeedbackPolicy.php @@ -0,0 +1,41 @@ +is_crew; + } + + /** + * Determines whether the user can view any feedback + * @param User $user + * @return bool + */ + public function viewAny(User $user) + { + return $user->can('viewAny feedback'); + } + + /** + * Determines whether the user can view specific feedback + * @param User $user + * @param Feedback $feedback + * @return bool + */ + public function view(User $user, Feedback $feedback) + { + return $user->can('viewAny feedback'); + } +} diff --git a/database/migrations/2024_10_16_141617_add_tag_to_users_for_crew.php b/database/migrations/2024_10_16_141617_add_tag_to_users_for_crew.php new file mode 100644 index 00000000..403b4d60 --- /dev/null +++ b/database/migrations/2024_10_16_141617_add_tag_to_users_for_crew.php @@ -0,0 +1,28 @@ +enum('crew_team', ['organization', 'website'])->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('crew_team'); + }); + } +}; diff --git a/database/migrations/2024_10_19_160643_create_feedback_table.php b/database/migrations/2024_10_19_160643_create_feedback_table.php new file mode 100644 index 00000000..f36fd24d --- /dev/null +++ b/database/migrations/2024_10_19_160643_create_feedback_table.php @@ -0,0 +1,36 @@ +id(); + $table->enum('type', ['website', 'organization']); + $table->string('title'); + $table->text('content'); + $table->unsignedBigInteger('reported_by_id')->nullable(); + $table->boolean('is_archived')->default(false); + + $table->foreign('reported_by_id')->on('users')->references('id') + ->cascadeOnUpdate()->nullOnDelete(); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('feedback'); + } +}; diff --git a/resources/views/components/dashboards/blocks/participant-notification.blade.php b/resources/views/components/dashboards/blocks/participant-notification.blade.php new file mode 100644 index 00000000..c766ea74 --- /dev/null +++ b/resources/views/components/dashboards/blocks/participant-notification.blade.php @@ -0,0 +1,4 @@ +
+ {{ $slot }} +
diff --git a/resources/views/components/dashboards/company.blade.php b/resources/views/components/dashboards/company.blade.php index 9c398d8e..238d7d5f 100644 --- a/resources/views/components/dashboards/company.blade.php +++ b/resources/views/components/dashboards/company.blade.php @@ -53,7 +53,7 @@ class="text-partner-500"> && !Auth::user()->speaker) @endif--}} -
+
@endcan + @can('viewAny', \App\Models\Feedback::class) + + @endcan diff --git a/resources/views/crew/crew/index.blade.php b/resources/views/crew/crew/index.blade.php index 6c43b54c..c53e882b 100644 --- a/resources/views/crew/crew/index.blade.php +++ b/resources/views/crew/crew/index.blade.php @@ -21,7 +21,7 @@ class="ml-2 text-gray-400 hover:text-gray-500 relative group"> -
+
@forelse ($role->users as $user)
@@ -34,6 +34,10 @@ class="relative bg-white dark:bg-gray-800 shadow rounded-lg p-2 flex items-cente class="text-md font-medium text-gray-900 dark:text-gray-100">{{ $user->name }}
{{ $user->email }}
+ @if($user->crew_team) +
{{ 'Team: ' . ucfirst($user->crew_team) }}
+ @endif
@can('remove-crew-member') + @if($role->name == 'event organizer') + + @endif @endcan
@empty diff --git a/resources/views/crew/feedback/index.blade.php b/resources/views/crew/feedback/index.blade.php new file mode 100644 index 00000000..c220f2d1 --- /dev/null +++ b/resources/views/crew/feedback/index.blade.php @@ -0,0 +1,70 @@ + +
+

+ {{ __('Received feedback') }} +

+
+ + + + + + + + + + + + @forelse($feedbackReports as $index => $feedback) + + + + + + @empty + + + + @endforelse + +
+ Title + + Type + + Created At +
+
+
+
+ + + +
+ {{$feedback->title}} +
+
+
+
+
+ {{ ucfirst($feedback->type) }} + +
+ + {{ $feedback->created_at->format('d/m/Y') }} +
+
+ There is currently no feedback submitted. +
+
+
+
+
+
diff --git a/resources/views/crew/feedback/show.blade.php b/resources/views/crew/feedback/show.blade.php new file mode 100644 index 00000000..7b356a4f --- /dev/null +++ b/resources/views/crew/feedback/show.blade.php @@ -0,0 +1,32 @@ + +
+

+ {{ __('Feedback Details') }} +

+
+ + + {{ __('Feedback Information') }} + + + + {{ __('The details regarding the feedback received.') }} + + + + + {{ $feedback->title }} + + + {{ optional($feedback->reportedBy)->name }} + + + + + + + +
+ +
+
diff --git a/resources/views/emails/feedback-received.blade.php b/resources/views/emails/feedback-received.blade.php new file mode 100644 index 00000000..38006e63 --- /dev/null +++ b/resources/views/emails/feedback-received.blade.php @@ -0,0 +1,18 @@ +@component('mail::message') +# Feedback Received + +You have received new feedback related to team {{$feedback->type}}. + +The title given is {{$feedback->title}}. + +@if($feedback->reportedBy) +It has been reported by: {{$feedback->reportedBy->name}} +@endif + +@component('mail::button', ['url' => route('moderator.feedback.show', $feedback)]) +View Feedback +@endcomponent + +Thanks,
+{{ config('app.name') }} +@endcomponent diff --git a/resources/views/layouts/hub.blade.php b/resources/views/layouts/hub.blade.php index 45d27d04..a952d40f 100644 --- a/resources/views/layouts/hub.blade.php +++ b/resources/views/layouts/hub.blade.php @@ -65,6 +65,14 @@ class="pb-4 px-6 border-r border-b border-t dark:border-gray-800 overflow-y-auto
  • Profile
      + @can('create', \App\Models\Feedback::class) + + @endcan + + Assign crew team to {{$user->name}} + + + + {{ __('Assign team to the crew member. This way they will receive email notifications only for feedback related to their team.') }} + + + +
      + + + @error('crew_team') {{ $message }} @enderror +
      +
      + + @if($user->crew_team) + + @endif + + {{ __('Cancel') }} + + + + diff --git a/resources/views/myhub/feedback.blade.php b/resources/views/myhub/feedback.blade.php new file mode 100644 index 00000000..9e3f29ea --- /dev/null +++ b/resources/views/myhub/feedback.blade.php @@ -0,0 +1,42 @@ +@php use App\Models\Difficulty; @endphp + +
      +
      +

      + Give feedback +

      +

      + As a team, we are always striving to improve! If you have any suggestions or have encountered issues with the website or the organization, please let us know! Or if everything is perfect, we also would like to hear from you! +

      +
      +
      + @csrf +
      +
      + + + +
      +
      + + + + + + +
      +
      + + + +
      +
      + + Submit + +
      +
      +
      +
      +
      diff --git a/resources/views/myhub/home.blade.php b/resources/views/myhub/home.blade.php index 7569c5e0..6afcbe8b 100644 --- a/resources/views/myhub/home.blade.php +++ b/resources/views/myhub/home.blade.php @@ -15,6 +15,18 @@ @endif @if(Auth::user()->is_crew) + @else +
      + +

      New! 🌟 Submit Your Feedback

      +

      We are eager to hear from you! Run into any issues, have ideas, or just want to tell us + what's working well? We're all ears. Hit the button below to let us know what you think!

      + + Go to Feedback Form + +
      +
      @endif
  • diff --git a/routes/web.php b/routes/web.php index 0d6efca5..cafd98e8 100644 --- a/routes/web.php +++ b/routes/web.php @@ -72,6 +72,10 @@ ])->group(function () { Route::prefix('/my')->group(function () { Route::view('/', 'myhub.home')->name('dashboard'); + Route::get('/feedback', [ParticipantController::class, 'createFeedback']) + ->name('feedback.create'); + Route::post('/feedback', [ParticipantController::class, 'storeFeedback']) + ->name('feedback.store'); Route::get('/company/details', [\App\Http\Controllers\Hub\CompanyController::class, 'details']) ->name('company.details'); Route::get('/company/requests', [\App\Http\Controllers\Hub\CompanyController::class, 'requests']) @@ -195,6 +199,11 @@ App\Http\Controllers\Crew\BoothController::class, 'approve' ])->name('booths.approve'); + Route::get('/moderator/feedback', [\App\Http\Controllers\Crew\FeedbackController::class, 'index']) + ->name('feedback.index'); + Route::get('/moderator/feedback/{feedback}', [\App\Http\Controllers\Crew\FeedbackController::class, 'show']) + ->name('feedback.show'); + Route::resource( '/moderator/companies', App\Http\Controllers\Crew\CompanyController::class diff --git a/storage/app/config/permissions.yml b/storage/app/config/permissions.yml index 8b0e5eef..89716cf5 100644 --- a/storage/app/config/permissions.yml +++ b/storage/app/config/permissions.yml @@ -101,3 +101,6 @@ permissions: view: [ event organizer, assistant organizer ] create: [ event organizer, assistant organizer ] delete: [ event organizer, assistant organizer ] + feedback: + viewAny: event organizer + view: event organizer