Add all files
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
@props([
|
||||
'options' => [],
|
||||
'placeholder' => 'Select option'
|
||||
])
|
||||
|
||||
<div
|
||||
x-data="{
|
||||
selectOpen: false,
|
||||
selectedValue: @entangle($attributes->wire('model')),
|
||||
selectableItems: {{ json_encode($options) }},
|
||||
selectableItemActive: null,
|
||||
selectId: $id('select'),
|
||||
selectDropdownPosition: 'bottom',
|
||||
|
||||
init() {
|
||||
this.$watch('selectOpen', () => {
|
||||
if (this.selectOpen) {
|
||||
this.selectableItemActive = this.selectableItems.find(item => item.value === this.selectedValue)
|
||||
|| this.selectableItems[0];
|
||||
this.$nextTick(() => this.selectScrollToActiveItem());
|
||||
}
|
||||
this.$nextTick(() => this.selectPositionUpdate());
|
||||
});
|
||||
|
||||
window.addEventListener('resize', () => this.selectPositionUpdate());
|
||||
},
|
||||
|
||||
selectableItemIsActive(item) {
|
||||
return this.selectableItemActive && this.selectableItemActive.value === item.value;
|
||||
},
|
||||
|
||||
selectableItemActiveNext() {
|
||||
let index = this.selectableItems.indexOf(this.selectableItemActive);
|
||||
if (index < this.selectableItems.length - 1) {
|
||||
this.selectableItemActive = this.selectableItems[index + 1];
|
||||
this.selectScrollToActiveItem();
|
||||
}
|
||||
},
|
||||
|
||||
selectableItemActivePrevious() {
|
||||
let index = this.selectableItems.indexOf(this.selectableItemActive);
|
||||
if (index > 0) {
|
||||
this.selectableItemActive = this.selectableItems[index - 1];
|
||||
this.selectScrollToActiveItem();
|
||||
}
|
||||
},
|
||||
|
||||
selectScrollToActiveItem() {
|
||||
if (this.selectableItemActive) {
|
||||
const activeElement = document.getElementById(this.selectableItemActive.value + '-' + this.selectId);
|
||||
if (activeElement) {
|
||||
activeElement.scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
selectPositionUpdate() {
|
||||
if (!this.$refs.selectButton || !this.$refs.selectableItemsList) return;
|
||||
|
||||
const selectDropdownBottomPos = this.$refs.selectButton.getBoundingClientRect().top +
|
||||
this.$refs.selectButton.offsetHeight +
|
||||
this.$refs.selectableItemsList.offsetHeight;
|
||||
|
||||
this.selectDropdownPosition = window.innerHeight < selectDropdownBottomPos ? 'top' : 'bottom';
|
||||
}
|
||||
}"
|
||||
@keydown.escape="selectOpen = false"
|
||||
@keydown.down.prevent="if(selectOpen) { selectableItemActiveNext() } else { selectOpen = true }"
|
||||
@keydown.up.prevent="if(selectOpen) { selectableItemActivePrevious() } else { selectOpen = true }"
|
||||
@keydown.enter.prevent="selectedValue = selectableItemActive.value; selectOpen = false"
|
||||
class="relative w-full"
|
||||
>
|
||||
<button
|
||||
x-ref="selectButton"
|
||||
@click="selectOpen = !selectOpen"
|
||||
:class="{ 'ring-2 ring-offset-2 ring-neutral-800': selectOpen }"
|
||||
class="relative w-full min-h-[38px] py-2 pl-3 pr-10 text-left bg-background-secondary border border-neutral rounded-md shadow-sm cursor-pointer text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-800"
|
||||
type="button"
|
||||
>
|
||||
<span x-text="selectableItems.find(item => item.value === selectedValue)?.label ?? '{{ $placeholder }}'" class="block truncate"></span>
|
||||
<span class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
|
||||
<x-ri-expand-up-down-line class="size-4" />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<ul
|
||||
x-show="selectOpen"
|
||||
x-ref="selectableItemsList"
|
||||
@click.away="selectOpen = false"
|
||||
x-transition:enter="transition ease-out duration-100"
|
||||
x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100"
|
||||
:class="{ 'bottom-full mb-1': selectDropdownPosition === 'top', 'top-full mt-1': selectDropdownPosition === 'bottom' }"
|
||||
class="absolute z-50 w-full py-1 overflow-auto bg-background-secondary border border-neutral rounded-lg shadow-lg max-h-60 focus:outline-none text-sm"
|
||||
x-cloak
|
||||
>
|
||||
<template x-for="item in selectableItems" :key="item.value">
|
||||
<li
|
||||
:id="item.value + '-' + selectId"
|
||||
@click="selectedValue = item.value; selectOpen = false"
|
||||
@mousemove="selectableItemActive = item"
|
||||
:class="{ 'bg-neutral-700': selectableItemIsActive(item) }"
|
||||
class="relative py-2 pl-8 pr-4 cursor-pointer select-none hover:bg-neutral-700"
|
||||
>
|
||||
<span
|
||||
class="block truncate"
|
||||
:class="{ 'font-semibold': item.value === selectedValue }"
|
||||
x-text="item.label"
|
||||
></span>
|
||||
<span
|
||||
x-show="item.value === selectedValue"
|
||||
class="absolute inset-y-0 left-0 flex items-center pl-2 text-neutral-400"
|
||||
>
|
||||
<x-ri-check-fill class="size-4" />
|
||||
</span>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
47
Disabled/ProductPageBuilder/resources/views/index.blade.php
Normal file
47
Disabled/ProductPageBuilder/resources/views/index.blade.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="title">Games</x-slot>
|
||||
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
@foreach($pages as $page)
|
||||
<div class="group relative bg-background-secondary hover:bg-background-secondary/80 border border-neutral rounded-lg overflow-hidden transition-all duration-300 hover:shadow-xl p-4">
|
||||
@if(isset($page->hero_section['background']))
|
||||
<div class="w-full h-36 overflow-hidden rounded-lg mb-4">
|
||||
<img src="{{ Storage::url($page->hero_section['background']) }}"
|
||||
alt="{{ $page->hero_section['title'] }}"
|
||||
class="w-full h-full object-cover">
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="relative">
|
||||
<h2 class="text-2xl font-bold text-base mb-4">{{ $page->hero_section['title'] }}</h2>
|
||||
@if(isset($page->hero_section['description']))
|
||||
<div class="prose dark:prose-invert line-clamp-2 mb-4">
|
||||
{!! strip_tags($page->hero_section['description']) !!}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="flex justify-between items-start mb-4">
|
||||
@if($page->min_price_details)
|
||||
<div class="text-base text-white font-semibold">
|
||||
@php
|
||||
$currency = \App\Models\Currency::firstWhere('code', $page->min_price_details['currency_code']);
|
||||
$formattedPrice = $currency ?
|
||||
($currency->prefix ?? '') . number_format($page->min_price_details['price'], 2) . ($currency->suffix ?? '') :
|
||||
number_format($page->min_price_details['price'], 2);
|
||||
@endphp
|
||||
Starting from {{ $formattedPrice }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex items-center justify-end">
|
||||
<a href="{{ route('product-page.show', $page->hero_section['slug']) }}" wire:navigate class="flex items-center gap-2 justify-center bg-primary text-white text-sm font-semibold hover:bg-primary/80 py-2.5 lg:py-2 px-4.5 rounded-md w-full duration-300 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50">
|
||||
View Details
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</x-app-layout>
|
||||
273
Disabled/ProductPageBuilder/resources/views/show.blade.php
Normal file
273
Disabled/ProductPageBuilder/resources/views/show.blade.php
Normal file
@@ -0,0 +1,273 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="title">{{ $page->seo['title'] ?? $page->hero_section['title'] }}</x-slot>
|
||||
<x-slot name="description">{!! $page->seo['description'] ?? strip_tags($page->hero_section['description']) !!}</x-slot>
|
||||
{{--
|
||||
<x-slot name="keywords">{{ is_array($page->seo['keywords'] ?? null) ? implode(', ', $page->seo['keywords']) : ($page->seo['keywords'] ?? '') }}</x-slot>
|
||||
--}}
|
||||
@if(isset($page->seo['banner']))
|
||||
<x-slot name="image">{{ Storage::url($page->seo['banner']) }}</x-slot>
|
||||
@endif
|
||||
|
||||
<!-- Main content -->
|
||||
@if(isset($page->hero_section))
|
||||
<div class="relative bg-background-secondary hover:bg-background-secondary/80 border border-neutral p-4 rounded-lg flex items-center overflow-hidden">
|
||||
@if(isset($page->hero_section['background']))
|
||||
<div class="absolute inset-0 transform">
|
||||
<img src="{{ Storage::url($page->hero_section['background']) }}"
|
||||
alt="{{ $page->hero_section['title'] }}"
|
||||
class="h-full w-full object-cover">
|
||||
<div class="absolute inset-0 bg-background-secondary/50 dark:bg-background-secondary/60"></div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="relative w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 lg:py-28">
|
||||
<h1 class="text-4xl lg:text-5xl font-extrabold text-base mb-4 leading-tight">
|
||||
{{ $page->hero_section['title'] }}
|
||||
</h1>
|
||||
<div class="text-xl prose dark:prose-invert max-w-5xl">
|
||||
{!! $page->hero_section['description'] !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($page->category)
|
||||
<div class="py-24">
|
||||
<div class="grid gap-8 md:grid-cols-2 lg:grid-cols-4">
|
||||
@foreach($page->category->products()->with(['plans.prices'])->get() as $product)
|
||||
@php
|
||||
$lowestPlan = $product->plans
|
||||
->flatMap(function($plan) {
|
||||
return $plan->prices->map(function($price) use ($plan) {
|
||||
return ['plan' => $plan, 'price' => $price];
|
||||
});
|
||||
})
|
||||
->sortBy('price.price')
|
||||
->first();
|
||||
@endphp
|
||||
<div class="bg-background-secondary hover:bg-background-secondary/80 border border-neutral p-4 lg:p-6 rounded-lg overflow-hidden transition-all duration-300 hover:shadow-xl">
|
||||
{{-- Prdocut Image Banner
|
||||
@if($product->image)
|
||||
<div class="mb-4 rounded-lg overflow-hidden">
|
||||
<img src="{{ Storage::url($product->image) }}"
|
||||
alt="{{ $product->name }}"
|
||||
class="w-full h-48 object-cover">
|
||||
</div>
|
||||
@endif
|
||||
--}}
|
||||
{{-- Product Image Icon --}}
|
||||
@if($product->image)
|
||||
<div class="mb-4 rounded-lg overflow-hidden">
|
||||
<img src="{{ Storage::url($product->image) }}"
|
||||
alt="{{ $product->name }}"
|
||||
class="w-full h-24 object-contain">
|
||||
</div>
|
||||
@endif
|
||||
<h3 class="text-3xl font-bold text-base my-4 text-center">
|
||||
{{ $product->name }}
|
||||
</h3>
|
||||
<div class="prose dark:prose-invert text-base mb-4">{!! $product->description !!}</div>
|
||||
@if($lowestPlan)
|
||||
@php
|
||||
$currency = \App\Models\Currency::firstWhere('code', $lowestPlan['price']->currency_code);
|
||||
$formattedPrice = $currency ?
|
||||
($currency->prefix ?? '') . number_format($lowestPlan['price']->price, 2) . ($currency->suffix ?? '') :
|
||||
number_format($lowestPlan['price']->price, 2);
|
||||
@endphp
|
||||
<div class="prose dark:prose-invert mb-4 text-center flex items-center gap-2 justify-center">
|
||||
<span class="font-medium text-lg">Starting from</span>
|
||||
<span class="text-base text-2xl font-bold">{{ $formattedPrice }}</span>
|
||||
</div>
|
||||
@endif
|
||||
<a href="{{ route('products.show', ['category' => $page->category->slug, 'product' => $product->slug]) }}"
|
||||
wire:navigate
|
||||
class="flex items-center gap-2 justify-center bg-primary text-base text-sm font-semibold hover:bg-primary/80 py-2.5 lg:py-2 px-4.5 rounded-md w-full duration-300 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50">
|
||||
{{ __('general.view') }}
|
||||
</a>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@php
|
||||
$activeFeatures = collect($page->features ?? [])->filter(fn($feature) => $feature['is_active'] ?? true);
|
||||
@endphp
|
||||
|
||||
@if($activeFeatures->isNotEmpty())
|
||||
<div class="py-12 lg:py-24">
|
||||
<h2 class="text-3xl font-bold text-center text-base mb-12">Features</h2>
|
||||
<div class="grid grid-cols-1 gap-12 lg:grid-cols-3">
|
||||
@foreach($activeFeatures as $feature)
|
||||
<div class="relative group">
|
||||
<div class="relative bg-background-secondary hover:bg-background-secondary/80 border border-neutral p-4 lg:p-6 rounded-lg shadow-xl">
|
||||
@if(!empty($feature['icon_svg']))
|
||||
<div class="h-12 w-12 mb-4 flex items-center justify-center">
|
||||
{!! $feature['icon_svg'] !!}
|
||||
</div>
|
||||
@elseif(isset($feature['icon']))
|
||||
<div class="h-12 w-12 mb-4">
|
||||
<img src="{{ Storage::url($feature['icon']) }}"
|
||||
alt="{{ $feature['title'] }}"
|
||||
class="h-full w-full object-contain">
|
||||
</div>
|
||||
@endif
|
||||
<h3 class="text-xl font-semibold text-base mb-3">{{ $feature['title'] }}</h3>
|
||||
<div class="prose prose dark:prose-invert">{!! $feature['description'] !!}</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
|
||||
@if(!empty($page->game_preview))
|
||||
@foreach($page->game_preview as $preview)
|
||||
@if($preview['is_active'] ?? true)
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 items-center py-12 lg:py-24">
|
||||
<div class="max-w-xl me-auto lg:text-left text-center">
|
||||
<h3 class="text-3xl font-bold text-base mb-6">{{ $preview['title'] }}</h3>
|
||||
<div class="prose dark:prose-invert text-base">{!! $preview['description'] !!}</div>
|
||||
</div>
|
||||
@if(isset($preview['youtube_embed']))
|
||||
<div class="h-48 lg:h-96 rounded-lg overflow-hidden">
|
||||
<iframe src="{{ $preview['youtube_embed'] }}"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen
|
||||
class="w-full h-full"></iframe>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
@endif
|
||||
|
||||
@php
|
||||
$activeGamePanels = collect($page->game_panel ?? [])->filter(fn($panel) => $panel['is_active'] ?? true);
|
||||
$activeGamePreviews = collect($page->game_preview ?? [])->filter(fn($preview) => $preview['is_active'] ?? true);
|
||||
$swapOrder = $activeGamePanels->isNotEmpty() && $activeGamePreviews->isNotEmpty();
|
||||
@endphp
|
||||
|
||||
@if(!empty($page->game_panel))
|
||||
@foreach($page->game_panel as $panel)
|
||||
@if($panel['is_active'] ?? true)
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 items-center py-12 lg:py-24">
|
||||
<div class="max-w-xl ms-auto lg:text-left text-center {{ $swapOrder ? 'order-2' : 'order-1' }}">
|
||||
<h3 class="text-3xl font-bold text-base mb-6">{{ $panel['title'] }}</h3>
|
||||
<div class="prose dark:prose-invert text-base">{!! $panel['description'] !!}</div>
|
||||
@php
|
||||
$features = is_array($panel['features'] ?? null)
|
||||
? $panel['features']
|
||||
: (empty($panel['features']) ? [] : explode(',', $panel['features']));
|
||||
@endphp
|
||||
@if(!empty($features))
|
||||
<div class="flex flex-wrap justify-center lg:justify-start gap-2 my-4">
|
||||
@foreach($features as $feature)
|
||||
<span class="flex items-center gap-1 bg-background-secondary border border-neutral text-base px-3 py-1 rounded-lg text-sm font-semibold">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" class="w-4 h-4 text-primary">
|
||||
<path d="M9.9997 15.1709L19.1921 5.97852L20.6063 7.39273L9.9997 17.9993L3.63574 11.6354L5.04996 10.2212L9.9997 15.1709Z"></path>
|
||||
</svg>
|
||||
{{ trim($feature) }}
|
||||
</span>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@if(!empty($panel['gallery']))
|
||||
<div
|
||||
x-data="{
|
||||
slides: {{ json_encode($panel['gallery']) }},
|
||||
current: 0,
|
||||
goTo(idx) { this.current = idx },
|
||||
next() { this.current = (this.current + 1) % this.slides.length },
|
||||
prev() { this.current = (this.current - 1 + this.slides.length) % this.slides.length }
|
||||
}"
|
||||
class="relative rounded-lg overflow-hidden shadow-lg"
|
||||
>
|
||||
<div class="relative h-48 md:h-96">
|
||||
<template x-for="(image, idx) in slides" :key="idx">
|
||||
<div
|
||||
x-show="current === idx"
|
||||
x-transition:enter="transition-opacity duration-700"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition-opacity duration-700"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
class="absolute inset-0"
|
||||
>
|
||||
<img :src="'{{ Storage::url('') }}' + image" class="object-cover w-full h-full" alt="Slide" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- Slider indicators -->
|
||||
<div class="flex absolute bottom-5 left-1/2 z-30 -translate-x-1/2 space-x-2">
|
||||
<template x-for="(image, idx) in slides" :key="idx">
|
||||
<button
|
||||
type="button"
|
||||
:class="{'bg-primary': current === idx, 'bg-gray-300': current !== idx}"
|
||||
class="w-4 h-1 rounded-lg hover:bg-gray-400 focus:outline-none transition"
|
||||
@click="goTo(idx)"
|
||||
></button>
|
||||
</template>
|
||||
</div>
|
||||
<!-- Slider controls -->
|
||||
<button type="button"
|
||||
class="flex absolute top-1/2 -translate-y-1/2 left-3 z-40 items-center justify-center w-8 h-8 bg-gray-200/50 rounded-full hover:bg-gray-300 focus:outline-none transition cursor-pointer"
|
||||
@click="prev"
|
||||
>
|
||||
<svg class="w-4 h-4 text-gray-800" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M15 19l-7-7 7-7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button"
|
||||
class="flex absolute top-1/2 -translate-y-1/2 right-3 z-40 items-center justify-center w-8 h-8 bg-gray-200/50 rounded-full hover:bg-gray-300 focus:outline-none transition cursor-pointer"
|
||||
@click="next"
|
||||
>
|
||||
<svg class="w-4 h-4 text-gray-800" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
@endif
|
||||
|
||||
@php
|
||||
$activeFaqs = collect($page->faqs ?? [])->filter(fn($faq) => $faq['is_active'] ?? true);
|
||||
@endphp
|
||||
|
||||
@if($activeFaqs->isNotEmpty())
|
||||
<div class="max-w-7xl mx-auto py-12 lg:py-24">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h2 class="text-3xl font-bold text-center text-base mb-12">Frequently Asked Questions</h2>
|
||||
<div class="space-y-8">
|
||||
@foreach($activeFaqs as $faq)
|
||||
<div class="bg-background-secondary hover:bg-background-secondary/80 border border-neutral p-4 rounded-lg" x-data="{ open: false }">
|
||||
<dt>
|
||||
<button @click="open = !open" class="w-full flex justify-between items-center text-left cursor-pointer">
|
||||
<span class="text-base lg:text-lg font-semibold text-base">{{ $faq['question'] }}</span>
|
||||
<span class="ml-6 flex-shrink-0">
|
||||
<svg class="h-4 w-4 transform transition-transform duration-200" :class="{'rotate-180': open}" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</dt>
|
||||
<dd class="mt-4" x-show="open" x-transition>
|
||||
<div class="prose prose-sm max-w-none prose dark:prose-invert">
|
||||
{!! $faq['answer'] !!}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</x-app-layout>
|
||||
Reference in New Issue
Block a user