banner done

dev
Aji Kamaludin 1 year ago
parent 5d93cba509
commit c6abdf69d3
No known key found for this signature in database
GPG Key ID: 19058F67F0083AD3

@ -3,7 +3,7 @@
### Admin
- [x] CRUD Info
- [ ] CRUD Banner
- [x] CRUD Banner
- [ ] CRUD Rekening / Account
- [ ] CRUD Customer
- [ ] CRUD Voucher

@ -0,0 +1,83 @@
<?php
namespace App\Http\Controllers;
use App\Models\Banner;
use Illuminate\Http\Request;
class BannerController extends Controller
{
public function index()
{
$query = Banner::orderBy('updated_at', 'desc')->paginate();
return inertia('Banner/Index', [
'query' => $query
]);
}
public function create()
{
return inertia('Banner/Form');
}
public function store(Request $request)
{
$request->validate([
'image' => 'required|image',
'title' => 'required|string',
'description' => 'required|string',
]);
$file = $request->file('image');
$file->store('uploads', 'public');
Banner::create([
'image' => $file->hashName('uploads'),
'title' => $request->title,
'description' => $request->description,
]);
return redirect()->route('banner.index')
->with('message', ['type' => 'success', 'message' => 'Item has beed saved']);
}
public function edit(Banner $banner)
{
return inertia('Banner/Form', [
'banner' => $banner
]);
}
public function update(Request $request, Banner $banner)
{
$request->validate([
'image' => 'nullable|image',
'title' => 'required|string',
'description' => 'required|string',
]);
if ($request->hasFile('image')) {
$file = $request->file('image');
$file->store('uploads', 'public');
$banner->image = $file->hashName('uploads');
}
$banner->update([
'image' => $banner->image,
'title' => $request->title,
'description' => $request->description,
]);
return redirect()->route('banner.index')
->with('message', ['type' => 'success', 'message' => 'Item has beed updated']);
}
public function destroy(Banner $banner)
{
$banner->delete();
return redirect()->route('banner.index')
->with('message', ['type' => 'success', 'message' => 'Item has beed deleted']);
}
}

@ -73,7 +73,7 @@ class AuthController extends Controller
'fullname' => $user->name,
'name' => $user->nickname,
'email' => $user->email,
'username' => Str::random(10),
'username' => Str::slug($user->name . '_' . Str::random(5), '_'),
'google_id' => $user->id,
'google_oauth_response' => json_encode($user),
'customer_level_id' => $basic->id,

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Customer;
use App\Http\Controllers\Controller;
use App\Models\Banner;
use App\Models\Info;
class HomeController extends Controller
@ -10,9 +11,18 @@ class HomeController extends Controller
public function index()
{
$infos = Info::where('is_publish', 1)->orderBy('updated_at', 'desc')->get();
$banners = Banner::orderBy('updated_at', 'desc')->get();
return inertia('Home/Index/Index', [
'infos' => $infos,
'banners' => $banners
]);
}
public function banner(Banner $banner)
{
return inertia('Home/Index/Banner', [
'banner' => $banner,
]);
}
}

@ -2,6 +2,9 @@
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class GeneralController extends Controller
{
public function index()
@ -13,4 +16,18 @@ class GeneralController extends Controller
{
return inertia('Maintance');
}
public function upload(Request $request)
{
$request->validate(['image' => 'required|file']);
$file = $request->file('image');
$file->store('uploads', 'public');
return response()->json([
'id' => Str::ulid(),
'name' => $file->getClientOriginalName(),
'url' => asset($file->hashName('uploads')),
]);
}
}

@ -37,6 +37,7 @@ class HandleInertiaRequests extends Middleware
'message' => fn () => $request->session()->get('message'),
],
'app_name' => env('APP_NAME', 'App Name'),
'csrf_token' => csrf_token(),
]);
}
}

@ -2,6 +2,8 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
class Banner extends Model
{
protected $fillable = [
@ -11,4 +13,15 @@ class Banner extends Model
'destination',
'type',
];
protected $appends = [
'image_url'
];
protected function imageUrl(): Attribute
{
return Attribute::make(get: function () {
return asset($this->image);
});
}
}

