diff --git a/app/app/Http/Pages/Host/Properties/NewProperty.php b/app/app/Http/Pages/Host/Properties/NewProperty.php
index 84273d8..06dc1f4 100644
--- a/app/app/Http/Pages/Host/Properties/NewProperty.php
+++ b/app/app/Http/Pages/Host/Properties/NewProperty.php
@@ -5,6 +5,7 @@
use App\Livewire\Forms\HostPropertyForm;
use App\Models\Amenity;
use App\Models\AmenityGroup;
+use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Collection;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Validate;
@@ -18,204 +19,152 @@ class NewProperty extends Component
use WireToast, WithFileUploads;
public HostPropertyForm $form;
- public Collection $all_amenities;
- public $selected_amenities;
+ public $all_amenities;
+
+ #[Validate([
+ 'selected_amenities' => 'nullable|array',
+ 'selected_amenities.*' => 'required|integer|min:0|max:9999',
+ ])]
+ public $selected_amenities = [];
+
+ #[Validate([
+ 'temp_photos' => 'nullable|array',
+ 'temp_photos.*' => 'image|mimes:jpg,jpeg,png,webp,bmp|extensions:jpg,jpeg,png,webp,bmp|max:10240'
+ ], as: [
+ 'temp_photos' => 'selected photos',
+ 'temp_photos.*' => 'selected photo',
+ ])]
public $temp_photos;
- // protected function rules()
- // {
- // return [
- // 'property.name' => ['required', 'string', 'max:60', 'unique:properties.name'],
- // 'property.address.line_1' => ['required', 'string', 'max:250'],
- // 'property.address.line_2' => ['nullable', 'string', 'max:250'],
- // 'property.address.city' => ['required', 'string', 'max:250'],
- // 'property.address.state' => ['required', 'string', 'alpha', 'size:2'],
- // 'property.address.postal' => ['required', 'string', 'numeric', 'digits:5', 'regex:/^\d{5}$/'],
- // 'property.address.country' => ['required', 'string', 'alpha', 'size:2'],
- // // Listing
- // 'property.listing.type' => ['required'],
- // 'property.listing.headline' => ['required', 'string', 'max:250'],
- // 'property.listing.description' => ['nullable', 'string', 'max:3000'],
- // 'property.listing.guests' => ['required', 'numeric', 'min:1', 'max:16'],
- // 'property.listing.beds' => ['required', 'numeric', 'min:0', 'max:99'],
- // 'property.listing.bedrooms' => ['required', 'numeric', 'min:0', 'max:99'],
- // 'property.listing.bathrooms' => ['required', 'numeric', 'min:0', 'max:99'],
- // 'property.listing.amenities' => ['required'],
- // 'property.listing.amenities.*' => [],
- // // Pricing
- // 'property.rates.base' => ['required', 'numeric'],
- // 'property.rates.tax' => ['required', 'numeric'],
- // 'property.rates.fees' => [''],
- // 'property.rates.fees.*' => [''],
- // 'property.rates.fees.*.name' => ['required', 'string', 'max:250'],
- // 'property.rates.fees.*.amount' => ['required', 'numeric'],
- // 'property.rates.fees.*.type' => ['required', 'string'],
- // 'property.photos' => ['required'],
- // 'property.photos.*' => ['image', 'mimes:jpg,jpeg,png,webp', 'extensions:jpg,jpeg,png,webp', 'max:6144'],
- // 'temp_photos.*' => ['image', 'mimes:jpg,jpeg,png,webp', 'extensions:jpg,jpeg,png,webp', 'max:6144'],
- // 'property.options.visibility' => ['required'],
- // 'property.options.min-nights' => ['required', 'numeric', 'min:1', 'max:30'],
- // 'property.options.max-nights' => ['required', 'numeric', 'min:1', 'max:30'],
- // 'property.options.slug' => ['required'],
- // 'property.options.color' => ['required'],
- // ];
- // }
-
- // protected function messages()
- // {
- // return [
- // 'property.rates.base.required' => 'Base rate required',
- // 'property.rates.tax.required' => 'Tax rate required',
- // ];
- // }
-
- // protected function validationAttributes()
- // {
- // return [
- // 'property.name' => 'Property Name',
- // 'property.address.line_1' => 'Address Line 1',
- // 'property.address.line_2' => 'Address Line 2',
- // 'property.address.city' => 'City',
- // 'property.address.state' => 'State',
- // 'property.address.postal' => 'ZIP Code',
- // 'property.address.country' => 'Country',
- // 'property.listing.headline' => 'Listing Headline',
- // 'property.listing.description' => 'Listing Description',
-
- // 'property.listing.guests' => 'number of guests',
- // 'property.listing.beds' => 'number of beds',
- // 'property.listing.bedrooms' => 'number of bedrooms',
- // 'property.listing.bathrooms' => 'number of bathrooms',
-
- // 'property.listing.amenities' => 'amenities',
- // 'property.listing.amenities.*' => 'amenity',
- // 'property.rates.base' => 'base rate',
- // 'property.rates.tax' => 'tax rate',
- // 'property.rates.fees' => 'fees',
- // 'property.rates.fees.*' => 'fee',
- // 'property.rates.fees.*.name' => 'fee name',
- // 'property.rates.fees.*.amount' => 'fee amount',
- // 'property.rates.fees.*.type' => 'fee type',
- // 'property.photos' => 'photos',
- // 'property.photos.*' => 'photo',
- // 'temp_photos.*' => 'photo',
- // 'property.options.visibility' => 'visibility',
- // 'property.options.min-nights' => 'minimum nights',
- // 'property.options.max-nights' => 'maximum nights',
- // ];
- // }
-
-
- public function mount(): void
- {
- // Need to init this so the char counter will work.
- // WHAT?
- }
-
- public function render()
+ /**
+ * Runs on page load
+ *
+ * @return View
+ */
+ function render(): View
{
return view('pages.host.properties.new-property');
}
- public function load(): void
+ /**
+ * Runs on page load, after page rendered
+ *
+ * @return void
+ */
+ function load(): void
{
-
- $this->loadAmenities();
- // $this->property = [
- // 'name' => '',
- // 'address' => [
- // 'street_1' => '',
- // 'street_2' => '',
- // 'city' => '',
- // 'state' => '',
- // 'postal' => '',
- // 'country' => '',
- // ],
- // 'listing' => [
- // 'headline' => '',
- // 'description' => '',
- // 'guests' => 1,
- // 'beds' => 0,
- // 'bedrooms' => 0,
- // 'bathrooms' => 0,
- // 'amenities' => [],
- // ],
- // 'rates' => [
- // 'base' => '',
- // 'tax' => '',
- // 'fees' => [],
- // ],
- // 'photos' => [],
- // 'options' => [
- // 'duration-min' => 1,
- // 'duration-max' => 14,
- // 'visibility' => 'private',
- // ],
- // ];
+ //
}
- public function reload(): void
+ /**
+ * Runs when user clicks the "reset" form button at the bottom of the page.
+ * Resets the form and closes the reset confirmation modal
+ *
+ * @return void
+ */
+ function reload(): void
{
- $this->property = [];
- $this->load();
- $this->modal('reset-modal')->close();
- }
+ // Reset the entire form
+ $this->form->reset();
+ // Close the reset modal
+ $this->modal('reset-confirm-modal')->close();
+ }
/**
- * Amenities
+ * Open Amenities Modal
+ * Runs when the user attempts to open the amenities modal
+ *
+ * - Pulls all amenity groups with associated amenenites
+ * - Clears selected amenities
+ * - Loops through each existing amenities, adds to the selected amenities
+ * -
+ *
+ * @return void
*/
- public function loadAmenities(): void
+ function openAmenitiesModal(): void
{
+ // Loading the amenity groups with associated amenities from database
$this->all_amenities = AmenityGroup::with('amenities')->get();
- $this->form->amenities = [];
- }
- public function openAmenitiesModal(): void
- {
- // Loading the amenities if not already
- if (!$this->all_amenities) {
- $this->loadAmenities();
- }
+ // Reset the selected amenities
$this->selected_amenities = [];
+ // Add existing selected amenities to the
foreach ($this->form->amenities as $amenity) {
$this->selected_amenities[] = $amenity->id;
}
+ if ($this->selected_amenities) {
+ dd($this->selected_amenities);
+ }
+
+
+ // Close amenities modal
$this->modal('amenities-modal')->show();
}
/**
- * ! asdf
+ * Save Amenities Changes
+ *
+ * Runs when the user presses the "save changes" button on the amenities modal
+ *
+ *
+ * @return void
*/
- public function updateAmenities(): void
+ public function saveAmenityChanges(): void
{
+ // Clear out existing amenities
$this->form->reset('amenities');
+ // Set variable for flatMap
$selected_amenities = $this->selected_amenities;
- $this->form->amenities = $this->all_amenities->flatMap(function ($amenity_group) use ($selected_amenities) {
+
+ // Validate amenities
+ $this->validateOnly('selected_amenities');
+
+ // Filters through the amenities groups, then each amenity, checks if amenity id exists
+ $new_amenities = $this->all_amenities->flatMap(function ($amenity_group) use ($selected_amenities) {
return $amenity_group->amenities->filter(function ($amenity) use ($selected_amenities) {
return in_array($amenity->id, $selected_amenities);
});
});
- $this->validateOnly('amenities');
+ // Adds each amenity to the form
+ foreach ($new_amenities as $new_amenity) {
+ $this->form->amenities[] = $new_amenity;
+ }
+ // Closes amenities modal
$this->modal('amenities-modal')->close();
}
- public function removeAmenity($amenity_id): void
+ /**
+ * Remove Amenity
+ * Removes an amenity from the existing amenities.
+ *
+ * @param string $amenity_id
+ * @return void
+ */
+ public function removeAmenity(string $amenity_id): void
{
- $this->form->amenities = $this->form->amenities->filter(function ($amenity) use ($amenity_id) {
+ $new_amenities = collect($this->form->amenities)->filter(function ($amenity) use ($amenity_id) {
return $amenity->id != $amenity_id;
});
+
+ $this->form->reset('amenities');
+
+ foreach ($new_amenities as $amenity) {
+ $this->form->amenities[] = $amenity;
+ }
}
/**
- * Rates & Fees
+ * Add new fee
+ * Adds a new fee to the fees array
+ *
+ * @return void
*/
-
public function addFee(): void
{
array_push($this->form->fees, [
@@ -225,56 +174,74 @@ public function addFee(): void
]);
}
- public function removeFee($key): void
+ /**
+ * Remove fee
+ * Removes fee by fee array key from the fees array
+ *
+ * @param integer $fee_key - The array key of the fee the user wants removed
+ * @return void
+ */
+ public function removeFee(int $fee_key): void
{
- unset($this->form->fees[$key]);
+ unset($this->form->fees[$fee_key]);
}
/**
* Photos
- *
* When the user uploads photos, $temp_photos are cleared and only shows new photos.
* This takes all photos from $temp_photos and adds them to $photos so when the user
* uploads more photos, the previous photos are not cleared.
*
+ * @param array $temp_photos - an array of photos uploaded via livewire not uploaded yet
* @return void
*/
function updatedTempPhotos(): void
{
+ // Validate temp photos
$this->validateOnly('temp_photos.*');
- foreach ($this->temp_photos as $temp_photo) {
- $this->property['photos'][] = $temp_photo;
+ // Loop through each temp photo, adding it to the form
+ foreach ($this->temp_photos as $selected_photo) {
+ $this->form->photos[] = $selected_photo;
}
+ // Clear temp photos
+ // This is primarily to remove the "# selected files" from the file input
$this->temp_photos = [];
}
/**
- * Runs when the user deletes a specific photo from the photo's grid
+ * Runs when the user deletes a specific photo from the photo's grid, then re-indexs
+ * the array
*
- * @param int $key The array key of the photo the user wants to delete
+ * @param int $photos_array_key - The array key of the photo the user wants to remove
* @return void
*/
- public function deletePhoto($key): void
+ public function removePhoto($photos_array_key): void
{
- // remove the photo
- unset($this->property['photos'][$key]);
+ // Unset the photo from the form's photo array
+ unset($this->form->photos[$photos_array_key]);
- // reset the array keys to
- $this->property['photos'] = array_values($this->property['photos']);
+ // Re-index the array
+ $this->form->photos = array_values($this->form->photos);
+ // Why? When you have an array [1,2,3,4] and you delete key 3, the array
+ // will now look like [1,2,4], then you can re-index so it looks like [1,2,3]
}
/**
* Runs when the user reorders photos.
+ * Most of the magic happens with wire:sortable, but this rearranges the photos based on
+ * a new order specified in the $photo_order_data, given by wire:sortable.
+ *
+ * https://github.com/livewire/sortable
*
- * @param array $data
+ * @param array $photo_order_data -
* @return void
*/
- function updatePhotoOrder(array $data): void
+ function updatePhotoOrder(array $photo_order_data): void
{
- $this->property['photos'] = array_map(fn($item) => $this->property['photos'][$item['value']], $data);
- toast()->debug('test')->push();
+ // Reorder photos based on the order given in $photo_order_data
+ $this->form->photos = array_map(fn($item) => $this->form->photos[$item['value']], $photo_order_data);
}
/**
@@ -282,8 +249,8 @@ function updatePhotoOrder(array $data): void
*/
public function submit(): void
{
+ $this->dispatch('console', $this->form);
+ // Validate the form
$this->validate();
-
- dd($this->property);
}
}
diff --git a/app/app/Http/Test.php b/app/app/Http/Test.php
new file mode 100644
index 0000000..de7047b
--- /dev/null
+++ b/app/app/Http/Test.php
@@ -0,0 +1,19 @@
+ 'Add Property'])]
+class Test extends Component
+{
+
+ #[Validate('required|string|numeric|digits:5|regex:/^\d{5}$/', as: 'zip / postal code')]
+ public ?int $address_postal;
+
+ public function render()
+ {
+ return view('test');
+ }
+}
diff --git a/app/app/Livewire/Forms/HostPropertyForm.php b/app/app/Livewire/Forms/HostPropertyForm.php
index 5c3c301..d0d395b 100644
--- a/app/app/Livewire/Forms/HostPropertyForm.php
+++ b/app/app/Livewire/Forms/HostPropertyForm.php
@@ -10,93 +10,103 @@ class HostPropertyForm extends Form
{
// Basic
#[Validate('required|string|max:250|unique:properties,name', as: 'property name')]
- public $name;
+ public ?string $name;
#[Validate('required|string|max:250', as: 'street address')]
- public $address_line1;
+ public ?string $address_line1;
#[Validate('nullable|string|max:250', as: 'address line 2')]
- public $address_line2;
+ public ?string $address_line2;
#[Validate('required|string|max:250', as: 'city')]
- public $address_city;
+ public ?string $address_city;
#[Validate('required|string|alpha|size:2', as: 'state')]
- public $address_state;
+ public ?string $address_state;
- #[Validate('required|string|numeric|digits:5|regex:/^\d{5}$/', as: 'zip / postal code', onUpdate: false)]
- public $address_postal;
+ #[Validate('required|string|numeric|digits:5|regex:/^\d{5}$/', as: 'zip / postal code')]
+ public ?int $address_postal;
#[Validate('required|string|alpha|size:2', as: 'country')]
- public $address_country;
+ public ?string $address_country;
// Listing
#[Validate('required|string|max:250', as: 'headline')]
- public $listing_headline;
+ public ?string $listing_headline;
#[Validate('required|string|max:3000', as: 'description')]
- public $listing_description;
+ public string $listing_description = "";
#[Validate('required|integer|numeric|min:1|max:16', as: 'guest count')]
- public $guest_count;
+ public int $guest_count = 1;
#[Validate('required|integer|numeric|min:0|max:99', as: 'bed count')]
- public $bed_count;
+ public int $bed_count = 0;
#[Validate('required|integer|numeric|min:0|max:99', as: 'bedroom count')]
- public $bedroom_count;
+ public int $bedroom_count = 0;
#[Validate('required|decimal:0,1|numeric|min:0|max:99', as: 'bathroom count')]
- public $bathroom_count;
+ public int $bathroom_count = 0;
- #[Validate('required|string', as: 'property type')]
- public $property_type;
+ #[Validate('required|integer|exists:App\Models\PropertyType,id', as: 'property type')]
+ public ?int $property_type;
// #[Validate('required|array', as: 'amenities')]
#[Validate([
'amenities' => 'required|array',
- 'amenities.*' => 'required|integer'
+ 'amenities.*' => 'required'
], as: [
'amenities' => 'amenities',
- 'amenities.*' => 'amenities',
+ 'amenities.*' => 'amenity item',
], message: [
'amenities.required' => 'Amenities are required.',
])]
- public $amenities;
+ public array $amenities = [];
// Rates
#[Validate('required|numeric|min:1|max:1000|decimal:0,2', as: 'base rate', onUpdate: false)]
- public $base_rate;
+ public ?int $base_rate;
#[Validate('required|numeric|min:0|max:99', as: 'tax rate', onUpdate: false)]
- public $tax_rate;
+ public ?int $tax_rate;
#[Validate([
- 'fees' => '',
+ 'fees' => 'nullable|array:name,amount,type',
'fees.*.name' => 'required|string|max:250',
'fees.*.amount' => 'required|numeric|min:0|max:1000|decimal:0,2',
- 'fees.*.type' => 'required|',
+ 'fees.*.type' => 'required|in:fixed,variable',
+ ], as: [
+ 'fees.*.name' => 'fee name',
+ 'fees.*.amount' => 'fee amount',
+ 'fees.*.type' => 'fee type',
])]
- public array $fees;
+ public array $fees = [];
// Photos
- #[Validate('required|', as: '')]
- public $selected_photos;
+ #[Validate([
+ 'photos' => 'required',
+ 'photos.*' => 'required|image|mimes:jpg,jpeg,png,webp,bmp|extensions:jpg,jpeg,png,webp,bmp|max:10240',
+ ], as: [
+ 'photos.' => 'photos',
+ 'photos.*' => 'photo',
+ ])]
+ public array $photos = [];
// Options
- #[Validate('required|', as: '')]
- public $duration_min;
+ #[Validate('required|string|max:250|unique:properties,slug|regex:^[a-z0-9]+(?:-[a-z0-9]+)*$', as: 'url', onUpdate: false)]
+ public ?string $slug;
- #[Validate('required|', as: '')]
- public $duration_max;
+ #[Validate('required|integer|min:1|max:99', as: 'minimum nights')]
+ public int $duration_min = 1;
- #[Validate('required|', as: '')]
- public $slug;
+ #[Validate('required|integer|min:1|max:99', as: 'maximum nights')]
+ public int $duration_max = 14;
- #[Validate('required|', as: '')]
- public $visibility;
+ #[Validate('required|string|in:private,unlisted,public', as: 'visibility')]
+ public ?string $visibility;
- #[Validate('required|', as: '')]
- public $calendar_color;
+ #[Validate('required|regex:/^#[0-9A-Fa-f]{6}$/', as: 'color')]
+ public string $calendar_color = "#2563eb";
}
diff --git a/app/resources/css/app.css b/app/resources/css/app.css
index fe1f621..23f12c0 100644
--- a/app/resources/css/app.css
+++ b/app/resources/css/app.css
@@ -65,16 +65,16 @@
@apply -page-x-padding -page-y-padding;
}
.page-x-padding {
- @apply px-4 tablet:px-6;
+ @apply px-4 tablet-lg:px-6;
}
.-page-x-padding {
- @apply -mx-4 tablet:-mx-6;
+ @apply -mx-4 tablet-lg:-mx-6;
}
.page-y-padding {
- @apply py-4 tablet:py-6;
+ @apply py-4 tablet-lg:py-6;
}
.-page-y-padding {
- @apply -my-4 tablet:-my-6;
+ @apply -my-4 tablet-lg:-my-6;
}
.page-container {
@@ -84,7 +84,7 @@
@apply page-padding mb-8 border-b border-gray-300 bg-white;
}
.page-title {
- @apply text-2xl font-bold text-gray-700 tablet:font-bold;
+ @apply text-2xl font-bold text-gray-700 tablet-lg:font-bold;
}
.page-desc {
@apply mt-2 text-muted;
@@ -101,20 +101,20 @@
/* Cards */
/* .card-container {
- @apply mx-0 w-full tablet-sm:mx-6;
+ @apply mx-0 w-full tablet:mx-6;
} */
.card {
- @apply has-border bg-white tablet-sm:rounded-xl;
+ @apply has-border bg-white tablet:rounded-xl;
}
.card-padding {
- /* @apply px-6 py-8 tablet-sm:px-8; */
+ /* @apply px-6 py-8 tablet:px-8; */
@apply page-padding;
}
.card-flex {
@apply flex flex-col space-y-8;
}
/* .card-padding-sm {
- @apply px-6 py-6 tablet-sm:px-8;
+ @apply px-6 py-6 tablet:px-8;
} */
.card-header {
@apply flex flex-col space-y-2;
@@ -147,7 +147,7 @@
@apply grid grid-cols-1 gap-x-8 gap-y-4 laptop:grid-cols-3;
}
/* .form-section-details {
- @apply page-x-padding flex flex-col justify-center space-y-0 tablet-sm:p-0;
+ @apply page-x-padding flex flex-col justify-center space-y-0 tablet:p-0;
}
.form-section-header {
@apply text-lg font-semibold;
@@ -193,7 +193,7 @@
}
.form-input {
@apply w-full border-0 bg-transparent p-0 placeholder:text-muted-light focus-within:placeholder-muted-lighter focus:outline-none focus:ring-0;
- /* @apply has-border block w-full rounded-md border-0 bg-gray-100 px-3 py-2 text-lg leading-6 ring-inset placeholder:text-muted-light hover:shadow hover:ring-1 hover:ring-gray-400/60 focus:bg-white focus:placeholder-gray-200 focus:ring-2 focus:ring-inset focus:ring-primary dark:bg-gray-900/70 dark:ring-gray-700 dark:placeholder:text-gray-700 dark:focus:ring-primary tablet-sm:text-base; */
+ /* @apply has-border block w-full rounded-md border-0 bg-gray-100 px-3 py-2 text-lg leading-6 ring-inset placeholder:text-muted-light hover:shadow hover:ring-1 hover:ring-gray-400/60 focus:bg-white focus:placeholder-gray-200 focus:ring-2 focus:ring-inset focus:ring-primary dark:bg-gray-900/70 dark:ring-gray-700 dark:placeholder:text-gray-700 dark:focus:ring-primary tablet:text-base; */
}
.form-input-error {
@apply !ring-2 !ring-red-500;
@@ -349,7 +349,7 @@
@apply mx-auto flex max-w-screen-laptop flex-col space-y-10;
}
.frontend-section {
- @apply flex flex-col space-y-5 tablet-sm:p-10;
+ @apply flex flex-col space-y-5 tablet:p-10;
}
.frontend-title {
@apply text-3xl font-semibold;
@@ -357,7 +357,7 @@
/* Properties */
.frontend-property-grid {
- @apply grid grid-cols-1 gap-10 tablet-sm:grid-cols-3;
+ @apply grid grid-cols-1 gap-10 tablet:grid-cols-3;
}
}
@@ -433,4 +433,7 @@
.label-space {
@apply !mt-[31px];
}
+ .color-input > input{
+ @apply p-0.5
+ }
}
diff --git a/app/resources/js/_formatter.js b/app/resources/js/_formatter.js
deleted file mode 100644
index 57104eb..0000000
--- a/app/resources/js/_formatter.js
+++ /dev/null
@@ -1,67 +0,0 @@
-/**
- * Formatters / Masks
- * Cleave - https://github.com/nosir/cleave.js
- */
-import Cleave from "cleave.js";
-function formatPhone(element) {
- new Cleave(element, {
- phone: true,
- delimiter: " ",
- phoneRegionCode: "US",
- });
-}
-function formatDate(element) {
- new Cleave(element, {
- date: true,
- delimiter: "/",
- datePattern: ["m", "d", "Y"],
- });
-}
-function formatZipCode(element) {
- new Cleave(element, {
- numericOnly: true,
- blocks: [5],
- rawValueTrimPrefix: true,
- });
-}
-function formatMoney(element) {
- new Cleave(element, {
- numeral: true,
- numeralPositiveOnly: true,
- numeralDecimalMark: ".",
- delimiter: ",",
- numeralDecimalScale: 2,
- rawValueTrimPrefix: true,
- });
-}
-
-document.querySelectorAll('[money="money"]').forEach((element) => {
- formatMoney(element);
-});
-
-// document.querySelectorAll(".phone").forEach((element) => {
-// formatPhone(element);
-// });
-// document.querySelectorAll(".date").forEach((element) => {
-// formatDate(element);
-// });
-// document.querySelectorAll(".money").forEach((element) => {
-// formatMoney(element);
-// });
-// document.querySelectorAll(".zip-code").forEach((element) => {
-// formatZipCode(element);
-// });
-// window.addEventListener("maskAllElements", (event) => {
-// document.querySelectorAll(".phone").forEach((element) => {
-// formatPhone(element);
-// });
-// document.querySelectorAll(".date").forEach((element) => {
-// formatDate(element);
-// });
-// document.querySelectorAll(".money").forEach((element) => {
-// formatMoney(element);
-// });
-// document.querySelectorAll(".zip-code").forEach((element) => {
-// formatZipCode(element);
-// });
-// });
diff --git a/app/resources/js/_masks.js b/app/resources/js/_masks.js
new file mode 100644
index 0000000..3943210
--- /dev/null
+++ b/app/resources/js/_masks.js
@@ -0,0 +1,17 @@
+
+/**
+ * This mask is to validate a url slug
+ *
+ * @param {string} text - the text to mask
+ * @returns
+ */
+function slugify(text) {
+ return text.toString().toLowerCase()
+ .replace(/\s+/g, '-') // Replace spaces with dashes
+ .replace(/[^\w\-]+/g, '') // Remove non-word characters
+ .replace(/\-\-+/g, '-') // Replace multiple dashes with a single dash
+ .replace(/^-+/, '') // Remove leading dashes
+ // .replace(/-+$/, ''); // Remove trailing dashes
+}
+
+window.slugify = slugify
diff --git a/app/resources/js/_photos.js b/app/resources/js/_photos.js
index 79e4069..534e7cb 100644
--- a/app/resources/js/_photos.js
+++ b/app/resources/js/_photos.js
@@ -7,80 +7,4 @@ Alpine.data("photosuploader", () => ({
photosCount: 0,
}));
-// import "livewire-sortable";
-
import "@nextapps-be/livewire-sortablejs";
-
-// Livewire.hook("drag:start", function () {
-// console.log("test");
-// });
-
-// window.addEventListener("init-sortable", (event) => {
-// // initSortablePhotos();
-// setTimeout(function () {
-// //your code to be executed after 1 second
-// initSortablePhotos();
-// }, 150);
-// });
-
-/**
- * Draggable - v1.1.3
- * https://github.com/Shopify/draggable
- */
-// import { Sortable } from "@shopify/draggable";
-// window.Sortable = Sortable;
-
-// var sortable = false;
-
-// function initSortablePhotos() {
-// const sortableContainer = ".sortable";
-// const containers = document.querySelectorAll(sortableContainer);
-
-// // If there isn't any photos to sort, don't sort.
-// if (containers.length === 0) {
-// return false;
-// }
-
-// // If there is an existing sortable instance, destroy it
-// if (sortable) {
-// sortable.destroy();
-// }
-
-// // Create the sortable instance
-// sortable = new Sortable(containers, {
-// draggable: ".sortable--item",
-// handle: ".sortable--handle",
-// mirror: {
-// appendTo: sortableContainer,
-// constrainDimensions: true,
-// cursorOffsetX: 50,
-// cursorOffsetY: 50,
-// },
-// classes: {
-// mirror: ["mirror", "drop-shadow-xl", "shadow-lg", "rounded-lg"],
-// "draggable:over": ["opacity-0"], // Classes added on draggable element you are dragging over
-// "source:dragging": ["opacity-20"], // Classes added on the draggable element that has been picked up
-// "source:original": ["opacity-0"], // Classes added on the original source element, which is hidden on drag
-// },
-// });
-
-// sortable.on("sortable:stop", () => {
-
-// });
-
-// // Do not allow users the sort the header photo
-// sortable.on("drag:start", (event) => {
-// const { sourceContainer } = event; // The sortable container
-// const { source } = event; // The element/photo that the user clicked to drag
-
-// /**
-// * Prevent the first photo from initiating a drag
-// */
-// const firstPhoto = sourceContainer.children[0];
-// if (source === firstPhoto) {
-// event.cancel();
-// }
-// });
-
-// return sortable;
-// }
diff --git a/app/resources/js/app.js b/app/resources/js/app.js
index 06b9a61..1dfff80 100644
--- a/app/resources/js/app.js
+++ b/app/resources/js/app.js
@@ -24,6 +24,8 @@ Alpine.plugin(AlpineCollapse);
Alpine.plugin(mask)
// Alpine.plugin(AlpinePersist);
Alpine.plugin(ToastComponent);
+// Start Livewire
+Livewire.start();
// Dev bar
import "./_devbar";
@@ -31,18 +33,19 @@ import "./_devbar";
// Theme Mode
import "./_themeMode";
+
+
+
// Photos
import "./_photos";
+// Masks
+import "./_masks";
-// Start Livewire
-Livewire.start();
-
+// Custom dispatch event to write to browser console
window.addEventListener("console", (event) => {
console.log(event.detail);
});
-import "./_formatter";
-
window.addEventListener(
"popstate",
(event) => {
diff --git a/app/resources/views/components/banner.blade.php b/app/resources/views/components/banner.blade.php
index 2647cfd..b8d3b51 100644
--- a/app/resources/views/components/banner.blade.php
+++ b/app/resources/views/components/banner.blade.php
@@ -13,7 +13,7 @@
{{ $banner['title'] }}
-
+
{!! $banner['description'] !!}
diff --git a/app/resources/views/components/utilities/dev-bar.blade.php b/app/resources/views/components/utilities/dev-bar.blade.php
index 4ed0453..395f9ec 100644
--- a/app/resources/views/components/utilities/dev-bar.blade.php
+++ b/app/resources/views/components/utilities/dev-bar.blade.php
@@ -1,33 +1,33 @@
@env('local')
-
+
-
+
{{-- Close --}}
-
+
{{-- Open --}}
-
+
-
-
+
+
-
+
-
+
@@ -46,9 +46,9 @@
size:
-
mobile
-
tablet-sm
-
tablet
+
mobile
+
tablet
+
tablet-lg
laptop
desktop
(xs)
diff --git a/app/resources/views/flux/icon/house-plus.blade.php b/app/resources/views/flux/icon/house-plus.blade.php
new file mode 100644
index 0000000..9c65476
--- /dev/null
+++ b/app/resources/views/flux/icon/house-plus.blade.php
@@ -0,0 +1,44 @@
+{{-- Credit: Lucide (https://lucide.dev) --}}
+
+@props([
+ 'variant' => 'outline',
+])
+
+@php
+if ($variant === 'solid') {
+ throw new \Exception('The "solid" variant is not supported in Lucide.');
+}
+
+$classes = Flux::classes('shrink-0')
+ ->add(match($variant) {
+ 'outline' => '[:where(&)]:size-6',
+ 'solid' => '[:where(&)]:size-6',
+ 'mini' => '[:where(&)]:size-5',
+ 'micro' => '[:where(&)]:size-4',
+ });
+
+$strokeWidth = match ($variant) {
+ 'outline' => 2,
+ 'mini' => 2.25,
+ 'micro' => 2.5,
+};
+@endphp
+
+
class($classes) }}
+ data-flux-icon
+ xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 24 24"
+ fill="none"
+ stroke="currentColor"
+ stroke-width="{{ $strokeWidth }}"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ aria-hidden="true"
+ data-slot="icon"
+>
+
+
+
+
+
diff --git a/app/resources/views/flux/icon/move.blade.php b/app/resources/views/flux/icon/move.blade.php
new file mode 100644
index 0000000..b0d3454
--- /dev/null
+++ b/app/resources/views/flux/icon/move.blade.php
@@ -0,0 +1,46 @@
+{{-- Credit: Lucide (https://lucide.dev) --}}
+
+@props([
+ 'variant' => 'outline',
+])
+
+@php
+if ($variant === 'solid') {
+ throw new \Exception('The "solid" variant is not supported in Lucide.');
+}
+
+$classes = Flux::classes('shrink-0')
+ ->add(match($variant) {
+ 'outline' => '[:where(&)]:size-6',
+ 'solid' => '[:where(&)]:size-6',
+ 'mini' => '[:where(&)]:size-5',
+ 'micro' => '[:where(&)]:size-4',
+ });
+
+$strokeWidth = match ($variant) {
+ 'outline' => 2,
+ 'mini' => 2.25,
+ 'micro' => 2.5,
+};
+@endphp
+
+
class($classes) }}
+ data-flux-icon
+ xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 24 24"
+ fill="none"
+ stroke="currentColor"
+ stroke-width="{{ $strokeWidth }}"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ aria-hidden="true"
+ data-slot="icon"
+>
+
+
+
+
+
+
+
diff --git a/app/resources/views/flux/icon/picture-in-picture-2.blade.php b/app/resources/views/flux/icon/picture-in-picture-2.blade.php
new file mode 100644
index 0000000..a4309b6
--- /dev/null
+++ b/app/resources/views/flux/icon/picture-in-picture-2.blade.php
@@ -0,0 +1,42 @@
+{{-- Credit: Lucide (https://lucide.dev) --}}
+
+@props([
+ 'variant' => 'outline',
+])
+
+@php
+if ($variant === 'solid') {
+ throw new \Exception('The "solid" variant is not supported in Lucide.');
+}
+
+$classes = Flux::classes('shrink-0')
+ ->add(match($variant) {
+ 'outline' => '[:where(&)]:size-6',
+ 'solid' => '[:where(&)]:size-6',
+ 'mini' => '[:where(&)]:size-5',
+ 'micro' => '[:where(&)]:size-4',
+ });
+
+$strokeWidth = match ($variant) {
+ 'outline' => 2,
+ 'mini' => 2.25,
+ 'micro' => 2.5,
+};
+@endphp
+
+
class($classes) }}
+ data-flux-icon
+ xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 24 24"
+ fill="none"
+ stroke="currentColor"
+ stroke-width="{{ $strokeWidth }}"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ aria-hidden="true"
+ data-slot="icon"
+>
+
+
+
diff --git a/app/resources/views/flux/icon/save.blade.php b/app/resources/views/flux/icon/save.blade.php
new file mode 100644
index 0000000..444b632
--- /dev/null
+++ b/app/resources/views/flux/icon/save.blade.php
@@ -0,0 +1,43 @@
+{{-- Credit: Lucide (https://lucide.dev) --}}
+
+@props([
+ 'variant' => 'outline',
+])
+
+@php
+if ($variant === 'solid') {
+ throw new \Exception('The "solid" variant is not supported in Lucide.');
+}
+
+$classes = Flux::classes('shrink-0')
+ ->add(match($variant) {
+ 'outline' => '[:where(&)]:size-6',
+ 'solid' => '[:where(&)]:size-6',
+ 'mini' => '[:where(&)]:size-5',
+ 'micro' => '[:where(&)]:size-4',
+ });
+
+$strokeWidth = match ($variant) {
+ 'outline' => 2,
+ 'mini' => 2.25,
+ 'micro' => 2.5,
+};
+@endphp
+
+
class($classes) }}
+ data-flux-icon
+ xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 24 24"
+ fill="none"
+ stroke="currentColor"
+ stroke-width="{{ $strokeWidth }}"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ aria-hidden="true"
+ data-slot="icon"
+>
+
+
+
+
diff --git a/app/resources/views/layouts/host.blade.php b/app/resources/views/layouts/host.blade.php
index 960b09b..0c7d227 100644
--- a/app/resources/views/layouts/host.blade.php
+++ b/app/resources/views/layouts/host.blade.php
@@ -1,19 +1,19 @@
@props(['pageTitle' => false, 'pageActions' => false])
-
+
{{-- Topbar --}}
{{-- Menu Button --}}
-
+
-
+
-
+
@@ -22,9 +22,9 @@
{{-- Logo --}}
-
+
@@ -33,13 +33,13 @@
@persist('profile-dropdown')
-
+
-
+
{{-- Container --}}
-
+
{{-- Mobile Sidebar Overlay --}}
-
+
{{-- Sidebar --}}
-
-
+
+
{{-- Property Selector --}}
@@ -88,10 +88,10 @@
{{-- Navigation --}}
-
+
{{-- Content --}}
-
-
+
+
{{-- Banner --}}
@@ -154,9 +154,9 @@
{{-- Content Container --}}
-
-