lots of frontend stuff

shift-build-2464
Nadim Salloum 2021-05-12 18:17:28 +02:00
parent 68ca52f6c4
commit f280e3d6a3
25 changed files with 6570 additions and 2023 deletions

View File

@ -4,6 +4,9 @@ namespace App\Http\Controllers;
use App\Models\Car;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Validation\Rule;
use Inertia\Inertia;
class CarController extends Controller
{
@ -15,10 +18,9 @@ class CarController extends Controller
public function index()
{
return Inertia::render('Cars/Index', [
'filters' => Request::all('search', 'trashed'),
'filters' => request()->all('search', 'trashed'),
'cars' => Car::all()
->orderByName()
->filter(Request::only('search', 'trashed'))
->filter(request()->only('search', 'trashed'))
->paginate()
->withQueryString()
->through(function ($car) {
@ -28,8 +30,8 @@ class CarController extends Controller
'vin' => $car->vin,
'bought_at' => $car->bought_at,
'buy_price' => $car->buy_price,
'seller' => $car->seller->only('name');
'buyer' => $car->buyer->only('name');
'seller' => $car->seller->only('name'),
'buyer' => $car->buyer->only('name'),
'car_model' => $car->carModel->only('name'),
'name' => $car->name,
'phone' => $car->phone,

View File

@ -2,8 +2,12 @@
namespace App\Http\Controllers;
use Inertia\Inertia;
use App\Models\Contact;
use App\Enums\InsuranceType;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\Redirect;
class ContactController extends Controller
{
@ -12,25 +16,22 @@ class ContactController extends Controller
*
* @return \Illuminate\Http\Response
*/
public function index()
public function index(Request $request)
{
return Inertia::render('Contacts/Index', [
'filters' => Request::all('search', 'trashed'),
'contacts' => Contact::all()
'filters' => $request->all('search', 'trashed'),
'contacts' => Contact::filter($request->only('search', 'trashed'))
->orderByName()
->filter(Request::only('search', 'trashed'))
->paginate()
->paginate(50)
->withQueryString()
->through(function ($contact) {
return [
'id' => $contact->id,
'name' => $contact->name,
'phone' => $contact->phone,
'zipcode' => $contact->city,
'city' => $contact->city,
'deleted_at' => $contact->deleted_at,
];
}),
->through(fn ($contact) => [
'id' => $contact->id,
'name' => $contact->name,
'company' => $contact->company,
'phone' => $contact->phone,
'fullCity' => $contact->fullCity,
'deleted_at' => $contact->deleted_at,
]),
]);
}
@ -53,13 +54,13 @@ class ContactController extends Controller
public function store(Request $request)
{
Contact::create(
Request::validate([
$request->validate([
'firstname' => ['required', 'max:75'],
'lastname' => ['required', 'max:75'],
'email' => ['nullable', 'max:75', 'email'],
'phone' => ['max:75'],
'address' => ['nullable', 'max:150'],
'zipcode' => ['nullable', 'max:6'],
'zip' => ['nullable', 'max:6'],
'city' => ['nullable', 'max:75'],
'country' => ['nullable', 'max:2'],
'company' => ['nullable', 'max:75'],
@ -80,15 +81,28 @@ class ContactController extends Controller
return Inertia::render('Contacts/Edit', [
'contact' => [
'id' => $contact->id,
'firstname' => $contact->first_name,
'lastname' => $contact->last_name,
'firstname' => $contact->firstname,
'lastname' => $contact->lastname,
'company' => $contact->company,
'title' => $contact->title,
'email' => $contact->email,
'notes' => $contact->notes,
'phone' => $contact->phone,
'address' => $contact->address,
'zipcode' => $contact->postal_code,
'zip' => $contact->zip,
'city' => $contact->city,
'country' => $contact->country,
'deleted_at' => $contact->deleted_at,
'contracts' => $contact->contracts()
->with('car')
->paginate(10)
->through(fn ($contract) => [
'sold_at' => $contract->sold_at,
'sell_price' => $contract->sell_price,
'car' => $contract->car->name,
'link' => route('cars.edit', $contract->car),
'insurance_type' => InsuranceType::fromValue((int)$contract->insurance_type)->key,
]),
]
]);
}
@ -103,20 +117,20 @@ class ContactController extends Controller
public function update(Request $request, Contact $contact)
{
$contact->update(
Request::validate([
'firstname' => ['required', 'max:75'],
'lastname' => ['required', 'max:75'],
$request->validate([
'firstname' => ['max:75'],
'lastname' => ['max:75'],
'email' => ['nullable', 'max:75', 'email'],
'phone' => ['max:75'],
'address' => ['nullable', 'max:150'],
'zipcode' => ['nullable', 'max:6'],
'zip' => ['nullable', 'max:6'],
'city' => ['nullable', 'max:75'],
'country' => ['nullable', 'max:2'],
'company' => ['nullable', 'max:75'],
])
);
return Redirect::route('contacts')->with('success', 'Kontakt geändert.');
return Redirect::back()->with('success', 'Kontakt geändert.');
}
/**

View File

@ -26,12 +26,15 @@ class Car extends Model
public function getNameAttribute()
{
return $this->brand->name . ' ' . $this->carModel->car_model . $this->variation ? '(' . $this->variation . ')' : '';
$out = $this->brand->name . ' ' . $this->carModel->name;
$out .= $this->variation ? ' (' . $this->variation . ')' : '';
return $out;
}
public function brand()
{
return $this->hasOneThrough(Brand::class, CarModel::class);
return $this->carModel->brand();
}
public function carModel()
@ -58,4 +61,9 @@ class Car extends Model
{
return $this->hasManyThrough(CarPayment::class, Contract::class);
}
public function scopeSoldThisYear($query)
{
return $query->whereDate('sold_at', \Carbon\Carbon::today());
}
}

View File

@ -23,6 +23,25 @@ class Contact extends Model
'notes',
];
public function getNameAttribute()
{
return $this->lastname . ' ' . $this->firstname;
}
public function getTitleAttribute()
{
if ($this->company != '') {
return $this->company;
}
return $this->name;
}
public function getFullCityAttribute()
{
return $this->zip . ' ' . $this->city;
}
public function scopeOrderByName($query)
{
$query->orderBy('lastname')->orderBy('firstname');
@ -30,12 +49,12 @@ class Contact extends Model
public function contracts()
{
return $this->hasMany(Contracts::class);
return $this->hasMany(Contract::class);
}
public function boughtCars()
{
return $this->hasManyThrough(Car::class, Contracts::class);
return $this->hasManyThrough(Car::class, Contract::class);
}
public function soldCars()
@ -49,6 +68,7 @@ class Contact extends Model
$query->where(function ($query) use ($search) {
$query->where('firstname', 'like', '%' . $search . '%')
->orWhere('lastname', 'like', '%' . $search . '%')
->orWhere('company', 'like', '%' . $search . '%')
->orWhere('email', 'like', '%' . $search . '%');
});
})->when($filters['trashed'] ?? null, function ($query, $trashed) {

View File

@ -80,7 +80,7 @@ return [
|
*/
'locale' => 'en',
'locale' => 'de',
/*
|--------------------------------------------------------------------------
@ -106,7 +106,7 @@ return [
|
*/
'faker_locale' => 'en_US',
'faker_locale' => 'de_CH',
/*
|--------------------------------------------------------------------------

View File

@ -43,10 +43,10 @@ return [
'features' => [
// Features::termsAndPrivacyPolicy(),
// Features::profilePhotos(),
Features::profilePhotos(),
// Features::api(),
// Features::teams(['invitations' => true]),
Features::accountDeletion(),
// Features::accountDeletion(),
],
/*

View File

@ -29,6 +29,7 @@ class ContactFactory extends Factory
'firstname' => $this->faker->firstName(),
'lastname' => $this->faker->lastName(),
'phone' => $this->faker->PhoneNumber(),
'email' => $this->faker->email(),
'address' => $this->faker->streetName() . ' ' . $this->faker->buildingNumber(),
'zip' => $this->faker->randomNumber(4, true),
'city' => $this->faker->city(),

1806
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -15,14 +15,18 @@
"@inertiajs/progress": "^0.2.4",
"@tailwindcss/forms": "^0.2.1",
"@tailwindcss/typography": "^0.3.0",
"@types/lodash": "^4.14.168",
"@vue/compiler-sfc": "^3.0.5",
"axios": "^0.21",
"laravel-mix": "^6.0.6",
"lodash": "^4.17.19",
"lodash": "^4.17.21",
"postcss": "^8.1.14",
"postcss-import": "^12.0.1",
"tailwindcss": "^2.0.1",
"vue": "^3.0.5",
"vue-loader": "^16.1.2"
},
"dependencies": {
"vue-unicons": "^3.2.1"
}
}

5875
public/js/app.js vendored

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,14 @@
<template>
<inertia-link :href="href" class="text-indigo-400 hover:text-indigo-600" >
{{ text }} /
</inertia-link>
</template>
<script>
export default {
props: {
href: String,
text: String,
},
}
</script>

View File

@ -0,0 +1,61 @@
<template>
<button type="button" @click="show = true">
<slot />
<portal v-if="show" to="dropdown">
<div>
<div style="position: fixed; top: 0; right: 0; left: 0; bottom: 0; z-index: 99998; background: black; opacity: .2" @click="show = false" />
<div ref="dropdown" style="position: absolute; z-index: 99999;" @click.stop="show = autoClose ? false : true">
<slot name="dropdown" />
</div>
</div>
</portal>
</button>
</template>
<script>
export default {
props: {
placement: {
type: String,
default: 'bottom-end',
},
boundary: {
type: String,
default: 'scrollParent',
},
autoClose: {
type: Boolean,
default: true,
},
},
data() {
return {
show: false,
}
},
watch: {
show(show) {
if (show) {
this.$nextTick(() => {
this.popper = new Popper(this.$el, this.$refs.dropdown, {
placement: this.placement,
modifiers: {
preventOverflow: { boundariesElement: this.boundary },
},
})
})
} else if (this.popper) {
setTimeout(() => this.popper.destroy(), 100)
}
},
},
mounted() {
document.addEventListener('keydown', e => {
if (e.keyCode === 27) {
this.show = false
}
})
},
}
</script>

View File

@ -0,0 +1,18 @@
<template>
<div v-if="links.length > 3">
<div class="flex flex-wrap -mb-1">
<template v-for="(link, key) in links">
<div v-if="link.url === null" :key="key" class="mr-1 mb-1 px-4 py-3 text-sm leading-4 text-gray-400 border rounded" v-html="link.label" />
<inertia-link v-if="link.url !== null" :key="key" class="mr-1 mb-1 px-4 py-3 text-sm leading-4 border rounded hover:bg-white focus:border-indigo-500 focus:text-indigo-500" :class="{ 'bg-white': link.active }" :href="link.url" v-html="link.label" />
</template>
</div>
</div>
</template>
<script>
export default {
props: {
links: Array,
},
}
</script>

View File

@ -0,0 +1,21 @@
<template>
<div class="flex items-center">
<input class="border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm block w-full" autocomplete="off" type="text" name="search" placeholder="Suchen..." :value="value" @input="$emit('input', $event.target.value)" />
<button class="ml-3 text-sm text-gray-500 hover:text-gray-700 focus:text-indigo-500" type="button" @click="$emit('reset')">zurücksetzen</button>
</div>
</template>
<script>
export default {
components: {
},
props: {
value: String,
maxWidth: {
type: Number,
default: 300,
},
},
}
</script>

View File

@ -0,0 +1,43 @@
<template>
<div>
<div class="bg-grey overflow-hidden sm:rounded-lg">
<div class="whitespace-nowrap">
<h3 class="font-semibold text-xl m-3 text-gray-800 leading-tight">{{ title }}</h3>
</div>
<div class="bg-white rounded-md shadow overflow-x-auto">
<table class="w-full whitespace-nowrap">
<tr class="text-left font-bold">
<th v-for="col in columns" :key="col.key" class="px-6 pt-4 pb-4">{{ col }}</th>
</tr>
<tr v-for="(index, row) in data.data" :key="row.link" class="hover:bg-gray-100 focus-within:bg-gray-100">
<td v-for="(val, col) in columns" :key="col" class="border-t">
<inertia-link class="px-6 py-4 flex items-center focus:text-indigo-500" :href="row.link" tabindex="{ '-1': index !== 0 }">
{{ row[col] }}
<unicon v-if="columns[col] == columns[columns.length - 1]" class="m-2" height="22" width="22" name="angle-right"></unicon>
</inertia-link>
</td>
</tr>
<tr v-if="data.total === 0">
<td class="border-t px-6 py-4" colspan="4">Keine Einträge gefunden</td>
</tr>
</table>
</div>
</div>
<Paginator class="mt-6" :links="data.links" />
</div>
</template>
<script>
import Paginator from "@/Components/Paginator"
export default {
components: {
Paginator,
},
props: {
data: Object,
columns: Array,
title: String,
},
}
</script>

View File

@ -18,11 +18,23 @@
<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<jet-nav-link :href="route('dashboard')" :active="route().current('dashboard')">
<unicon class="m-2" height="22" width="22" name="dashboard"></unicon>
Dashboard
</jet-nav-link>
</div>
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<jet-nav-link :href="route('contacts')" :active="route().current('contacts')">
<unicon class="m-2" height="22" width="22" name="users-alt"></unicon>
Kontakte
</jet-nav-link>
</div>
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<jet-nav-link :href="route('cars')" :active="route().current('cars')">
<unicon class="m-2" height="22" width="22" name="car-sideview"></unicon>
Autos
</jet-nav-link>
</div>
</div>
<div class="hidden sm:flex sm:items-center sm:ml-6">
<div class="ml-3 relative">
<!-- Teams Dropdown -->

View File

@ -0,0 +1,180 @@
<template>
<app-layout>
<template #header>
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
<bread-crumb text="Kontakte" :href="route('contacts')" />
{{ title }}
</h2>
</template>
<div>
<div class="max-w-7xl mx-auto py-10 sm:px-6 lg:px-8">
<jet-form-section @submitted="submitForm">
<template #title>
Kontaktinformationen
</template>
<template #description>
Kontaktinformationen anschauen &amp; anpassen
</template>
<template #form>
<div class="col-span-6 sm:col-span-4">
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6 sm:col-span-3">
<jet-label for="firstname" value="Vorname" />
<jet-input id="firstname" type="text" class="mt-1 block w-full" v-model="form.firstname" ref="firstname" autocomplete="firstname" />
<jet-input-error :message="form.errors.firstname" class="mt-2" />
</div>
<div class="col-span-6 sm:col-span-3">
<jet-label for="lastname" value="Nachname" />
<jet-input id="lastname" type="text" class="mt-1 block w-full" v-model="form.lastname" ref="lastname" autocomplete="lastname" />
<jet-input-error :message="form.errors.lastname" class="mt-2" />
</div>
</div>
</div>
<div class="col-span-6 sm:col-span-4">
<jet-label for="company" value="Firma" />
<jet-input id="company" type="text" class="mt-1 block w-full" v-model="form.company" ref="company" autocomplete="company" />
<jet-input-error :message="form.errors.company" class="mt-2" />
</div>
<div class="col-span-6 sm:col-span-4">
<jet-label for="address" value="Strasse" />
<jet-input id="address" type="text" class="mt-1 block w-full" v-model="form.address" ref="address" autocomplete="address" />
<jet-input-error :message="form.errors.address" class="mt-2" />
</div>
<div class="col-span-6 sm:col-span-4">
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6 sm:col-span-2">
<jet-label for="zip" value="PLZ" />
<jet-input id="zip" type="text" class="mt-1 block w-full" v-model="form.zip" ref="zip" autocomplete="zip" />
<jet-input-error :message="form.errors.zip" class="mt-2" />
</div>
<div class="col-span-6 sm:col-span-3">
<jet-label for="city" value="Ort" />
<jet-input id="city" type="text" class="mt-1 block w-full" v-model="form.city" ref="city" autocomplete="city" />
<jet-input-error :message="form.errors.city" class="mt-2" />
</div>
<div class="col-span-6 sm:col-span-1">
<jet-label for="country" value="Land" />
<jet-input id="country" type="text" class="mt-1 block w-full" v-model="form.country" ref="country" autocomplete="country" />
<jet-input-error :message="form.errors.country" class="mt-2" />
</div>
</div>
</div>
<div class="col-span-6 sm:col-span-4">
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6 sm:col-span-3">
<jet-label for="email" value="E-Mail" />
<jet-input id="email" type="email" class="mt-1 block w-full" v-model="form.email" ref="email" autocomplete="email" />
<jet-input-error :message="form.errors.email" class="mt-2" />
</div>
<div class="col-span-6 sm:col-span-3">
<jet-label for="phone" value="Telefon" />
<jet-input id="phone" type="text" class="mt-1 block w-full" v-model="form.phone" ref="phone" autocomplete="phone" />
<jet-input-error :message="form.errors.phone" class="mt-2" />
</div>
</div>
</div>
<div class="col-span-6 sm:col-span-4">
<jet-label for="notes" value="Bemerkungen" />
<textarea class="mt-1 block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm" v-model="form.notes" ref="input">
</textarea>
<jet-input-error :message="form.errors.notes" class="mt-2" />
</div>
</template>
<template #actions>
<jet-action-message :on="form.recentlySuccessful" class="mr-3">
Änderungen gespeichert.
</jet-action-message>
<jet-button :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
Änderungen speichern
</jet-button>
</template>
</jet-form-section>
</div>
</div>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<simple-table :title="'An \'' + title + '\' verkaufte Autos'" :data="contact.contracts" :columns="Array.from(contractColumns)" />
</div>
</div>
</app-layout>
</template>
<script>
import AppLayout from '@/Layouts/AppLayout'
import JetButton from '@/Jetstream/Button'
import BreadCrumb from '@/Components/BreadCrumb.vue'
import SimpleTable from '@/Components/SimpleTable.vue'
import JetLabel from '@/Jetstream/Label.vue'
import JetInput from '@/Jetstream/Input.vue'
import JetActionMessage from '@/Jetstream/ActionMessage'
import JetInputError from '@/Jetstream/InputError'
import JetFormSection from '@/Jetstream/FormSection'
export default {
components: {
JetButton,
JetFormSection,
AppLayout,
BreadCrumb,
SimpleTable,
JetLabel,
JetInput,
JetInputError,
JetActionMessage,
},
props: {
contact: Object,
},
data() {
return {
form: this.$inertia.form({
_method: 'PUT',
firstname: this.contact.firstname,
lastname: this.contact.lastname,
company: this.contact.company,
email: this.contact.email,
phone: this.contact.phone,
address: this.contact.address,
zip: this.contact.zip,
city: this.contact.city,
country: this.contact.country,
notes: this.contact.notes,
}),
contractColumns: {
car: 'Auto',
sold_at: 'Verkaufsdatum',
sell_price: 'Verkaufspreis',
insurance_type: 'Versicherungstyp',
}
}
},
computed: {
title: function () {
if (this.form.company) {
return this.form.company;
}
return this.form.lastname + ' ' + this.form.firstname;
}
},
methods: {
submitForm() {
this.form.post(route('contacts.update', this.contact), {
preserveScroll: true,
});
},
},
}
</script>

View File

@ -1,84 +1,84 @@
<template>
<div>
<h1 class="mb-8 font-bold text-3xl">Kontakte</h1>
<div class="mb-6 flex justify-between items-center">
<search-filter v-model="form.search" class="w-full max-w-md mr-4" @reset="reset">
<label class="block text-gray-700">Trashed:</label>
<select v-model="form.trashed" class="mt-1 w-full form-select">
<option :value="null" />
<option value="with">With Trashed</option>
<option value="only">Only Trashed</option>
</select>
</search-filter>
<inertia-link class="btn-indigo" :href="route('contacts.create')">
<span>Create</span>
<span class="hidden md:inline">Contact</span>
</inertia-link>
<app-layout>
<template #header>
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
Kontakte
</h2>
</template>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="mb-6 flex justify-between items-center">
<!-- <search-filter ref="search" v-model="form.search" class="w-full max-w-md mr-4" @reset="reset"></search-filter> -->
<input type="text" ref="search" v-model="form.search" autofocus="true" name="search" placeholder="Suchen..." class="border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm block w-full" autocomplete="off">
<jet-button class="ml-4" @click="createContact">
Kontakt erstellen
</jet-button>
</div>
<div class="bg-grey overflow-hidden sm:rounded-lg">
<div class="whitespace-nowrap">
<h3 class="font-semibold text-xl m-3 text-gray-800 leading-tight">{{ contacts.total }} Kontakte</h3>
</div>
<div class="bg-white rounded-md shadow overflow-x-auto">
<table class="w-full whitespace-nowrap">
<tr class="text-left font-bold">
<th class="px-6 pt-4 pb-4">Name</th>
<th class="px-6 pt-4 pb-4">Firma</th>
<th class="px-6 pt-4 pb-4">Ort</th>
<th class="px-6 pt-4 pb-4" colspan="2">Telefon</th>
</tr>
<tr v-for="contact in contacts.data" :key="contact.id" class="hover:bg-gray-100 focus-within:bg-gray-100">
<td class="border-t">
<inertia-link class="px-6 py-4 flex items-center focus:text-indigo-500" :href="route('contacts.edit', contact.id)">
{{ contact.name }}
</inertia-link>
</td>
<td class="border-t">
<inertia-link class="px-6 py-4 flex items-center" :href="route('contacts.edit', contact.id)" tabindex="-1">
{{ contact.company}}
</inertia-link>
</td>
<td class="border-t">
<inertia-link class="px-6 py-4 flex items-center" :href="route('contacts.edit', contact.id)" tabindex="-1">
{{ contact.fullCity }}
</inertia-link>
</td>
<td class="border-t">
<inertia-link class="px-6 py-4 flex items-center" :href="route('contacts.edit', contact.id)" tabindex="-1">
{{ contact.phone }}
</inertia-link>
</td>
<td class="border-t w-px">
<inertia-link class="px-4 flex items-center" :href="route('contacts.edit', contact.id)" tabindex="-1">
<unicon class="m-2" height="22" width="22" name="angle-right"></unicon>
</inertia-link>
</td>
</tr>
<tr v-if="contacts.length === 0">
<td class="border-t px-6 py-4" colspan="4">Keine Kontakte gefunden</td>
</tr>
</table>
</div>
</div>
<Paginator class="mt-6" :links="contacts.links" />
</div>
</div>
<div class="bg-white rounded-md shadow overflow-x-auto">
<table class="w-full whitespace-nowrap">
<tr class="text-left font-bold">
<th class="px-6 pt-6 pb-4">Name</th>
<th class="px-6 pt-6 pb-4">Organization</th>
<th class="px-6 pt-6 pb-4">City</th>
<th class="px-6 pt-6 pb-4" colspan="2">Phone</th>
</tr>
<tr v-for="contact in contacts.data" :key="contact.id" class="hover:bg-gray-100 focus-within:bg-gray-100">
<td class="border-t">
<inertia-link class="px-6 py-4 flex items-center focus:text-indigo-500" :href="route('contacts.edit', contact.id)">
{{ contact.name }}
<icon v-if="contact.deleted_at" name="trash" class="flex-shrink-0 w-3 h-3 fill-gray-400 ml-2" />
</inertia-link>
</td>
<td class="border-t">
<inertia-link class="px-6 py-4 flex items-center" :href="route('contacts.edit', contact.id)" tabindex="-1">
<div v-if="contact.organization">
{{ contact.organization.name }}
</div>
</inertia-link>
</td>
<td class="border-t">
<inertia-link class="px-6 py-4 flex items-center" :href="route('contacts.edit', contact.id)" tabindex="-1">
{{ contact.city }}
</inertia-link>
</td>
<td class="border-t">
<inertia-link class="px-6 py-4 flex items-center" :href="route('contacts.edit', contact.id)" tabindex="-1">
{{ contact.phone }}
</inertia-link>
</td>
<td class="border-t w-px">
<inertia-link class="px-4 flex items-center" :href="route('contacts.edit', contact.id)" tabindex="-1">
<icon name="cheveron-right" class="block w-6 h-6 fill-gray-400" />
</inertia-link>
</td>
</tr>
<tr v-if="contacts.data.length === 0">
<td class="border-t px-6 py-4" colspan="4">No contacts found.</td>
</tr>
</table>
</div>
<pagination class="mt-6" :links="contacts.links" />
</div>
</app-layout>
</template>
<script>
import Icon from '@/Shared/Icon'
import pickBy from 'lodash/pickBy'
import Layout from '@/Shared/Layout'
import throttle from 'lodash/throttle'
import mapValues from 'lodash/mapValues'
import Pagination from '@/Shared/Pagination'
import SearchFilter from '@/Shared/SearchFilter'
import { pickBy, throttle, mapValues } from 'lodash'
import AppLayout from '@/Layouts/AppLayout'
import Paginator from "@/Components/Paginator"
import SearchFilter from '@/Components/SearchFilter'
import JetButton from '@/Jetstream/Button'
export default {
metaInfo: { title: 'Contacts' },
components: {
Icon,
Pagination,
Paginator,
SearchFilter,
JetButton,
AppLayout,
},
layout: Layout,
props: {
filters: Object,
contacts: Object,
@ -95,14 +95,18 @@ export default {
form: {
deep: true,
handler: throttle(function() {
this.$inertia.get(this.route('contacts'), pickBy(this.form), { preserveState: true })
}, 150),
this.$inertia.get(this.route('contacts'), pickBy(this.form), { preserveState: false })
// this.$refs.search.focus();
}, 300),
},
},
methods: {
reset() {
this.form = mapValues(this.form, () => null)
},
createContact() {
this.$inertia.visit(route('contacts.create'), { method: 'get' })
},
},
}
</script>
</script>

View File

@ -9,7 +9,7 @@
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
<welcome />
<p class="font-semibold text-xl mt-3 ml-3 mb-3 text-gray-800 leading-tight">hallo</p>
</div>
</div>
</div>
@ -18,12 +18,10 @@
<script>
import AppLayout from '@/Layouts/AppLayout'
import Welcome from '@/Jetstream/Welcome'
export default {
components: {
AppLayout,
Welcome,
},
}
</script>

View File

@ -1,26 +0,0 @@
<template>
<div class="font-sans text-gray-900 antialiased">
<div class="pt-4 bg-gray-100">
<div class="min-h-screen flex flex-col items-center pt-6 sm:pt-0">
<div>
<jet-authentication-card-logo />
</div>
<div v-html="policy" class="w-full sm:max-w-2xl mt-6 p-6 bg-white shadow-md overflow-hidden sm:rounded-lg prose">
</div>
</div>
</div>
</div>
</template>
<script>
import JetAuthenticationCardLogo from '@/Jetstream/AuthenticationCardLogo'
export default {
props: ['policy'],
components: {
JetAuthenticationCardLogo,
},
}
</script>

View File

@ -1,26 +0,0 @@
<template>
<div class="font-sans text-gray-900 antialiased">
<div class="pt-4 bg-gray-100">
<div class="min-h-screen flex flex-col items-center pt-6 sm:pt-0">
<div>
<jet-authentication-card-logo />
</div>
<div v-html="terms" class="w-full sm:max-w-2xl mt-6 p-6 bg-white shadow-md overflow-hidden sm:rounded-lg prose">
</div>
</div>
</div>
</div>
</template>
<script>
import JetAuthenticationCardLogo from '@/Jetstream/AuthenticationCardLogo'
export default {
props: ['terms'],
components: {
JetAuthenticationCardLogo,
},
}
</script>

View File

@ -1,186 +0,0 @@
<template>
<div class="relative flex items-top justify-center min-h-screen bg-gray-100 dark:bg-gray-900 sm:items-center sm:pt-0">
<div v-if="canLogin" class="hidden fixed top-0 right-0 px-6 py-4 sm:block">
<inertia-link v-if="$page.props.user" href="/dashboard" class="text-sm text-gray-700 underline">
Dashboard
</inertia-link>
<template v-else>
<inertia-link :href="route('login')" class="text-sm text-gray-700 underline">
Log in
</inertia-link>
<inertia-link v-if="canRegister" :href="route('register')" class="ml-4 text-sm text-gray-700 underline">
Register
</inertia-link>
</template>
</div>
<div class="max-w-6xl mx-auto sm:px-6 lg:px-8">
<div class="flex justify-center pt-8 sm:justify-start sm:pt-0">
<svg viewBox="0 0 651 192" fill="none" xmlns="http://www.w3.org/2000/svg" class="h-16 w-auto text-gray-700 sm:h-20">
<g clip-path="url(#clip0)" fill="#EF3B2D">
<path d="M248.032 44.676h-16.466v100.23h47.394v-14.748h-30.928V44.676zM337.091 87.202c-2.101-3.341-5.083-5.965-8.949-7.875-3.865-1.909-7.756-2.864-11.669-2.864-5.062 0-9.69.931-13.89 2.792-4.201 1.861-7.804 4.417-10.811 7.661-3.007 3.246-5.347 6.993-7.016 11.239-1.672 4.249-2.506 8.713-2.506 13.389 0 4.774.834 9.26 2.506 13.459 1.669 4.202 4.009 7.925 7.016 11.169 3.007 3.246 6.609 5.799 10.811 7.66 4.199 1.861 8.828 2.792 13.89 2.792 3.913 0 7.804-.955 11.669-2.863 3.866-1.908 6.849-4.533 8.949-7.875v9.021h15.607V78.182h-15.607v9.02zm-1.431 32.503c-.955 2.578-2.291 4.821-4.009 6.73-1.719 1.91-3.795 3.437-6.229 4.582-2.435 1.146-5.133 1.718-8.091 1.718-2.96 0-5.633-.572-8.019-1.718-2.387-1.146-4.438-2.672-6.156-4.582-1.719-1.909-3.032-4.152-3.938-6.73-.909-2.577-1.36-5.298-1.36-8.161 0-2.864.451-5.585 1.36-8.162.905-2.577 2.219-4.819 3.938-6.729 1.718-1.908 3.77-3.437 6.156-4.582 2.386-1.146 5.059-1.718 8.019-1.718 2.958 0 5.656.572 8.091 1.718 2.434 1.146 4.51 2.674 6.229 4.582 1.718 1.91 3.054 4.152 4.009 6.729.953 2.577 1.432 5.298 1.432 8.162-.001 2.863-.479 5.584-1.432 8.161zM463.954 87.202c-2.101-3.341-5.083-5.965-8.949-7.875-3.865-1.909-7.756-2.864-11.669-2.864-5.062 0-9.69.931-13.89 2.792-4.201 1.861-7.804 4.417-10.811 7.661-3.007 3.246-5.347 6.993-7.016 11.239-1.672 4.249-2.506 8.713-2.506 13.389 0 4.774.834 9.26 2.506 13.459 1.669 4.202 4.009 7.925 7.016 11.169 3.007 3.246 6.609 5.799 10.811 7.66 4.199 1.861 8.828 2.792 13.89 2.792 3.913 0 7.804-.955 11.669-2.863 3.866-1.908 6.849-4.533 8.949-7.875v9.021h15.607V78.182h-15.607v9.02zm-1.432 32.503c-.955 2.578-2.291 4.821-4.009 6.73-1.719 1.91-3.795 3.437-6.229 4.582-2.435 1.146-5.133 1.718-8.091 1.718-2.96 0-5.633-.572-8.019-1.718-2.387-1.146-4.438-2.672-6.156-4.582-1.719-1.909-3.032-4.152-3.938-6.73-.909-2.577-1.36-5.298-1.36-8.161 0-2.864.451-5.585 1.36-8.162.905-2.577 2.219-4.819 3.938-6.729 1.718-1.908 3.77-3.437 6.156-4.582 2.386-1.146 5.059-1.718 8.019-1.718 2.958 0 5.656.572 8.091 1.718 2.434 1.146 4.51 2.674 6.229 4.582 1.718 1.91 3.054 4.152 4.009 6.729.953 2.577 1.432 5.298 1.432 8.162 0 2.863-.479 5.584-1.432 8.161zM650.772 44.676h-15.606v100.23h15.606V44.676zM365.013 144.906h15.607V93.538h26.776V78.182h-42.383v66.724zM542.133 78.182l-19.616 51.096-19.616-51.096h-15.808l25.617 66.724h19.614l25.617-66.724h-15.808zM591.98 76.466c-19.112 0-34.239 15.706-34.239 35.079 0 21.416 14.641 35.079 36.239 35.079 12.088 0 19.806-4.622 29.234-14.688l-10.544-8.158c-.006.008-7.958 10.449-19.832 10.449-13.802 0-19.612-11.127-19.612-16.884h51.777c2.72-22.043-11.772-40.877-33.023-40.877zm-18.713 29.28c.12-1.284 1.917-16.884 18.589-16.884 16.671 0 18.697 15.598 18.813 16.884h-37.402zM184.068 43.892c-.024-.088-.073-.165-.104-.25-.058-.157-.108-.316-.191-.46-.056-.097-.137-.176-.203-.265-.087-.117-.161-.242-.265-.345-.085-.086-.194-.148-.29-.223-.109-.085-.206-.182-.327-.252l-.002-.001-.002-.002-35.648-20.524a2.971 2.971 0 00-2.964 0l-35.647 20.522-.002.002-.002.001c-.121.07-.219.167-.327.252-.096.075-.205.138-.29.223-.103.103-.178.228-.265.345-.066.089-.147.169-.203.265-.083.144-.133.304-.191.46-.031.085-.08.162-.104.25-.067.249-.103.51-.103.776v38.979l-29.706 17.103V24.493a3 3 0 00-.103-.776c-.024-.088-.073-.165-.104-.25-.058-.157-.108-.316-.191-.46-.056-.097-.137-.176-.203-.265-.087-.117-.161-.242-.265-.345-.085-.086-.194-.148-.29-.223-.109-.085-.206-.182-.327-.252l-.002-.001-.002-.002L40.098 1.396a2.971 2.971 0 00-2.964 0L1.487 21.919l-.002.002-.002.001c-.121.07-.219.167-.327.252-.096.075-.205.138-.29.223-.103.103-.178.228-.265.345-.066.089-.147.169-.203.265-.083.144-.133.304-.191.46-.031.085-.08.162-.104.25-.067.249-.103.51-.103.776v122.09c0 1.063.568 2.044 1.489 2.575l71.293 41.045c.156.089.324.143.49.202.078.028.15.074.23.095a2.98 2.98 0 001.524 0c.069-.018.132-.059.2-.083.176-.061.354-.119.519-.214l71.293-41.045a2.971 2.971 0 001.489-2.575v-38.979l34.158-19.666a2.971 2.971 0 001.489-2.575V44.666a3.075 3.075 0 00-.106-.774zM74.255 143.167l-29.648-16.779 31.136-17.926.001-.001 34.164-19.669 29.674 17.084-21.772 12.428-43.555 24.863zm68.329-76.259v33.841l-12.475-7.182-17.231-9.92V49.806l12.475 7.182 17.231 9.92zm2.97-39.335l29.693 17.095-29.693 17.095-29.693-17.095 29.693-17.095zM54.06 114.089l-12.475 7.182V46.733l17.231-9.92 12.475-7.182v74.537l-17.231 9.921zM38.614 7.398l29.693 17.095-29.693 17.095L8.921 24.493 38.614 7.398zM5.938 29.632l12.475 7.182 17.231 9.92v79.676l.001.005-.001.006c0 .114.032.221.045.333.017.146.021.294.059.434l.002.007c.032.117.094.222.14.334.051.124.088.255.156.371a.036.036 0 00.004.009c.061.105.149.191.222.288.081.105.149.22.244.314l.008.01c.084.083.19.142.284.215.106.083.202.178.32.247l.013.005.011.008 34.139 19.321v34.175L5.939 144.867V29.632h-.001zm136.646 115.235l-65.352 37.625V148.31l48.399-27.628 16.953-9.677v33.862zm35.646-61.22l-29.706 17.102V66.908l17.231-9.92 12.475-7.182v33.841z"/>
</g>
</svg>
</div>
<div class="mt-8 bg-white dark:bg-gray-800 overflow-hidden shadow sm:rounded-lg">
<div class="grid grid-cols-1 md:grid-cols-2">
<div class="p-6">
<div class="flex items-center">
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-gray-500"><path d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path></svg>
<div class="ml-4 text-lg leading-7 font-semibold"><a href="https://laravel.com/docs" class="underline text-gray-900 dark:text-white">Documentation</a></div>
</div>
<div class="ml-12">
<div class="mt-2 text-gray-600 dark:text-gray-400 text-sm">
Laravel has wonderful, thorough documentation covering every aspect of the framework. Whether you are new to the framework or have previous experience with Laravel, we recommend reading all of the documentation from beginning to end.
</div>
</div>
</div>
<div class="p-6 border-t border-gray-200 dark:border-gray-700 md:border-t-0 md:border-l">
<div class="flex items-center">
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-gray-500"><path d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"></path><path d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
<div class="ml-4 text-lg leading-7 font-semibold"><a href="https://laracasts.com" class="underline text-gray-900 dark:text-white">Laracasts</a></div>
</div>
<div class="ml-12">
<div class="mt-2 text-gray-600 dark:text-gray-400 text-sm">
Laracasts offers thousands of video tutorials on Laravel, PHP, and JavaScript development. Check them out, see for yourself, and massively level up your development skills in the process.
</div>
</div>
</div>
<div class="p-6 border-t border-gray-200 dark:border-gray-700">
<div class="flex items-center">
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-gray-500"><path d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z"></path></svg>
<div class="ml-4 text-lg leading-7 font-semibold"><a href="https://laravel-news.com/" class="underline text-gray-900 dark:text-white">Laravel News</a></div>
</div>
<div class="ml-12">
<div class="mt-2 text-gray-600 dark:text-gray-400 text-sm">
Laravel News is a community driven portal and newsletter aggregating all of the latest and most important news in the Laravel ecosystem, including new package releases and tutorials.
</div>
</div>
</div>
<div class="p-6 border-t border-gray-200 dark:border-gray-700 md:border-l">
<div class="flex items-center">
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-gray-500"><path d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
<div class="ml-4 text-lg leading-7 font-semibold text-gray-900 dark:text-white">Vibrant Ecosystem</div>
</div>
<div class="ml-12">
<div class="mt-2 text-gray-600 dark:text-gray-400 text-sm">
Laravel's robust library of first-party tools and libraries, such as <a href="https://forge.laravel.com" class="underline">Forge</a>, <a href="https://vapor.laravel.com" class="underline">Vapor</a>, <a href="https://nova.laravel.com" class="underline">Nova</a>, and <a href="https://envoyer.io" class="underline">Envoyer</a> help you take your projects to the next level. Pair them with powerful open source libraries like <a href="https://laravel.com/docs/billing" class="underline">Cashier</a>, <a href="https://laravel.com/docs/dusk" class="underline">Dusk</a>, <a href="https://laravel.com/docs/broadcasting" class="underline">Echo</a>, <a href="https://laravel.com/docs/horizon" class="underline">Horizon</a>, <a href="https://laravel.com/docs/sanctum" class="underline">Sanctum</a>, <a href="https://laravel.com/docs/telescope" class="underline">Telescope</a>, and more.
</div>
</div>
</div>
</div>
</div>
<div class="flex justify-center mt-4 sm:items-center sm:justify-between">
<div class="text-center text-sm text-gray-500 sm:text-left">
<div class="flex items-center">
<svg fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor" class="-mt-px w-5 h-5 text-gray-400">
<path d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
<a href="https://laravel.bigcartel.com" class="ml-1 underline">
Shop
</a>
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="ml-4 -mt-px w-5 h-5 text-gray-400">
<path d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"></path>
</svg>
<a href="https://github.com/sponsors/taylorotwell" class="ml-1 underline">
Sponsor
</a>
</div>
</div>
<div class="ml-4 text-center text-sm text-gray-500 sm:text-right sm:ml-0">
Laravel v{{ laravelVersion }} (PHP v{{ phpVersion }})
</div>
</div>
</div>
</div>
</template>
<style scoped>
.bg-gray-100 {
background-color: #f7fafc;
background-color: rgba(247, 250, 252, var(--tw-bg-opacity));
}
.border-gray-200 {
border-color: #edf2f7;
border-color: rgba(237, 242, 247, var(--tw-border-opacity));
}
.text-gray-400 {
color: #cbd5e0;
color: rgba(203, 213, 224, var(--tw-text-opacity));
}
.text-gray-500 {
color: #a0aec0;
color: rgba(160, 174, 192, var(--tw-text-opacity));
}
.text-gray-600 {
color: #718096;
color: rgba(113, 128, 150, var(--tw-text-opacity));
}
.text-gray-700 {
color: #4a5568;
color: rgba(74, 85, 104, var(--tw-text-opacity));
}
.text-gray-900 {
color: #1a202c;
color: rgba(26, 32, 44, var(--tw-text-opacity));
}
@media (prefers-color-scheme: dark) {
.dark\:bg-gray-800 {
background-color: #2d3748;
background-color: rgba(45, 55, 72, var(--tw-bg-opacity));
}
.dark\:bg-gray-900 {
background-color: #1a202c;
background-color: rgba(26, 32, 44, var(--tw-bg-opacity));
}
.dark\:border-gray-700 {
border-color: #4a5568;
border-color: rgba(74, 85, 104, var(--tw-border-opacity));
}
.dark\:text-white {
color: #fff;
color: rgba(255, 255, 255, var(--tw-text-opacity));
}
.dark\:text-gray-400 {
color: #cbd5e0;
color: rgba(203, 213, 224, var(--tw-text-opacity));
}
}
</style>
<script>
export default {
props: {
canLogin: Boolean,
canRegister: Boolean,
laravelVersion: String,
phpVersion: String,
}
}
</script>

10
resources/js/app.js vendored
View File

@ -4,6 +4,11 @@ require('./bootstrap');
import { createApp, h } from 'vue';
import { App as InertiaApp, plugin as InertiaPlugin } from '@inertiajs/inertia-vue3';
import { InertiaProgress } from '@inertiajs/progress';
import Unicon from 'vue-unicons';
import { uniUsersAlt, uniCarSideview, uniDashboard, uniSearch, uniFilter, uniFilterSlash, uniTrashAlt, uniPen, uniExclamationTriangle, uniMapMarker, uniPhone, uniEnvelope, uniFileDownload, uniArrowDown, uniArrowUp, uniAngleRight, uniAngleUp, uniAngleDown, uniAngleLeft, uniFileUploadAlt } from 'vue-unicons/dist/icons'
Unicon.add([uniUsersAlt, uniCarSideview, uniDashboard, uniSearch, uniFilter, uniFilterSlash, uniTrashAlt, uniPen, uniExclamationTriangle, uniMapMarker, uniPhone, uniEnvelope, uniFileDownload, uniArrowDown, uniArrowUp, uniAngleRight, uniAngleUp, uniAngleDown, uniAngleLeft, uniFileUploadAlt])
const el = document.getElementById('app');
@ -16,6 +21,11 @@ createApp({
})
.mixin({ methods: { route } })
.use(InertiaPlugin)
.use(Unicon, {
fill: '#4B5563',
height: 32,
width: 32
})
.mount(el);
InertiaProgress.init({ color: '#4B5563' });

View File

@ -13,7 +13,7 @@ return [
|
*/
'previous' => '&laquo; Previous',
'next' => 'Next &raquo;',
'previous' => '&laquo; Zurück',
'next' => 'Weiter &raquo;',
];

View File

@ -1,6 +1,8 @@
<?php
use Illuminate\Foundation\Application;
use App\Http\Controllers\ContactController;
use App\Http\Controllers\CarController;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
@ -16,7 +18,7 @@ use Inertia\Inertia;
*/
Route::get('/', function () {
return Inertia::render('Welcome', [
return Inertia::render('Dashboard', [
'canLogin' => Route::has('login'),
'canRegister' => Route::has('register'),
'laravelVersion' => Application::VERSION,
@ -28,30 +30,38 @@ Route::middleware(['auth:sanctum', 'verified'])->get('/dashboard', function () {
return Inertia::render('Dashboard');
})->name('dashboard');
Route::get('contacts', [ContactsController::class, 'index'])
Route::get('contacts', [ContactController::class, 'index'])
->name('contacts')
->middleware(['auth:sanctum', 'verified']);
Route::get('contacts/create', [ContactsController::class, 'create'])
Route::get('contacts/create', [ContactController::class, 'create'])
->name('contacts.create')
->middleware(['auth:sanctum', 'verified']);
Route::post('contacts', [ContactsController::class, 'store'])
Route::post('contacts', [ContactController::class, 'store'])
->name('contacts.store')
->middleware(['auth:sanctum', 'verified']);
Route::get('contacts/{contact}/edit', [ContactsController::class, 'edit'])
Route::get('contacts/{contact}/edit', [ContactController::class, 'edit'])
->name('contacts.edit')
->middleware(['auth:sanctum', 'verified']);
Route::put('contacts/{contact}', [ContactsController::class, 'update'])
Route::put('contacts/{contact}', [ContactController::class, 'update'])
->name('contacts.update')
->middleware(['auth:sanctum', 'verified']);
Route::delete('contacts/{contact}', [ContactsController::class, 'destroy'])
Route::delete('contacts/{contact}', [ContactController::class, 'destroy'])
->name('contacts.destroy')
->middleware(['auth:sanctum', 'verified']);
Route::put('contacts/{contact}/restore', [ContactsController::class, 'restore'])
Route::put('contacts/{contact}/restore', [ContactController::class, 'restore'])
->name('contacts.restore')
->middleware(['auth:sanctum', 'verified']);
Route::get('cars', [CarController::class, 'index'])
->name('cars')
->middleware(['auth:sanctum', 'verified']);
Route::get('cars/{car}/edit', [CarController::class, 'edit'])
->name('cars.edit')
->middleware(['auth:sanctum', 'verified']);