@ -41,6 +41,7 @@ class Customer extends Authenticatable
'image_url',
'display_deposit',
'display_coin',
'display_phone'
];
public function imageUrl(): Attribute
@ -62,6 +63,16 @@ class Customer extends Authenticatable
);
}
public function displayPhone(): Attribute
{
return Attribute::make(get: function () {
if ($this->phone === null) {
return ' - ';
}
return '+62' . $this->phone;
});
}
public function displayDeposit(): Attribute
{
return Attribute::make(get: function () {

@ -16,7 +16,7 @@ return new class extends Migration
$table->string('image');
$table->string('title');
$table->string('description')->nullable();
$table->text('description')->nullable();
$table->string('destination')->nullable();
$table->string('type')->nullable();
$table->smallInteger('is_publish')->nullable();

@ -2,6 +2,7 @@
namespace Database\Seeders;
use App\Models\Banner;
use App\Models\Info;
use Illuminate\Database\Seeder;
@ -15,6 +16,7 @@ class DummySeeder extends Seeder
public function run()
{
$this->info();
$this->banner();
}
public function info()
@ -24,4 +26,16 @@ class DummySeeder extends Seeder
'is_publish' => 1,
]);
}
public function banner()
{
$images = ['1.webp', '2.webp', '3.webp'];
foreach ($images as $index => $image) {
Banner::create([
'title' => 'Banner ' . $index,
'image' => 'sample/' . $image,
'description' => '<h1>Banner </h1>'
]);
}
}
}

22
package-lock.json generated

@ -5,6 +5,7 @@
"packages": {
"": {
"dependencies": {
"@tinymce/tinymce-react": "^4.3.0",
"flowbite": "^1.6.3",
"flowbite-react": "^0.3.8",
"is": "^3.3.0",
@ -15,7 +16,8 @@
"react-icons": "^4.7.1",
"react-number-format": "^5.1.2",
"react-toastify": "^9.1.1",
"react-use": "^17.4.0"
"react-use": "^17.4.0",
"tinymce": "^6.4.2"
},
"devDependencies": {
"@headlessui/react": "^1.4.2",
@ -930,6 +932,19 @@
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1"
}
},
"node_modules/@tinymce/tinymce-react": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@tinymce/tinymce-react/-/tinymce-react-4.3.0.tgz",
"integrity": "sha512-iB4cUsYfcJL4NGuKhqCGYuTmFTje3nPxyPv1HxprTsp/YMGuuiiSNWrv3zwI31QX5Cn8qeq9MrMDnbxuRugHyg==",
"dependencies": {
"prop-types": "^15.6.2",
"tinymce": "^6.3.1"
},
"peerDependencies": {
"react": "^18.0.0 || ^17.0.1 || ^16.7.0",
"react-dom": "^18.0.0 || ^17.0.1 || ^16.7.0"
}
},
"node_modules/@types/js-cookie": {
"version": "2.2.7",
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.7.tgz",
@ -2825,6 +2840,11 @@
"node": ">=10"
}
},
"node_modules/tinymce": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/tinymce/-/tinymce-6.4.2.tgz",
"integrity": "sha512-te+4c8PoAwTKPMBQtMQehnSZlOO9Ay5tDgaRFJKBehYb6SlX2PYZ0v3oe/cmiv5EfqdwZLkEMXk2MNOeApZZLg=="
},
"node_modules/to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",

@ -20,16 +20,18 @@
"vite": "^4.0.0"
},
"dependencies": {
"@tinymce/tinymce-react": "^4.3.0",
"flowbite": "^1.6.3",
"flowbite-react": "^0.3.8",
"react-icons": "^4.7.1",
"is": "^3.3.0",
"moment": "^2.29.4",
"nprogress": "^0.2.0",
"qs": "^6.11.0",
"react-datepicker": "^4.8.0",
"react-icons": "^4.7.1",
"react-number-format": "^5.1.2",
"react-toastify": "^9.1.1",
"react-use": "^17.4.0"
"react-use": "^17.4.0",
"tinymce": "^6.4.2"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

@ -0,0 +1,165 @@
import { Editor } from '@tinymce/tinymce-react'
// TinyMCE so the global var exists
// eslint-disable-next-line no-unused-vars
import tinymce from 'tinymce/tinymce'
// DOM model
import 'tinymce/models/dom/model'
// Theme
import 'tinymce/themes/silver'
// Toolbar icons
import 'tinymce/icons/default'
// Editor styles
import 'tinymce/skins/ui/oxide/skin.min.css'
// importing the plugin js.
// if you use a plugin that is not listed here the editor will fail to load
import 'tinymce/plugins/advlist'
import 'tinymce/plugins/anchor'
import 'tinymce/plugins/autolink'
import 'tinymce/plugins/autoresize'
import 'tinymce/plugins/autosave'
import 'tinymce/plugins/charmap'
import 'tinymce/plugins/code'
import 'tinymce/plugins/codesample'
import 'tinymce/plugins/directionality'
import 'tinymce/plugins/emoticons'
import 'tinymce/plugins/fullscreen'
import 'tinymce/plugins/help'
import 'tinymce/plugins/image'
import 'tinymce/plugins/importcss'
import 'tinymce/plugins/insertdatetime'
import 'tinymce/plugins/link'
import 'tinymce/plugins/lists'
import 'tinymce/plugins/media'
import 'tinymce/plugins/nonbreaking'
import 'tinymce/plugins/pagebreak'
import 'tinymce/plugins/preview'
import 'tinymce/plugins/quickbars'
import 'tinymce/plugins/save'
import 'tinymce/plugins/searchreplace'
import 'tinymce/plugins/table'
import 'tinymce/plugins/template'
import 'tinymce/plugins/visualblocks'
import 'tinymce/plugins/visualchars'
import 'tinymce/plugins/wordcount'
// importing plugin resources
import 'tinymce/plugins/emoticons/js/emojis'
// Content styles, including inline UI like fake cursors
/* eslint import/no-webpack-loader-syntax: off */
import contentCss from 'tinymce/skins/content/default/content.min.css?inline'
import contentUiCss from 'tinymce/skins/ui/oxide/content.min.css?inline'
import generalCss from '../../css/app.css?inline'
import { usePage } from '@inertiajs/react'
export default function BundledEditor(props) {
const { init, ...rest } = props
const { csrf_token } = usePage().props
const FilePickerCallback = async (callback, value, meta) => {
// Provide file and text for the link dialog
const input = document.createElement('input')
input.setAttribute('type', 'file')
input.setAttribute('accept', 'image/*,video/*')
input.addEventListener('change', async (e) => {
const body = new FormData()
body.append('_token', csrf_token)
body.append('image', e.target.files[0])
await fetch(route('post.upload'), {
method: 'post',
body: body,
headers: {
'accept-content': 'application/json',
'X-CSSRF-TOKEN': csrf_token,
},
credentials: 'include',
})
.then((res) => res.json())
.then((res) => {
callback(res.url, { alt: 'My alt text' })
})
.catch((err) => {
alert(err)
})
})
input.click()
}
const ImageUploadHandler = (blobInfo, progress) =>
new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.withCredentials = true
xhr.open('POST', route('post.upload'))
xhr.upload.onprogress = (e) => {
progress((e.loaded / e.total) * 100)
}
xhr.onload = () => {
if (xhr.status === 403) {
reject({
message: 'HTTP Error: ' + xhr.status,
remove: true,
})
return
}
if (xhr.status < 200 || xhr.status >= 300) {
reject('HTTP Error: ' + xhr.status)
return
}
const json = JSON.parse(xhr.responseText)
if (!json || typeof json.url != 'string') {
reject('Invalid JSON: ' + xhr.responseText)
return
}
resolve(json.url)
}
xhr.onerror = () => {
reject(
'Image upload failed due to a XHR Transport error. Code: ' +
xhr.status
)
}
const formData = new FormData()
formData.append('_token', csrf_token)
formData.append('image', blobInfo.blob(), blobInfo.filename())
xhr.send(formData)
})
// note that skin and content_css is disabled to avoid the normal
// loading process and is instead loaded as a string via content_style
return (
<Editor
init={{
...init,
file_picker_callback: FilePickerCallback,
images_upload_handler: ImageUploadHandler,
promotion: false,
image_caption: true,
image_advtab: true,
object_resizing: true,
skin: false,
content_css: false,
content_style: [contentCss, contentUiCss, generalCss].join(
'\n'
),
importcss_append: true,
}}
{...rest}
/>
)
}

