Skip to content

Instantly share code, notes, and snippets.

@Neophen
Last active November 9, 2022 20:48
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Neophen/88cfa24fcbc3b7695851d12abee74554 to your computer and use it in GitHub Desktop.
Save Neophen/88cfa24fcbc3b7695851d12abee74554 to your computer and use it in GitHub Desktop.
<?php
namespace App\Providers;
use App\Support\Utils\OctaModal;
use App\Support\Utils\OctaResponse;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\ServiceProvider;
use Inertia\Inertia;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
$this->registerInertia();
}
public function registerInertia()
{
Inertia::version(function () {
return md5_file(public_path('mix-manifest.json'));
});
Inertia::share([
OctaModal::key => fn () => Session::has(OctaModal::key) ? Cache::pull(Session::get(OctaModal::key)) : null,
OctaResponse::key => fn () => Session::has(OctaResponse::key) ? Cache::pull(Session::get(OctaResponse::key)) : null,
'flash' => fn () => [
'success' => Session::get('success'),
'error' => Session::get('error'),
],
'errors' => fn () => Session::get('errors')
? Session::get('errors')
->getBag('default')
->getMessages()
: (object) [],
]);
}
}
<template>
<div
class="fixed inset-0 z-10 bg-black bg-opacity-75"
:class="{
block: hasModals,
hidden: !hasModals,
}"
>
<StackModalForm
v-for="(modal, i) in modals"
:key="modal.id"
:config="modal.config"
:show="modal.show"
:isActive="i == modals.length - 1"
@close="removeModal(modal.id)"
@success="removeModal(modal.id)"
/>
</div>
</template>
<script>
import { ref, watch } from "@vue/composition-api";
import StackModalForm from "./Form/StackModalForm.vue";
import { injectNotifications } from "../../../common/composables";
export default {
name: "Modals",
components: {
StackModalForm,
},
setup(props, { root }) {
const notifications = injectNotifications();
const modals = ref([]);
const hasModals = ref(false);
const addModal = (modal = null) => {
if (!modal) return;
modals.value.push({
id: modal.id,
show: true,
config: modal,
});
hasModals.value = true;
};
const removeModal = (id) => {
const hideModal = (modal) =>
modal.id !== id ? modal : { ...modal, show: false };
modals.value = modals.value.map(hideModal);
setTimeout(() => {
modals.value = modals.value.filter((x) => x.id !== id);
if (modals.value.length === 0) {
hasModals.value = false;
}
}, 250);
};
watch(() => root.$page.octa_modal, addModal);
watch(
() => root.$page.octa_response_item,
(item) => {
if (!item) return;
notifications.emit(item.id, item.item);
},
);
return {
modals,
removeModal,
addModal,
hasModals,
};
},
};
</script>
<?php
namespace App\Support\Utils;
use App\Support\ModalActionEnum;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
class OctaModal
{
public const key = "octa_modal";
private $uuid;
private ModalActionEnum $type;
private $title;
private $confirm;
private $fieldset;
private $submit;
public static function create()
{
return new OctaModal(ModalActionEnum::post(), "Create");
}
public static function update()
{
return new OctaModal(ModalActionEnum::put(), "Update");
}
public function __construct(ModalActionEnum $type, string $confirm)
{
do {
$uuid = Str::uuid()->__toString() . "-" . auth()->id();
} while (Cache::has($uuid));
$this->uuid = $uuid;
$this->type = $type;
$this->confirm = $confirm;
}
public function title(string $title): OctaModal
{
$this->title = $title;
return $this;
}
public function confirm(string $confirm): OctaModal
{
$this->confirm = $confirm;
return $this;
}
public function route(...$routeParams): OctaModal
{
$this->submit = route(...$routeParams);
return $this;
}
public function fieldset($fieldset): OctaModal
{
$this->fieldset = $fieldset;
return $this;
}
public function get()
{
Cache::put($this->uuid, [
"id" => $this->uuid,
"title" => $this->title,
"confirm" => $this->confirm,
"type" => $this->type,
"fieldset" => $this->fieldset,
"submit" => $this->submit,
]);
return redirect()->back()->with(self::key, $this->uuid);
}
}
<?php
namespace App\Support\Utils;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
class OctaResponse
{
public const key = "octa_response_item";
private $uuid;
private $id;
private $item;
private $success;
public static function make(string $success)
{
return new OctaResponse($success);
}
public function __construct(string $success)
{
do {
$uuid = Str::uuid()->__toString() . "-" . auth()->id();
} while (Cache::has($uuid));
$this->uuid = $uuid;
$this->success = $success;
}
public function id(string $id): OctaResponse
{
$this->id = $id;
return $this;
}
public function item($item): OctaResponse
{
$this->item = $item;
return $this;
}
public function get()
{
if ($this->id) {
Cache::put($this->uuid, [
"id" => $this->id,
"item" => $this->item,
]);
Session::flash(self::key, $this->uuid);
}
return redirect()->back()->with('success', $this->success);
}
}
<?php
namespace App\Http\Admin\Controllers\Posts;
use App\Fieldsets\PostFieldset;
use App\Policies\PostsAreaPolicy;
use App\Support\Utils\OctaModal;
class PostCreateController
{
public function __invoke()
{
if (!gateAllows(PostsAreaPolicy::EDIT)) {
return redirect()->back()->with('error', PostsAreaPolicy::EDIT_ERROR);
};
return OctaModal::create()
->title("Create Post")
->route('admin.posts.store')
->fieldset(PostFieldset::get())
->get();
}
}
<?php
namespace App\Http\Admin\Controllers\Tags;
use App\Models\PostThread;
use App\Fieldsets\TagFieldset;
use App\Policies\TagsAreaPolicy;
use App\Support\Utils\OctaModal;
use App\Support\Utils\OctaResponse;
use Illuminate\Http\Request;
class PostThreadsController
{
public function create(Request $request)
{
if (!gateAllows(TagsAreaPolicy::EDIT)) {
return redirect()->back()->with('error', TagsAreaPolicy::EDIT_ERROR);
}
return OctaModal::create()
->title("Add thread")
->confirm("Add")
// we add this to the visit request
->route('admin.post-threads.store', $request->get('octa_item_id'))
->fieldset(TagFieldset::get('post_threads'))
->get();
}
public function store(Request $request, string $id = null)
{
if (!gateAllows(TagsAreaPolicy::EDIT)) {
return redirect()->back()->with('error', TagsAreaPolicy::EDIT_ERROR);
}
$validated = $request->validate([
// some rules
]);
$tag = PostThread::create($validated);
return OctaResponse::make('Post thread added')
->id($id)
->item([
'value' => $tag->id,
'label' => $tag->title,
])
->get();
}
}
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Admin\Controllers\Posts\PostCreateController;
use App\Http\Admin\Controllers\Tags\PostThreadsController;
Route::get('posts/create', PostCreateController::class)->name('posts.create');
Route::get('post-threads/create', [PostThreadsController::class, 'create'])->name('post-threads.create');
// For the nested modals we add identification you can name it whatever
Route::post('post-threads/create/{id?}', [PostThreadsController::class, 'store'])->name('post-threads.store');
<template>
<div class="absolute w-full h-screen overflow-y-auto">
<transition
enter-active-class="transition duration-200 ease-out"
leave-active-class="transition duration-200 ease-in"
enter-class="transform translate-x-24 opacity-0"
enter-to-class="transform translate-x-0 opacity-100"
leave-class="transform translate-x-0 opacity-100"
leave-to-class="transform translate-x-24 opacity-0"
appear
>
<div
v-if="show"
class="relative flex flex-col items-center min-h-screen overflow-y-auto"
:class="{
'transform duration-200 -translate-x-24': !isActive,
'transform duration-200 translate-x-0': isActive,
}"
>
<div class="flex-shrink-0 w-8 h-24"></div>
<div class="relative flex items-center flex-1">
<OModalContent :size="size">
<div class="flex items-start justify-between mb-10">
<slot>
<OH class="truncate">{{ title }}</OH>
</slot>
<OButton
icon="close"
size="is-lg"
class="flex-shrink-0 ml-4"
@click="close"
/>
</div>
<OctoForm
:fieldset="octaForm.fieldset"
:config="octaForm.formConfig"
:errors="errors"
:loading="isLoading"
@cancel="close"
@submit="onSubmit"
/>
</OModalContent>
<transition
enter-active-class="transition-opacity duration-200 ease-out"
leave-active-class="transition-opacity duration-200 ease-in"
enter-class="opacity-0"
enter-to-class="opacity-100"
leave-class="opacity-100"
leave-to-class="opacity-0"
appear
>
<div
v-if="!isActive"
class="absolute inset-0 z-10 w-full h-full bg-black bg-opacity-50 rounded-large"
></div>
</transition>
</div>
<div class="flex-shrink-0 w-8 h-24"></div>
</div>
</transition>
</div>
</template>
<script>
import { computed, reactive, toRefs } from "@vue/composition-api";
import isEmpty from "lodash/isEmpty";
export default {
name: "StackModalForm",
props: {
show: Boolean,
isActive: Boolean,
config: Object,
},
setup(props, { root, emit }) {
// NOTE WE PROVIDE THE FILE MANAGER IN THE LAYOUT FILE SINCE OF VUE-PORTAL BUG
const state = reactive({
errors: [],
isLoading: false,
size: computed(() => props.config.size || "large"),
title: computed(() => props.config.title),
octaForm: computed(() => ({
url: props.config.submit,
type: props.config.type,
formConfig: {
confirm: props.config.confirm,
},
fieldset: props.config.fieldset,
})),
});
const onSubmit = async (formData) => {
state.isLoading = true;
try {
formData.append("_method", props.config.type);
await root.$inertia.post(props.config.submit, formData);
state.isLoading = false;
if (!isEmpty(root.$page.errors)) {
state.errors = root.$page.errors;
} else {
emit("success");
}
} catch (error) {
console.error(error);
state.isLoading = false;
}
};
const close = () => {
emit("close");
};
return {
...toRefs(state),
onSubmit,
close,
};
},
};
</script>
@patombugua
Copy link

Hi
This is very impressive but where is App\Support\ModalActionEnum file?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment