Browse Source

Add Curated Onboarding Templates

Daniel Supernault 1 year ago
parent
commit
071163b47b

+ 136 - 47
app/Http/Controllers/AdminCuratedRegisterController.php

@@ -2,69 +2,72 @@
 
 namespace App\Http\Controllers;
 
-use Illuminate\Http\Request;
-use App\Models\CuratedRegister;
-use App\Models\CuratedRegisterActivity;
-use Illuminate\Support\Str;
-use Illuminate\Support\Facades\Mail;
-use App\Mail\CuratedRegisterRequestDetailsFromUser;
 use App\Mail\CuratedRegisterAcceptUser;
 use App\Mail\CuratedRegisterRejectUser;
+use App\Mail\CuratedRegisterRequestDetailsFromUser;
+use App\Models\CuratedRegister;
+use App\Models\CuratedRegisterActivity;
+use App\Models\CuratedRegisterTemplate;
 use App\User;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Mail;
+use Illuminate\Support\Str;
 
 class AdminCuratedRegisterController extends Controller
 {
     public function __construct()
     {
-        $this->middleware(['auth','admin']);
+        $this->middleware(['auth', 'admin']);
     }
 
     public function index(Request $request)
     {
         $this->validate($request, [
             'filter' => 'sometimes|in:open,all,awaiting,approved,rejected,responses',
-            'sort' => 'sometimes|in:asc,desc'
+            'sort' => 'sometimes|in:asc,desc',
         ]);
         $filter = $request->input('filter', 'open');
         $sort = $request->input('sort', 'asc');
-        $records = CuratedRegister::when($filter, function($q, $filter) {
-                if($filter === 'open') {
-                    return $q->where('is_rejected', false)
-                    ->where(function($query) {
+        $records = CuratedRegister::when($filter, function ($q, $filter) {
+            if ($filter === 'open') {
+                return $q->where('is_rejected', false)
+                    ->where(function ($query) {
                         return $query->where('user_has_responded', true)->orWhere('is_awaiting_more_info', false);
                     })
                     ->whereNotNull('email_verified_at')
                     ->whereIsClosed(false);
-                } else if($filter === 'all') {
-                    return $q;
-                } else if($filter === 'responses') {
-                    return $q->whereIsClosed(false)
-                        ->whereNotNull('email_verified_at')
-                        ->where('user_has_responded', true)
-                        ->where('is_awaiting_more_info', true);
-                } elseif ($filter === 'awaiting') {
-                    return $q->whereIsClosed(false)
-                        ->where('is_rejected', false)
-                        ->where('is_approved', false)
-                        ->where('user_has_responded', false)
-                        ->where('is_awaiting_more_info', true);
-                } elseif ($filter === 'approved') {
-                    return $q->whereIsClosed(true)->whereIsApproved(true);
-                } elseif ($filter === 'rejected') {
-                    return $q->whereIsClosed(true)->whereIsRejected(true);
-                }
-            })
-            ->when($sort, function($query, $sort) {
+            } elseif ($filter === 'all') {
+                return $q;
+            } elseif ($filter === 'responses') {
+                return $q->whereIsClosed(false)
+                    ->whereNotNull('email_verified_at')
+                    ->where('user_has_responded', true)
+                    ->where('is_awaiting_more_info', true);
+            } elseif ($filter === 'awaiting') {
+                return $q->whereIsClosed(false)
+                    ->where('is_rejected', false)
+                    ->where('is_approved', false)
+                    ->where('user_has_responded', false)
+                    ->where('is_awaiting_more_info', true);
+            } elseif ($filter === 'approved') {
+                return $q->whereIsClosed(true)->whereIsApproved(true);
+            } elseif ($filter === 'rejected') {
+                return $q->whereIsClosed(true)->whereIsRejected(true);
+            }
+        })
+            ->when($sort, function ($query, $sort) {
                 return $query->orderBy('id', $sort);
             })
             ->paginate(10)
             ->withQueryString();
+
         return view('admin.curated-register.index', compact('records', 'filter'));
     }
 
     public function show(Request $request, $id)
     {
         $record = CuratedRegister::findOrFail($id);
+
         return view('admin.curated-register.show', compact('record'));
     }
 
@@ -80,10 +83,10 @@ class AdminCuratedRegisterController extends Controller
                 'message' => null,
                 'link' => null,
                 'timestamp' => $record->created_at,
-            ]
+            ],
         ]);
 
-        if($record->email_verified_at) {
+        if ($record->email_verified_at) {
             $res->push([
                 'id' => 3,
                 'action' => 'email_verified_at',
@@ -99,10 +102,11 @@ class AdminCuratedRegisterController extends Controller
         $idx = 4;
         $userResponses = collect([]);
 
-        foreach($activities as $activity) {
+        foreach ($activities as $activity) {
             $idx++;
-            if($activity->from_user) {
+            if ($activity->from_user) {
                 $userResponses->push($activity);
+
                 continue;
             }
             $res->push([
@@ -116,20 +120,22 @@ class AdminCuratedRegisterController extends Controller
             ]);
         }
 
-        foreach($userResponses as $ur) {
-            $res = $res->map(function($r) use($ur) {
-                if(!isset($r['aid'])) {
+        foreach ($userResponses as $ur) {
+            $res = $res->map(function ($r) use ($ur) {
+                if (! isset($r['aid'])) {
                     return $r;
                 }
-                if($ur->reply_to_id === $r['aid']) {
+                if ($ur->reply_to_id === $r['aid']) {
                     $r['user_response'] = $ur;
+
                     return $r;
                 }
+
                 return $r;
             });
         }
 
-        if($record->is_approved) {
+        if ($record->is_approved) {
             $idx++;
             $res->push([
                 'id' => $idx,
@@ -139,7 +145,7 @@ class AdminCuratedRegisterController extends Controller
                 'link' => null,
                 'timestamp' => $record->action_taken_at,
             ]);
-        } else if ($record->is_rejected) {
+        } elseif ($record->is_rejected) {
             $idx++;
             $res->push([
                 'id' => $idx,
@@ -157,13 +163,14 @@ class AdminCuratedRegisterController extends Controller
     public function apiMessagePreviewStore(Request $request, $id)
     {
         $record = CuratedRegister::findOrFail($id);
+
         return $request->all();
     }
 
     public function apiMessageSendStore(Request $request, $id)
     {
         $this->validate($request, [
-            'message' => 'required|string|min:5|max:1000'
+            'message' => 'required|string|min:5|max:1000',
         ]);
         $record = CuratedRegister::findOrFail($id);
         abort_if($record->email_verified_at === null, 400, 'Cannot message an unverified email');
@@ -179,6 +186,7 @@ class AdminCuratedRegisterController extends Controller
         $record->user_has_responded = false;
         $record->save();
         Mail::to($record->email)->send(new CuratedRegisterRequestDetailsFromUser($record, $activity));
+
         return $request->all();
     }
 
@@ -188,22 +196,23 @@ class AdminCuratedRegisterController extends Controller
         abort_if($record->email_verified_at === null, 400, 'Cannot message an unverified email');
         $activity = new CuratedRegisterActivity;
         $activity->message = $request->input('message');
+
         return new \App\Mail\CuratedRegisterRequestDetailsFromUser($record, $activity);
     }
 
-
     public function previewMessageShow(Request $request, $id)
     {
         $record = CuratedRegister::findOrFail($id);
         abort_if($record->email_verified_at === null, 400, 'Cannot message an unverified email');
         $record->message = $request->input('message');
+
         return new \App\Mail\CuratedRegisterSendMessage($record);
     }
 
     public function apiHandleReject(Request $request, $id)
     {
         $this->validate($request, [
-            'action' => 'required|in:reject-email,reject-silent'
+            'action' => 'required|in:reject-email,reject-silent',
         ]);
         $action = $request->input('action');
         $record = CuratedRegister::findOrFail($id);
@@ -212,9 +221,10 @@ class AdminCuratedRegisterController extends Controller
         $record->is_closed = true;
         $record->action_taken_at = now();
         $record->save();
-        if($action === 'reject-email') {
+        if ($action === 'reject-email') {
             Mail::to($record->email)->send(new CuratedRegisterRejectUser($record));
         }
+
         return [200];
     }
 
@@ -233,10 +243,89 @@ class AdminCuratedRegisterController extends Controller
             'password' => $record->password,
             'app_register_ip' => $record->ip_address,
             'email_verified_at' => now(),
-            'register_source' => 'cur_onboarding'
+            'register_source' => 'cur_onboarding',
         ]);
 
         Mail::to($record->email)->send(new CuratedRegisterAcceptUser($record));
+
         return [200];
     }
+
+    public function templates(Request $request)
+    {
+        $templates = CuratedRegisterTemplate::paginate(10);
+
+        return view('admin.curated-register.templates', compact('templates'));
+    }
+
+    public function templateCreate(Request $request)
+    {
+        return view('admin.curated-register.template-create');
+    }
+
+    public function templateEdit(Request $request, $id)
+    {
+        $template = CuratedRegisterTemplate::findOrFail($id);
+
+        return view('admin.curated-register.template-edit', compact('template'));
+    }
+
+    public function templateEditStore(Request $request, $id)
+    {
+        $this->validate($request, [
+            'name' => 'required|string|max:30',
+            'content' => 'required|string|min:5|max:3000',
+            'description' => 'nullable|sometimes|string|max:1000',
+            'active' => 'sometimes',
+        ]);
+        $template = CuratedRegisterTemplate::findOrFail($id);
+        $template->name = $request->input('name');
+        $template->content = $request->input('content');
+        $template->description = $request->input('description');
+        $template->is_active = $request->boolean('active');
+        $template->save();
+
+        return redirect()->back()->with('status', 'Successfully updated template!');
+    }
+
+    public function templateDelete(Request $request, $id)
+    {
+        $template = CuratedRegisterTemplate::findOrFail($id);
+        $template->delete();
+
+        return redirect(route('admin.curated-onboarding.templates'))->with('status', 'Successfully deleted template!');
+    }
+
+    public function templateStore(Request $request)
+    {
+        $this->validate($request, [
+            'name' => 'required|string|max:30',
+            'content' => 'required|string|min:5|max:3000',
+            'description' => 'nullable|sometimes|string|max:1000',
+            'active' => 'sometimes',
+        ]);
+        CuratedRegisterTemplate::create([
+            'name' => $request->input('name'),
+            'content' => $request->input('content'),
+            'description' => $request->input('description'),
+            'is_active' => $request->boolean('active'),
+        ]);
+
+        return redirect(route('admin.curated-onboarding.templates'))->with('status', 'Successfully created new template!');
+    }
+
+    public function getActiveTemplates(Request $request)
+    {
+        $templates = CuratedRegisterTemplate::whereIsActive(true)
+            ->orderBy('order')
+            ->get()
+            ->map(function ($tmp) {
+                return [
+                    'name' => $tmp->name,
+                    'content' => $tmp->content,
+                ];
+            });
+
+        return response()->json($templates);
+    }
 }

+ 19 - 0
app/Models/CuratedRegisterTemplate.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+class CuratedRegisterTemplate extends Model
+{
+    use HasFactory;
+
+    protected $fillable = [
+        'name', 'description', 'content', 'is_active', 'order',
+    ];
+
+    protected $casts = [
+        'is_active' => 'boolean',
+    ];
+}

+ 32 - 0
database/migrations/2024_02_24_105641_create_curated_register_templates_table.php

@@ -0,0 +1,32 @@
+<?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::create('curated_register_templates', function (Blueprint $table) {
+            $table->id();
+            $table->string('name')->nullable();
+            $table->text('description')->nullable();
+            $table->text('content')->nullable();
+            $table->boolean('is_active')->default(false)->index();
+            $table->tinyInteger('order')->default(10)->unsigned()->index();
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('curated_register_templates');
+    }
+};

+ 55 - 3
resources/views/admin/curated-register/partials/activity-log.blade.php

@@ -46,6 +46,21 @@
                     <p class="lead font-weight-bold text-center">Request Additional Details</p>
                     <p class="text-muted">Use this form to request additional details. Once you press Send, we'll send the potential user an email with a special link they can visit with a form that they can provide additional details with. You can also Preview the email before it's sent.</p>
 
+                    <div v-if="responseTemplates && responseTemplates.length" class="my-3">
+                        <p class="small font-weight-bold mb-1">Template Responses</p>
+
+                        <div class="d-grid">
+                            <template v-for="tmpl in responseTemplates">
+                                <button
+                                    class="btn btn-lighter btn-sm py-1 font-weight-bold rounded-lg text-dark border border-muted px-3"
+                                    style="font-size: 13px;"
+                                    @click="useTemplate(tmpl)">
+                                    <i class="far fa-plus mr-1 text-muted"></i> @{{ tmpl.name.slice(0, 25) }}
+                                </button>
+                            </template>
+                        </div>
+                    </div>
+
                     <div class="request-form">
                         <div class="form-group">
                             <label for="requestDetailsMessageInput" class="small text-muted">Your Message:</label>
@@ -60,7 +75,7 @@
                             <p class="help-text small text-right">
                                 <span>@{{ composeMessage && composeMessage.length ? composeMessage.length : 0 }}</span>
                                 <span>/</span>
-                                <span>500</span>
+                                <span>2000</span>
                             </p>
                         </div>
                         <div class="d-flex">
@@ -76,6 +91,12 @@
                                 target="_blank">
                                 Preview
                             </a>
+                            <a
+                                v-if="composeMessage && composeMessage.length"
+                                class="btn btn-outline-danger text-danger rounded-pill btn-sm px-4"
+                                @click="composeMessage = null">
+                                Clear
+                            </a>
                         </div>
                     </div>
                 </div>
@@ -90,6 +111,21 @@
                     <p class="lead font-weight-bold text-center">Send Message</p>
                     <p class="text-muted">Use this form to send a message to the applicant. Once you press Send, we'll send the potential user an email with your message. You can also Preview the email before it's sent.</p>
 
+                    <div v-if="responseTemplates && responseTemplates.length" class="my-3">
+                        <p class="small font-weight-bold mb-1">Template Responses</p>
+
+                        <div class="d-grid">
+                            <template v-for="tmpl in responseTemplates">
+                                <button
+                                    class="btn btn-lighter btn-sm py-1 font-weight-bold rounded-lg text-dark border border-muted px-3"
+                                    style="font-size: 13px;"
+                                    @click="useTemplateMessage(tmpl)">
+                                    <i class="far fa-plus mr-1 text-muted"></i> @{{ tmpl.name.slice(0, 25) }}
+                                </button>
+                            </template>
+                        </div>
+                    </div>
+
                     <div class="request-form">
                         <div class="form-group">
                             <label for="sendMessageInput" class="small text-muted">Your Message:</label>
@@ -187,11 +223,13 @@
                 messageFormOpen: false,
                 composeMessage: null,
                 messageBody: null,
+                responseTemplates: [],
             }
         },
 
         mounted() {
             setTimeout(() => {
+                this.fetchResponseTemplates();
                 this.fetchActivities();
             }, 1000)
         },
@@ -233,10 +271,16 @@
                 return str;
             },
 
+            fetchResponseTemplates() {
+                axios.get('/i/admin/api/curated-onboarding/templates/get')
+                .then(res => {
+                    this.responseTemplates = res.data;
+                })
+            },
+
             fetchActivities() {
                 axios.get('/i/admin/api/curated-onboarding/show/{{$id}}/activity-log')
                 .then(res => {
-                    console.log(res.data);
                     this.activities = res.data;
                 })
                 .finally(() => {
@@ -379,7 +423,15 @@
 
             openUserResponse(activity) {
                 swal('User Response', activity.user_response.message)
-            }
+            },
+
+            useTemplate(tmpl) {
+                this.composeMessage = tmpl.content;
+            },
+
+            useTemplateMessage(tmpl) {
+                this.messageBody = tmpl.content;
+            },
         }
     });
 

+ 4 - 1
resources/views/admin/curated-register/partials/nav.blade.php

@@ -15,7 +15,7 @@
     <div class="col-12">
         <ul class="nav nav-pills">
             <li class="nav-item">
-                <a class="nav-link {{request()->has('filter') ? '':'active'}}" href="/i/admin/curated-onboarding/home">Open Applications</a>
+                <a class="nav-link {{!request()->is('*home') || request()->has('filter') ? '':'active'}}" href="/i/admin/curated-onboarding/home">Open Applications</a>
             </li>
             <li class="nav-item">
                 <a class="nav-link {{request()->has('filter') && request()->filter == 'responses' ? 'active':''}}" href="/i/admin/curated-onboarding/home?filter=responses">User Response Replies</a>
@@ -32,6 +32,9 @@
             <li class="nav-item">
                 <a class="nav-link {{request()->has('filter') && request()->filter == 'all' ? 'active':''}}" href="/i/admin/curated-onboarding/home?filter=all&sort=desc">All Applications</a>
             </li>
+            <li class="nav-item">
+                <a class="nav-link {{ request()->is('*templates*') ? 'active' : ''}}" href="/i/admin/curated-onboarding/templates">Templates</a>
+            </li>
         </ul>
     </div>
 </div>

+ 98 - 0
resources/views/admin/curated-register/template-create.blade.php

@@ -0,0 +1,98 @@
+@extends('admin.partial.template-full')
+
+@section('section')
+</div><div class="header bg-primary pb-3 mt-n4">
+    <div class="container-fluid">
+        <div class="header-body">
+            <div class="row align-items-center py-4">
+                <div class="col-lg-8 col-12">
+                    <p class="display-1 text-white d-inline-block mb-0">Curated Onboarding</p>
+                    <p class="text-white mb-0">The ideal solution for communities seeking a balance between open registration and invite-only membership</p>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+@if((bool) config_cache('instance.curated_registration.enabled'))
+<div class="m-n2 m-lg-4">
+    <div class="container-fluid mt-4">
+        @include('admin.curated-register.partials.nav')
+
+        <div class="row justify-content-center">
+            <div class="col-12 col-lg-6">
+                <div class="card">
+                    <div class="card-body">
+                        <h2 class="display-4">Create Template</h2>
+                        <p class="lead my-0">Create re-usable templates of messages and application requests.</p>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="row justify-content-center">
+            <div class="col-12 col-lg-6">
+                <div class="card">
+                    <div class="card-body">
+                        @if ($errors->any())
+                            <div class="alert alert-danger">
+                                @foreach ($errors->all() as $error)
+                                    <p class="font-weight-bold mb-0">{{ $error }}</p>
+                                @endforeach
+                            </div>
+                        @endif
+                        <form method="post">
+                            @csrf
+
+                            <div class="form-group">
+                                <label class="small font-weight-bold">Shortcut/Name</label>
+                                <input
+                                    class="form-control"
+                                    name="name"
+                                    value="{{old('name')}}"
+                                    placeholder="An optional name/shortcut for easy access" />
+                            </div>
+
+                            <div class="form-group">
+                                <label class="small font-weight-bold">Content</label>
+                                <textarea
+                                    class="form-control"
+                                    name="content"
+                                    value="{{old('content')}}"
+                                    rows="8"
+                                    placeholder="Add your custom message template here..."></textarea>
+                            </div>
+
+                            <p class="font-weight-bold">
+                                <a class="font-weight-bold small" data-toggle="collapse" href="#collapseDescription" aria-expanded="false" aria-controls="collapseDescription">
+                                    Add optional description
+                                </a>
+                            </p>
+
+                            <div class="collapse" id="collapseDescription">
+                                <div class="form-group">
+                                    <label class="small font-weight-bold">Description</label>
+                                    <textarea
+                                        class="form-control"
+                                        name="description"
+                                        rows="4"
+                                        placeholder="Add an optional description that is only visible to admins..."></textarea>
+                                </div>
+                            </div>
+
+                            <div class="custom-control custom-checkbox">
+                                <input type="checkbox" class="custom-control-input" id="active" name="active" checked>
+                                <label class="custom-control-label font-weight-bold" for="active">Mark as Active</label>
+                            </div>
+
+                            <hr>
+                            <button class="btn btn-primary btn-block rounded-pill font-weight-bold">Create Template</button>
+                        </form>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+@endif
+
+@endsection

+ 148 - 0
resources/views/admin/curated-register/template-edit.blade.php

@@ -0,0 +1,148 @@
+@extends('admin.partial.template-full')
+
+@section('section')
+</div><div class="header bg-primary pb-3 mt-n4">
+    <div class="container-fluid">
+        <div class="header-body">
+            <div class="row align-items-center py-4">
+                <div class="col-lg-8 col-12">
+                    <p class="display-1 text-white d-inline-block mb-0">Curated Onboarding</p>
+                    <p class="text-white mb-0">The ideal solution for communities seeking a balance between open registration and invite-only membership</p>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+@if((bool) config_cache('instance.curated_registration.enabled'))
+<div class="m-n2 m-lg-4">
+    <div class="container-fluid mt-4">
+        @include('admin.curated-register.partials.nav')
+
+        <div class="row justify-content-center">
+            <div class="col-12 col-lg-8">
+                @if (session('status'))
+                    <div class="alert alert-success font-weight-bold lead" id="shm">
+                        {{ session('status') }}
+                    </div>
+                    <script>
+                        setTimeout(() => document.getElementById('shm').classList.add('animate__animated', 'animate__bounceOutLeft'), 2000);
+                        setTimeout(() => document.getElementById('shm').style.display = 'none', 2500);
+                    </script>
+                @endif
+                <div class="card">
+                    <div class="card-body">
+                        <h2 class="display-4 mb-0">Edit Template</h2>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="row justify-content-center">
+            <div class="col-12 col-lg-8">
+                <div class="card">
+                    <div class="card-body">
+                        @if ($errors->any())
+                            <div class="alert alert-danger">
+                                @foreach ($errors->all() as $error)
+                                    <p class="font-weight-bold mb-0">{{ $error }}</p>
+                                @endforeach
+                            </div>
+                        @endif
+                        <form method="post" id="updateForm">
+                            @csrf
+
+                            <div class="form-group">
+                                <label class="small font-weight-bold">Shortcut/Name</label>
+                                <input
+                                    class="form-control"
+                                    name="name"
+                                    value="{{$template->name}}"
+                                    placeholder="An optional name/shortcut for easy access" />
+                            </div>
+
+                            <div class="form-group">
+                                <label class="small font-weight-bold">Content</label>
+                                <textarea
+                                    class="form-control"
+                                    name="content"
+                                    rows="{{$template->content && strlen($template->content) > 500 ? 16 : 5}}"
+                                    placeholder="Add your custom message template here...">{{$template->content}}</textarea>
+                            </div>
+
+                            @if($template->description == null)
+                            <p class="font-weight-bold">
+                                <a class="font-weight-bold small" data-toggle="collapse" href="#collapseDescription" aria-expanded="false" aria-controls="collapseDescription">
+                                    Add optional description
+                                </a>
+                            </p>
+                            @endif
+
+                            <div class="collapse {{ $template->description === null ? '':'show'}}" id="collapseDescription">
+                                <div class="form-group">
+                                    <label class="small font-weight-bold">Description</label>
+                                    <textarea
+                                        class="form-control"
+                                        name="description"
+                                        rows="4"
+                                        placeholder="Add an optional description that is only visible to admins...">{{ $template->description }}</textarea>
+                                </div>
+                            </div>
+
+                            <div class="custom-control custom-checkbox">
+                                <input type="checkbox" class="custom-control-input" id="active" name="active" {{ $template->is_active ? 'checked' : ''}}>
+                                <label class="custom-control-label font-weight-bold" for="active">Mark as Active</label>
+                            </div>
+
+                            <hr>
+                            <div class="d-flex">
+                                <button type="button" class="btn btn-primary flex-grow-1 rounded-pill font-weight-bold" id="saveBtn">Save</button>
+                                <button type="button" class="btn btn-danger rounded-pill font-weight-bold" id="deleteBtn">Delete</button>
+                            </div>
+                        </form>
+                        <form method="post" id="deleteForm">
+                            @method('DELETE')
+                            @csrf
+                        </form>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+@endif
+
+@endsection
+
+@push('scripts')
+<script>
+    $('#saveBtn').click(() => {
+        $('#updateForm').submit()
+    })
+    $('#deleteBtn').click(() => {
+        swal({
+            title: 'Confirm Deletion',
+            text: 'Are you sure you want to delete this template? It will not be recoverable',
+            icon: 'warning',
+            dangerMode: true,
+            buttons: {
+                close: {
+                    text: "Close",
+                    value: "close",
+                    close: true,
+                    className: "swal-button--cancel"
+                },
+                confirm: {
+                    text: "Delete",
+                    value: "delete",
+                    className: "btn-danger"
+                }
+            }
+        }).then(res => {
+            if(res == 'delete') {
+                $('#deleteForm').submit();
+                // window.location.href = '/i/admin/curated-onboarding/templates';
+            }
+        })
+    })
+</script>
+@endpush

+ 91 - 0
resources/views/admin/curated-register/templates.blade.php

@@ -0,0 +1,91 @@
+@extends('admin.partial.template-full')
+
+@section('section')
+</div><div class="header bg-primary pb-3 mt-n4">
+    <div class="container-fluid">
+        <div class="header-body">
+            <div class="row align-items-center py-4">
+                <div class="col-lg-8 col-12">
+                    <p class="display-1 text-white d-inline-block mb-0">Curated Onboarding</p>
+                    <p class="text-white mb-0">The ideal solution for communities seeking a balance between open registration and invite-only membership</p>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+@if((bool) config_cache('instance.curated_registration.enabled'))
+<div class="m-n2 m-lg-4">
+    <div class="container-fluid mt-4">
+        @include('admin.curated-register.partials.nav')
+
+        <div class="row">
+            <div class="col-12">
+                @if (session('status'))
+                    <div class="alert alert-success font-weight-bold lead" id="shm">
+                        {{ session('status') }}
+                    </div>
+                    <script>
+                        setTimeout(() => document.getElementById('shm').classList.add('animate__animated', 'animate__bounceOutLeft'), 2000);
+                        setTimeout(() => document.getElementById('shm').style.display = 'none', 2500);
+                    </script>
+                @endif
+                <div class="card">
+                    <div class="card-body d-flex justify-content-between align-items-center">
+                        <p class="lead my-0">Create and manage re-usable templates of messages and application requests.</p>
+                        <a class="btn btn-primary font-weight-bold rounded-pill" href="{{route('admin.curated-onboarding.create-template')}}">Create new Template</a>
+                    </div>
+                </div>
+            </div>
+
+            <div class="col-12">
+
+                <div class="table-responsive rounded">
+                    <table class="table table-dark">
+                        <thead class="thead-dark">
+                            <tr>
+                                <th scope="col">ID</th>
+                                <th scope="col">Shortcut/Name</th>
+                                <th scope="col">Content</th>
+                                <th scope="col">Active</th>
+                                <th scope="col">Created</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                        @foreach($templates as $template)
+                            <tr>
+                                <td class="align-middle">
+                                    <a
+                                        href="/i/admin/curated-onboarding/templates/edit/{{$template->id}}"
+                                        class="font-weight-bold">
+                                        {{ $template->id }}
+                                    </a>
+                                </td>
+                                <td class="align-middle">
+                                    {{ $template->name }}
+                                </td>
+                                <td class="align-middle">
+                                    {{ str_limit($template->content, 80) }}
+                                </td>
+                                <td class="align-middle">
+                                    {{ $template->is_active ? '✅' : '❌' }}
+                                </td>
+                                <td class="align-middle">
+                                    {{ $template->created_at->format('M d Y') }}
+                                </td>
+                            </tr>
+                        @endforeach
+                        </tbody>
+                    </table>
+
+                    <div class="d-flex mt-3">
+                        {{ $templates->links() }}
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+@endif
+
+@endsection

+ 7 - 0
routes/web-admin.php

@@ -106,6 +106,12 @@ Route::domain(config('pixelfed.domain.admin'))->prefix('i/admin')->group(functio
     Route::get('asf/home', 'AdminShadowFilterController@home');
     Route::redirect('curated-onboarding/', 'curated-onboarding/home');
     Route::get('curated-onboarding/home', 'AdminCuratedRegisterController@index')->name('admin.curated-onboarding');
+    Route::get('curated-onboarding/templates', 'AdminCuratedRegisterController@templates')->name('admin.curated-onboarding.templates');
+    Route::get('curated-onboarding/templates/create', 'AdminCuratedRegisterController@templateCreate')->name('admin.curated-onboarding.create-template');
+    Route::post('curated-onboarding/templates/create', 'AdminCuratedRegisterController@templateStore');
+    Route::get('curated-onboarding/templates/edit/{id}', 'AdminCuratedRegisterController@templateEdit');
+    Route::post('curated-onboarding/templates/edit/{id}', 'AdminCuratedRegisterController@templateEditStore');
+    Route::delete('curated-onboarding/templates/edit/{id}', 'AdminCuratedRegisterController@templateDelete');
     Route::get('curated-onboarding/show/{id}/preview-details-message', 'AdminCuratedRegisterController@previewDetailsMessageShow');
     Route::get('curated-onboarding/show/{id}/preview-message', 'AdminCuratedRegisterController@previewMessageShow');
     Route::get('curated-onboarding/show/{id}', 'AdminCuratedRegisterController@show');
@@ -162,5 +168,6 @@ Route::domain(config('pixelfed.domain.admin'))->prefix('i/admin')->group(functio
         Route::post('curated-onboarding/show/{id}/message/send', 'AdminCuratedRegisterController@apiMessageSendStore');
         Route::post('curated-onboarding/show/{id}/reject', 'AdminCuratedRegisterController@apiHandleReject');
         Route::post('curated-onboarding/show/{id}/approve', 'AdminCuratedRegisterController@apiHandleApprove');
+        Route::get('curated-onboarding/templates/get', 'AdminCuratedRegisterController@getActiveTemplates');
     });
 });