@ -1,4 +1,10 @@
import { HiChartPie, HiUser, HiUsers, HiUserGroup } from 'react-icons/hi'
import {
HiChartPie,
HiUser,
HiUsers,
HiUserGroup,
HiInformationCircle,
} from 'react-icons/hi'
import { HiQuestionMarkCircle } from 'react-icons/hi2'
export default [
@ -10,6 +16,14 @@ export default [
active: 'dashboard',
permission: 'view-dashboard',
},
{
name: 'Banner',
show: true,
icon: HiInformationCircle,
route: route('banner.index'),
active: 'banner.*',
permission: 'view-banner',
},
{
name: 'Info',
show: true,

@ -0,0 +1,125 @@
import React, { useEffect, Suspense } from 'react'
import { isEmpty } from 'lodash'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import FormInput from '@/Components/FormInput'
import Button from '@/Components/Button'
import { Head, useForm } from '@inertiajs/react'
import FormFile from '@/Components/FormFile'
const TinyEditor = React.lazy(() => import('@/Components/TinyMCE'))
export default function Form(props) {
const { banner } = props
const { data, setData, post, processing, errors } = useForm({
title: '',
description: '',
image: '',
image_url: '',
})
const handleOnChange = (event) => {
setData(
event.target.name,
event.target.type === 'checkbox'
? event.target.checked
? 1
: 0
: event.target.value
)
}
const handleSubmit = () => {
if (isEmpty(banner) === false) {
post(route('banner.update', banner))
return
}
post(route('banner.store'))
}
useEffect(() => {
if (isEmpty(banner) === false) {
setData({
title: banner.title,
description: banner.description,
image_url: banner.image_url,
})
}
}, [banner])
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Banner'}
action={'Form'}
>
<Head title="Banner" />
<div>
<div className="mx-auto sm:px-6 lg:px-8">
<div className="overflow-hidden p-4 shadow-sm sm:rounded-lg bg-white dark:bg-gray-800 flex flex-col ">
<div className="text-xl font-bold mb-4">Banner</div>
<FormInput
name="title"
value={data.title}
onChange={handleOnChange}
label="Title"
error={errors.title}
/>
<FormFile
label={'Image'}
onChange={(e) =>
setData('image', e.target.files[0])
}
error={errors.image}
preview={
isEmpty(data.image_url) === false && (
<img
src={data.image_url}
className="mb-1 h-24 w-full object-cover"
alt="preview"
/>
)
}
/>
<div className="py-4">
<Suspense fallback={<div>Loading...</div>}>
<TinyEditor
value={data.description}
init={{
height: 500,
// menubar: false,
menubar:
'file edit view insert format tools table help',
plugins:
'preview importcss searchreplace autolink directionality code visualblocks visualchars fullscreen image link media codesample table charmap pagebreak nonbreaking anchor insertdatetime advlist lists wordcount help charmap emoticons',
toolbar_mode: 'scrolling',
toolbar:
'undo redo | insertfile image media link | bold italic underline strikethrough | fontfamily fontsize blocks | alignleft aligncenter alignright alignjustify | outdent indent | numlist bullist | forecolor backcolor removeformat | charmap emoticons | fullscreen preview save print | ltr rtl | anchor codesample',
}}
onEditorChange={(newValue, editor) => {
setData(
'description',
editor.getContent()
)
}}
/>
</Suspense>
</div>
<div className="mt-8">
<Button
onClick={handleSubmit}
processing={processing}
>
Simpan
</Button>
</div>
</div>
</div>
</div>
</AuthenticatedLayout>
)
}

@ -0,0 +1,143 @@
import React, { useEffect, useState } from 'react'
import { Link, router } from '@inertiajs/react'
import { usePrevious } from 'react-use'
import { Head } from '@inertiajs/react'
import { Button, Dropdown } from 'flowbite-react'
import { HiPencil, HiTrash } from 'react-icons/hi'
import { useModalState } from '@/hooks'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import Pagination from '@/Components/Pagination'
import ModalConfirm from '@/Components/ModalConfirm'
import { hasPermission } from '@/utils'
export default function Info(props) {
const {
query: { links, data },
auth,
} = props
const confirmModal = useModalState()
const handleDeleteClick = (banner) => {
confirmModal.setData(banner)
confirmModal.toggle()
}
const onDelete = () => {
if (confirmModal.data !== null) {
router.delete(route('banner.destroy', confirmModal.data.id))
}
}
const canCreate = hasPermission(auth, 'create-banner')
const canUpdate = hasPermission(auth, 'update-banner')
const canDelete = hasPermission(auth, 'delete-banner')
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Banner'}
action={''}
>
<Head title="Banner" />
<div>
<div className="mx-auto sm:px-6 lg:px-8 ">
<div className="p-6 overflow-hidden shadow-sm sm:rounded-lg bg-gray-200 dark:bg-gray-800 space-y-4">
<div className="flex justify-between">
{canCreate && (
<Link href={route('banner.create')}>
<Button size="sm">Tambah</Button>
</Link>
)}
</div>
<div className="overflow-auto">
<div>
<table className="w-full text-sm text-left text-gray-500 dark:text-gray-400 mb-4">
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th
scope="col"
className="py-3 px-6"
>
Judul
</th>
<th
scope="col"
className="py-3 px-6 w-1/8"
/>
</tr>
</thead>
<tbody>
{data.map((banner) => (
<tr
className="bg-white border-b dark:bg-gray-800 dark:border-gray-700"
key={banner.id}
>
<td
scope="row"
className="py-4 px-6 font-medium text-gray-900 whitespace-nowrap dark:text-white"
>
{banner.title}
</td>
<td className="py-4 px-6 flex justify-end">
<Dropdown
label={'Opsi'}
floatingArrow={true}
arrowIcon={true}
dismissOnClick={true}
size={'sm'}
>
{canUpdate && (
<Dropdown.Item>
<Link
href={route(
'banner.edit',
banner
)}
className="flex space-x-1 items-center"
>
<HiPencil />
<div>
Ubah
</div>
</Link>
</Dropdown.Item>
)}
{canDelete && (
<Dropdown.Item
onClick={() =>
handleDeleteClick(
banner
)
}
>
<div className="flex space-x-1 items-center">
<HiTrash />
<div>
Hapus
</div>
</div>
</Dropdown.Item>
)}
</Dropdown>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="w-full flex items-center justify-center">
<Pagination links={links} />
</div>
</div>
</div>
</div>
</div>
<ModalConfirm modalState={confirmModal} onConfirm={onDelete} />
</AuthenticatedLayout>
)
}

@ -6,7 +6,7 @@ export default function Index({ status }) {
return (
<CustomerLayout>
<Head title="Login" />
<div className="flex flex-col min-h-screen">
<div className="flex flex-col min-h-[calc(95dvh)]">
<div>Login</div>
</div>
</CustomerLayout>

@ -0,0 +1,21 @@
import React from 'react'
import { Head } from '@inertiajs/react'
import CustomerLayout from '@/Layouts/CustomerLayout'
export default function Banner({ banner }) {
return (
<CustomerLayout>
<Head title="Login" />
<div className="flex flex-col min-h-[calc(95dvh)] p-4">
<img
src={banner.image_url}
className="object-cover w-full h-32"
/>
<div className="mt-4 mb-2 text-2xl font-bold">
{banner.title}
</div>
<div dangerouslySetInnerHTML={{ __html: banner.description }} />
</div>
</CustomerLayout>
)
}

@ -1,5 +1,5 @@
import React from 'react'
import { Head, usePage } from '@inertiajs/react'
import { Head, router, usePage } from '@inertiajs/react'
import CustomerLayout from '@/Layouts/CustomerLayout'
import { HiOutlineBell } from 'react-icons/hi2'
@ -29,7 +29,11 @@ const GuestBanner = () => {
)
}
export default function Index({ auth: { user }, infos }) {
export default function Index({ auth: { user }, infos, banners }) {
const handleBanner = (banner) => {
router.get(route('home.banner', banner))
}
return (
<CustomerLayout>
<Head title="Home" />
@ -39,23 +43,16 @@ export default function Index({ auth: { user }, infos }) {
{/* banner */}
<div className="w-full">
<div className="flex flex-row overflow-y-scroll space-x-2 py-3 px-2">
<img
src="/sample/1.webp"
className="rounded w-[80%] min-w-[340px] h-28 object-cover"
/>
<img
src="/sample/banner.jpg"
className="rounded w-[80%] min-w-[340px] h-28 object-cover"
/>
<img
src="/sample/2.webp"
className="rounded w-[80%] min-w-[340px] h-28 object-cover"
/>
<img
src="/sample/3.webp"
className="rounded w-[80%] min-w-[340px] h-28 object-cover"
/>
{banners.map((banner) => (
<img
onClick={() => handleBanner(banner)}
key={banner.id}
src={banner.image_url}
className={`rounded w-${
banners.length === 1 ? 'full' : '[80%]'
} min-w-[340px] h-28 object-cover`}
/>
))}
</div>
</div>

@ -9,7 +9,7 @@ export default function UserBanner({ user }) {
<div className="flex flex-col text-white">
<div className="font-bold">{user.name}</div>
<div className="flex flex-row items-center space-x-1">
<div>+62{user.phone}</div>
<div>{user.display_phone}</div>
<div className="text-xs font-semibold px-2 py-1 bg-white text-black rounded-xl">
{user.level.name}
</div>

@ -42,7 +42,7 @@ export default function Index({ auth: { user } }) {
<div className="flex flex-col text-white">
<div className="font-bold">{user.name}</div>
<div className="flex flex-row items-center space-x-1">
<div>+62{user.phone}</div>
<div>{user.display_phone}</div>
<div className="text-xs font-semibold px-2 py-1 bg-white text-black rounded-xl">
{user.level.name}
</div>

@ -1,6 +1,7 @@
<?php
use App\Http\Controllers\Auth\AuthenticatedSessionController;
use App\Http\Controllers\BannerController;
use App\Http\Controllers\GeneralController;
use App\Http\Controllers\InfoController;
use App\Http\Controllers\ProfileController;
@ -47,5 +48,16 @@ Route::middleware(['http_secure_aware', 'inertia.admin'])
Route::post('/infos', [InfoController::class, 'store'])->name('info.store');
Route::put('/infos/{info}', [InfoController::class, 'update'])->name('info.update');
Route::delete('/infos/{info}', [InfoController::class, 'destroy'])->name('info.destroy');
// upload
Route::post('/upload', [GeneralController::class, 'upload'])->name('post.upload');
// banner
Route::get('/banner', [BannerController::class, 'index'])->name('banner.index');
Route::get('/banner/create', [BannerController::class, 'create'])->name('banner.create');
Route::post('/banner', [BannerController::class, 'store'])->name('banner.store');
Route::get('/banner/{banner}', [BannerController::class, 'edit'])->name('banner.edit');
Route::post('/banner/{banner}', [BannerController::class, 'update'])->name('banner.update');
Route::delete('/banner/{banner}', [BannerController::class, 'destroy'])->name('banner.destroy');
});
});

@ -19,6 +19,8 @@ use Illuminate\Support\Facades\Route;
Route::middleware(['http_secure_aware', 'guard_should_customer', 'inertia.customer'])->group(function () {
Route::get('/', [HomeController::class, 'index'])->name('home.index');
Route::get('/banner/{banner}', [HomeController::class, 'banner'])->name('home.banner');
Route::middleware('auth:customer')->group(function () {
// profile
Route::get('profile', [ProfileController::class, 'index'])->name('customer.profile.index');

Loading…
Cancel
Save