Merge pull request #99 from inertiajs/upgrade

Upgrade Ping CRM to latest version of Inertia.js
This commit is contained in:
Jonathan Reinink 2021-02-27 11:37:26 -05:00 committed by GitHub
commit 57593efb25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 5612 additions and 4966 deletions

39
.eslintrc.js vendored
View File

@ -1,15 +1,32 @@
module.exports = {
extends: [
'eslint:recommended',
'plugin:vue/recommended',
],
extends: ['eslint:recommended', 'plugin:vue/recommended'],
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
},
env: {
amd: true,
browser: true,
es6: true,
},
rules: {
"indent": ['error', 2],
'quotes': ['warn', 'single'],
'semi': ['warn', 'never'],
indent: ['error', 2],
quotes: ['warn', 'single'],
semi: ['warn', 'never'],
'no-unused-vars': ['error', { vars: 'all', args: 'after-used', ignoreRestSiblings: true }],
'comma-dangle': ['warn', 'always-multiline'],
'vue/max-attributes-per-line': false,
'vue/require-default-prop': false,
'vue/singleline-html-element-content-newline': false,
}
'vue/max-attributes-per-line': 'off',
'vue/require-default-prop': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/html-self-closing': [
'warn',
{
html: {
void: 'always',
normal: 'always',
component: 'always',
},
},
],
},
}

8
.prettierrc Normal file
View File

@ -0,0 +1,8 @@
{
"printWidth": 10000,
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"htmlWhitespaceSensitivity": "css"
}

View File

@ -20,7 +20,7 @@ class ContactsController extends Controller
->orderByName()
->filter(Request::only('search', 'trashed'))
->paginate()
->transform(function ($contact) {
->through(function ($contact) {
return [
'id' => $contact->id,
'name' => $contact->name,

View File

@ -18,7 +18,15 @@ class OrganizationsController extends Controller
->orderBy('name')
->filter(Request::only('search', 'trashed'))
->paginate()
->only('id', 'name', 'phone', 'city', 'deleted_at'),
->through(function ($organization) {
return [
'id' => $organization->id,
'name' => $organization->name,
'phone' => $organization->phone,
'city' => $organization->city,
'deleted_at' => $organization->deleted_at,
];
}),
]);
}

View File

@ -44,7 +44,7 @@ class HandleInertiaRequests extends Middleware
'first_name' => $request->user()->first_name,
'last_name' => $request->user()->last_name,
'email' => $request->user()->email,
'role' => $request->user()->role,
'owner' => $request->user()->owner,
'account' => [
'id' => $request->user()->account->id,
'name' => $request->user()->account->name,

View File

@ -2,10 +2,6 @@
namespace App\Providers;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\UrlWindow;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\ServiceProvider;
use League\Glide\Server;
@ -18,12 +14,6 @@ class AppServiceProvider extends ServiceProvider
* @return void
*/
public function register()
{
$this->registerGlide();
$this->registerLengthAwarePaginator();
}
protected function registerGlide()
{
$this->app->bind(Server::class, function ($app) {
return Server::create([
@ -35,75 +25,13 @@ class AppServiceProvider extends ServiceProvider
});
}
protected function registerLengthAwarePaginator()
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
$this->app->bind(LengthAwarePaginator::class, function ($app, $values) {
return new class(...array_values($values)) extends LengthAwarePaginator {
public function only(...$attributes)
{
return $this->transform(function ($item) use ($attributes) {
return $item->only($attributes);
});
}
public function transform($callback)
{
$this->items->transform($callback);
return $this;
}
public function toArray()
{
return [
'data' => $this->items->toArray(),
'links' => $this->links(),
];
}
public function links($view = null, $data = [])
{
$this->appends(Request::all());
$window = UrlWindow::make($this);
$elements = array_filter([
$window['first'],
is_array($window['slider']) ? '...' : null,
$window['slider'],
is_array($window['last']) ? '...' : null,
$window['last'],
]);
return Collection::make($elements)->flatMap(function ($item) {
if (is_array($item)) {
return Collection::make($item)->map(function ($url, $page) {
return [
'url' => $url,
'label' => $page,
'active' => $this->currentPage() === $page,
];
});
} else {
return [
[
'url' => null,
'label' => '...',
'active' => false,
],
];
}
})->prepend([
'url' => $this->previousPageUrl(),
'label' => 'Previous',
'active' => false,
])->push([
'url' => $this->nextPageUrl(),
'label' => 'Next',
'active' => false,
]);
}
};
});
//
}
}

View File

@ -8,7 +8,7 @@
],
"license": "MIT",
"require": {
"php": "^7.3.0",
"php": "^7.3|^8.0",
"ext-exif": "*",
"ext-gd": "*",
"facade/ignition": "^2.3.6",

1809
composer.lock generated

File diff suppressed because it is too large Load Diff

7948
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,31 +2,31 @@
"private": true,
"scripts": {
"dev": "npm run development",
"development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch": "npm run development -- --watch",
"watch-poll": "npm run watch -- --watch-poll",
"hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
"development": "mix",
"watch": "mix watch",
"watch-poll": "mix watch -- --watch-options-poll=1000",
"hot": "mix watch --hot",
"prod": "npm run production",
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
"production": "mix --production"
},
"dependencies": {
"@fullhuman/postcss-purgecss": "^1.3.0",
"@inertiajs/inertia": "^0.3.1",
"@inertiajs/inertia-vue": "^0.2.3",
"@inertiajs/progress": "^0.1.0",
"cross-env": "^5.2.1",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.2.3",
"laravel-mix": "^5.0.1",
"lodash": "^4.17.15",
"@inertiajs/inertia": "^0.8.5",
"@inertiajs/inertia-vue": "^0.5.5",
"@inertiajs/progress": "^0.2.4",
"autoprefixer": "^10.2.4",
"eslint": "^6.8.0",
"eslint-plugin-vue": "^7.5.0",
"laravel-mix": "^6.0.6",
"lodash": "^4.17.21",
"popper.js": "^1.16.0",
"portal-vue": "^1.5.1",
"postcss": "^8.2.6",
"postcss-import": "^12.0.1",
"postcss-nesting": "^7.0.1",
"resolve-url-loader": "^2.3.2",
"tailwindcss": "^1.2.0-canary.5",
"tailwindcss": "^2.0.3",
"vue": "^2.6.11",
"vue-loader": "^15.9.6",
"vue-meta": "^2.3.1",
"vue-template-compiler": "^2.6.11"
"vue-template-compiler": "^2.6.12"
}
}

12
resources/css/app.css vendored
View File

@ -1,11 +1,11 @@
/* Reset */
@import "tailwindcss/base";
@import "reset";
@import 'tailwindcss/base';
@import 'reset';
/* Components */
@import "tailwindcss/components";
@import "buttons";
@import "form";
@import 'tailwindcss/components';
@import 'buttons';
@import 'form';
/* Utilities */
@import "tailwindcss/utilities";
@import 'tailwindcss/utilities';

View File

@ -1,7 +1,5 @@
.btn-indigo {
@apply px-6 py-3 rounded bg-indigo-600 text-white text-sm font-bold whitespace-no-wrap;
&:hover, &:focus { @apply bg-orange-500 }
@apply px-6 py-3 rounded bg-indigo-600 text-white text-sm leading-4 font-bold whitespace-nowrap hover:bg-orange-400 focus:bg-orange-400;
}
.btn-spinner,
@ -15,15 +13,19 @@
font-size: 10px;
position: relative;
text-indent: -9999em;
border-top: .2em solid white;
border-right: .2em solid white;
border-bottom: .2em solid white;
border-left: .2em solid transparent;
border-top: 0.2em solid white;
border-right: 0.2em solid white;
border-bottom: 0.2em solid white;
border-left: 0.2em solid transparent;
transform: translateZ(0);
animation: spinning 1s infinite linear;
}
@keyframes spinning {
0% { transform: rotate(0deg) }
100% { transform: rotate(360deg) }
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View File

@ -1,25 +1,19 @@
.form-label {
@apply .mb-2 .block .text-gray-700 .select-none;
@apply mb-2 block text-gray-700 select-none;
}
.form-input,
.form-textarea,
.form-select {
@apply .p-2 .leading-normal .block .w-full .border .text-gray-700 .bg-white .font-sans .rounded .text-left .appearance-none .relative;
&:focus,
&.focus {
@apply .border-indigo-500;
box-shadow: 0 0 0 1px theme('colors.indigo.500');
}
@apply p-2 leading-normal block w-full border text-gray-700 bg-white font-sans rounded text-left appearance-none relative focus:border-indigo-400 focus:ring;
&::placeholder {
@apply .text-gray-500 .opacity-100;
@apply text-gray-500 opacity-100;
}
}
.form-select {
@apply .pr-6;
@apply pr-6;
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAQCAYAAAAMJL+VAAAABGdBTUEAALGPC/xhBQAAAQtJREFUOBG1lEEOgjAQRalbGj2OG9caOACn4ALGtfEuHACiazceR1PWOH/CNA3aMiTaBDpt/7zPdBKy7M/DCL9pGkvxxVp7KsvyJftL5rZt1865M+Ucq6pyyF3hNcI7Cuu+728QYn/JQA5yKaempxuZmQngOwEaYx55nu+1lQh8GIatMGi+01NwBcEmhxBqK4nAPZJ78K0KKFAJmR3oPp8+Iwgob0Oa6+TLoeCvRx+mTUYf/FVBGTPRwDkfLxnaSrRwcH0FWhNOmrkWYbE2XEicqgSa1J0LQ+aPCuQgZiLnwewbGuz5MGoAhcIkCQcjaTBjMgtXGURMVHC1wcQEy0J+Zlj8bKAnY1/UzDe2dbAVqfXn6wAAAABJRU5ErkJggg==');
background-size: 0.7rem;
@ -27,20 +21,16 @@
background-position: right 0.7rem center;
&::-ms-expand {
@apply .opacity-0;
@apply opacity-0;
}
}
.form-error {
@apply .text-red-700 .mt-2 .text-sm;
}
.form-input.error,
.form-textarea.error,
.form-select.error {
@apply .border-red-600;
&:focus {
box-shadow: 0 0 0 1px theme('colors.red.600');
}
@apply border-red-500 focus:ring focus:ring-red-200;
}
.form-error {
@apply text-red-700 mt-2 text-sm;
}

View File

@ -2,20 +2,20 @@
<div class="p-6 bg-indigo-800 min-h-screen flex justify-center items-center">
<div class="w-full max-w-md">
<logo class="block mx-auto w-full max-w-xs fill-white" height="50" />
<form class="mt-8 bg-white rounded-lg shadow-xl overflow-hidden" @submit.prevent="submit">
<form class="mt-8 bg-white rounded-lg shadow-xl overflow-hidden" @submit.prevent="login">
<div class="px-10 py-12">
<h1 class="text-center font-bold text-3xl">Welcome Back!</h1>
<div class="mx-auto mt-6 w-24 border-b-2" />
<text-input v-model="form.email" :error="errors.email" class="mt-10" label="Email" type="email" autofocus autocapitalize="off" />
<text-input v-model="form.password" class="mt-6" label="Password" type="password" />
<text-input v-model="form.email" :error="form.errors.email" class="mt-10" label="Email" type="email" autofocus autocapitalize="off" />
<text-input v-model="form.password" :error="form.errors.password" class="mt-6" label="Password" type="password" />
<label class="mt-6 select-none flex items-center" for="remember">
<input id="remember" v-model="form.remember" class="mr-1" type="checkbox">
<input id="remember" v-model="form.remember" class="mr-1" type="checkbox" />
<span class="text-sm">Remember Me</span>
</label>
</div>
<div class="px-10 py-4 bg-gray-100 border-t border-gray-200 flex justify-between items-center">
<div class="px-10 py-4 bg-gray-100 border-t border-gray-100 flex justify-between items-center">
<a class="hover:underline" tabindex="-1" href="#reset-password">Forget password?</a>
<loading-button :loading="sending" class="btn-indigo" type="submit">Login</loading-button>
<loading-button :loading="form.processing" class="btn-indigo" type="submit">Login</loading-button>
</div>
</form>
</div>
@ -23,9 +23,9 @@
</template>
<script>
import LoadingButton from '@/Shared/LoadingButton'
import Logo from '@/Shared/Logo'
import TextInput from '@/Shared/TextInput'
import LoadingButton from '@/Shared/LoadingButton'
export default {
metaInfo: { title: 'Login' },
@ -34,31 +34,23 @@ export default {
Logo,
TextInput,
},
props: {
errors: Object,
},
data() {
return {
sending: false,
form: {
form: this.$inertia.form({
email: 'johndoe@example.com',
password: 'secret',
remember: null,
},
remember: false,
}),
}
},
methods: {
submit() {
const data = {
email: this.form.email,
password: this.form.password,
remember: this.form.remember,
}
this.$inertia.post(this.route('login.attempt'), data, {
onStart: () => this.sending = true,
onFinish: () => this.sending = false,
})
login() {
this.form
.transform(data => ({
...data,
remember: data.remember ? 'on' : '',
}))
.post(this.route('login.attempt'))
},
},
}

View File

@ -4,29 +4,29 @@
<inertia-link class="text-indigo-400 hover:text-indigo-600" :href="route('contacts')">Contacts</inertia-link>
<span class="text-indigo-400 font-medium">/</span> Create
</h1>
<div class="bg-white rounded shadow overflow-hidden max-w-3xl">
<form @submit.prevent="submit">
<div class="bg-white rounded-md shadow overflow-hidden max-w-3xl">
<form @submit.prevent="store">
<div class="p-8 -mr-6 -mb-8 flex flex-wrap">
<text-input v-model="form.first_name" :error="errors.first_name" class="pr-6 pb-8 w-full lg:w-1/2" label="First name" />
<text-input v-model="form.last_name" :error="errors.last_name" class="pr-6 pb-8 w-full lg:w-1/2" label="Last name" />
<select-input v-model="form.organization_id" :error="errors.organization_id" class="pr-6 pb-8 w-full lg:w-1/2" label="Organization">
<text-input v-model="form.first_name" :error="form.errors.first_name" class="pr-6 pb-8 w-full lg:w-1/2" label="First name" />
<text-input v-model="form.last_name" :error="form.errors.last_name" class="pr-6 pb-8 w-full lg:w-1/2" label="Last name" />
<select-input v-model="form.organization_id" :error="form.errors.organization_id" class="pr-6 pb-8 w-full lg:w-1/2" label="Organization">
<option :value="null" />
<option v-for="organization in organizations" :key="organization.id" :value="organization.id">{{ organization.name }}</option>
</select-input>
<text-input v-model="form.email" :error="errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" />
<text-input v-model="form.phone" :error="errors.phone" class="pr-6 pb-8 w-full lg:w-1/2" label="Phone" />
<text-input v-model="form.address" :error="errors.address" class="pr-6 pb-8 w-full lg:w-1/2" label="Address" />
<text-input v-model="form.city" :error="errors.city" class="pr-6 pb-8 w-full lg:w-1/2" label="City" />
<text-input v-model="form.region" :error="errors.region" class="pr-6 pb-8 w-full lg:w-1/2" label="Province/State" />
<select-input v-model="form.country" :error="errors.country" class="pr-6 pb-8 w-full lg:w-1/2" label="Country">
<text-input v-model="form.email" :error="form.errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" />
<text-input v-model="form.phone" :error="form.errors.phone" class="pr-6 pb-8 w-full lg:w-1/2" label="Phone" />
<text-input v-model="form.address" :error="form.errors.address" class="pr-6 pb-8 w-full lg:w-1/2" label="Address" />
<text-input v-model="form.city" :error="form.errors.city" class="pr-6 pb-8 w-full lg:w-1/2" label="City" />
<text-input v-model="form.region" :error="form.errors.region" class="pr-6 pb-8 w-full lg:w-1/2" label="Province/State" />
<select-input v-model="form.country" :error="form.errors.country" class="pr-6 pb-8 w-full lg:w-1/2" label="Country">
<option :value="null" />
<option value="CA">Canada</option>
<option value="US">United States</option>
</select-input>
<text-input v-model="form.postal_code" :error="errors.postal_code" class="pr-6 pb-8 w-full lg:w-1/2" label="Postal code" />
<text-input v-model="form.postal_code" :error="form.errors.postal_code" class="pr-6 pb-8 w-full lg:w-1/2" label="Postal code" />
</div>
<div class="px-8 py-4 bg-gray-100 border-t border-gray-200 flex justify-end items-center">
<loading-button :loading="sending" class="btn-indigo" type="submit">Create Contact</loading-button>
<div class="px-8 py-4 bg-gray-50 border-t border-gray-100 flex justify-end items-center">
<loading-button :loading="form.processing" class="btn-indigo" type="submit">Create Contact</loading-button>
</div>
</form>
</div>
@ -35,27 +35,25 @@
<script>
import Layout from '@/Shared/Layout'
import LoadingButton from '@/Shared/LoadingButton'
import SelectInput from '@/Shared/SelectInput'
import TextInput from '@/Shared/TextInput'
import SelectInput from '@/Shared/SelectInput'
import LoadingButton from '@/Shared/LoadingButton'
export default {
metaInfo: { title: 'Create Contact' },
layout: Layout,
components: {
LoadingButton,
SelectInput,
TextInput,
},
layout: Layout,
props: {
errors: Object,
organizations: Array,
},
remember: 'form',
data() {
return {
sending: false,
form: {
form: this.$inertia.form({
first_name: null,
last_name: null,
organization_id: null,
@ -66,15 +64,12 @@ export default {
region: null,
country: null,
postal_code: null,
},
}),
}
},
methods: {
submit() {
this.$inertia.post(this.route('contacts.store'), this.form, {
onStart: () => this.sending = true,
onFinish: () => this.sending = false,
})
store() {
this.form.post(this.route('contacts.store'))
},
},
}

View File

@ -8,30 +8,30 @@
<trashed-message v-if="contact.deleted_at" class="mb-6" @restore="restore">
This contact has been deleted.
</trashed-message>
<div class="bg-white rounded shadow overflow-hidden max-w-3xl">
<form @submit.prevent="submit">
<div class="bg-white rounded-md shadow overflow-hidden max-w-3xl">
<form @submit.prevent="update">
<div class="p-8 -mr-6 -mb-8 flex flex-wrap">
<text-input v-model="form.first_name" :error="errors.first_name" class="pr-6 pb-8 w-full lg:w-1/2" label="First name" />
<text-input v-model="form.last_name" :error="errors.last_name" class="pr-6 pb-8 w-full lg:w-1/2" label="Last name" />
<select-input v-model="form.organization_id" :error="errors.organization_id" class="pr-6 pb-8 w-full lg:w-1/2" label="Organization">
<text-input v-model="form.first_name" :error="form.errors.first_name" class="pr-6 pb-8 w-full lg:w-1/2" label="First name" />
<text-input v-model="form.last_name" :error="form.errors.last_name" class="pr-6 pb-8 w-full lg:w-1/2" label="Last name" />
<select-input v-model="form.organization_id" :error="form.errors.organization_id" class="pr-6 pb-8 w-full lg:w-1/2" label="Organization">
<option :value="null" />
<option v-for="organization in organizations" :key="organization.id" :value="organization.id">{{ organization.name }}</option>
</select-input>
<text-input v-model="form.email" :error="errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" />
<text-input v-model="form.phone" :error="errors.phone" class="pr-6 pb-8 w-full lg:w-1/2" label="Phone" />
<text-input v-model="form.address" :error="errors.address" class="pr-6 pb-8 w-full lg:w-1/2" label="Address" />
<text-input v-model="form.city" :error="errors.city" class="pr-6 pb-8 w-full lg:w-1/2" label="City" />
<text-input v-model="form.region" :error="errors.region" class="pr-6 pb-8 w-full lg:w-1/2" label="Province/State" />
<select-input v-model="form.country" :error="errors.country" class="pr-6 pb-8 w-full lg:w-1/2" label="Country">
<text-input v-model="form.email" :error="form.errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" />
<text-input v-model="form.phone" :error="form.errors.phone" class="pr-6 pb-8 w-full lg:w-1/2" label="Phone" />
<text-input v-model="form.address" :error="form.errors.address" class="pr-6 pb-8 w-full lg:w-1/2" label="Address" />
<text-input v-model="form.city" :error="form.errors.city" class="pr-6 pb-8 w-full lg:w-1/2" label="City" />
<text-input v-model="form.region" :error="form.errors.region" class="pr-6 pb-8 w-full lg:w-1/2" label="Province/State" />
<select-input v-model="form.country" :error="form.errors.country" class="pr-6 pb-8 w-full lg:w-1/2" label="Country">
<option :value="null" />
<option value="CA">Canada</option>
<option value="US">United States</option>
</select-input>
<text-input v-model="form.postal_code" :error="errors.postal_code" class="pr-6 pb-8 w-full lg:w-1/2" label="Postal code" />
<text-input v-model="form.postal_code" :error="form.errors.postal_code" class="pr-6 pb-8 w-full lg:w-1/2" label="Postal code" />
</div>
<div class="px-8 py-4 bg-gray-100 border-t border-gray-200 flex items-center">
<div class="px-8 py-4 bg-gray-50 border-t border-gray-100 flex items-center">
<button v-if="!contact.deleted_at" class="text-red-600 hover:underline" tabindex="-1" type="button" @click="destroy">Delete Contact</button>
<loading-button :loading="sending" class="btn-indigo ml-auto" type="submit">Update Contact</loading-button>
<loading-button :loading="form.processing" class="btn-indigo ml-auto" type="submit">Update Contact</loading-button>
</div>
</form>
</div>
@ -40,9 +40,9 @@
<script>
import Layout from '@/Shared/Layout'
import LoadingButton from '@/Shared/LoadingButton'
import SelectInput from '@/Shared/SelectInput'
import TextInput from '@/Shared/TextInput'
import SelectInput from '@/Shared/SelectInput'
import LoadingButton from '@/Shared/LoadingButton'
import TrashedMessage from '@/Shared/TrashedMessage'
export default {
@ -51,13 +51,13 @@ export default {
title: `${this.form.first_name} ${this.form.last_name}`,
}
},
layout: Layout,
components: {
LoadingButton,
SelectInput,
TextInput,
TrashedMessage,
},
layout: Layout,
props: {
errors: Object,
contact: Object,
@ -66,8 +66,7 @@ export default {
remember: 'form',
data() {
return {
sending: false,
form: {
form: this.$inertia.form({
first_name: this.contact.first_name,
last_name: this.contact.last_name,
organization_id: this.contact.organization_id,
@ -78,15 +77,12 @@ export default {
region: this.contact.region,
country: this.contact.country,
postal_code: this.contact.postal_code,
},
}),
}
},
methods: {
submit() {
this.$inertia.put(this.route('contacts.update', this.contact.id), this.form, {
onStart: () => this.sending = true,
onFinish: () => this.sending = false,
})
update() {
this.form.put(this.route('contacts.update', this.contact.id))
},
destroy() {
if (confirm('Are you sure you want to delete this contact?')) {

View File

@ -15,8 +15,8 @@
<span class="hidden md:inline">Contact</span>
</inertia-link>
</div>
<div class="bg-white rounded shadow overflow-x-auto">
<table class="w-full whitespace-no-wrap">
<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>
@ -58,27 +58,27 @@
</tr>
</table>
</div>
<pagination :links="contacts.links" />
<pagination class="mt-6" :links="contacts.links" />
</div>
</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 pickBy from 'lodash/pickBy'
import SearchFilter from '@/Shared/SearchFilter'
import throttle from 'lodash/throttle'
export default {
metaInfo: { title: 'Contacts' },
layout: Layout,
components: {
Icon,
Pagination,
SearchFilter,
},
layout: Layout,
props: {
contacts: Object,
filters: Object,

View File

@ -4,24 +4,24 @@
<inertia-link class="text-indigo-400 hover:text-indigo-600" :href="route('organizations')">Organizations</inertia-link>
<span class="text-indigo-400 font-medium">/</span> Create
</h1>
<div class="bg-white rounded shadow overflow-hidden max-w-3xl">
<form @submit.prevent="submit">
<div class="bg-white rounded-md shadow overflow-hidden max-w-3xl">
<form @submit.prevent="store">
<div class="p-8 -mr-6 -mb-8 flex flex-wrap">
<text-input v-model="form.name" :error="errors.name" class="pr-6 pb-8 w-full lg:w-1/2" label="Name" />
<text-input v-model="form.email" :error="errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" />
<text-input v-model="form.phone" :error="errors.phone" class="pr-6 pb-8 w-full lg:w-1/2" label="Phone" />
<text-input v-model="form.address" :error="errors.address" class="pr-6 pb-8 w-full lg:w-1/2" label="Address" />
<text-input v-model="form.city" :error="errors.city" class="pr-6 pb-8 w-full lg:w-1/2" label="City" />
<text-input v-model="form.region" :error="errors.region" class="pr-6 pb-8 w-full lg:w-1/2" label="Province/State" />
<select-input v-model="form.country" :error="errors.country" class="pr-6 pb-8 w-full lg:w-1/2" label="Country">
<text-input v-model="form.name" :error="form.errors.name" class="pr-6 pb-8 w-full lg:w-1/2" label="Name" />
<text-input v-model="form.email" :error="form.errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" />
<text-input v-model="form.phone" :error="form.errors.phone" class="pr-6 pb-8 w-full lg:w-1/2" label="Phone" />
<text-input v-model="form.address" :error="form.errors.address" class="pr-6 pb-8 w-full lg:w-1/2" label="Address" />
<text-input v-model="form.city" :error="form.errors.city" class="pr-6 pb-8 w-full lg:w-1/2" label="City" />
<text-input v-model="form.region" :error="form.errors.region" class="pr-6 pb-8 w-full lg:w-1/2" label="Province/State" />
<select-input v-model="form.country" :error="form.errors.country" class="pr-6 pb-8 w-full lg:w-1/2" label="Country">
<option :value="null" />
<option value="CA">Canada</option>
<option value="US">United States</option>
</select-input>
<text-input v-model="form.postal_code" :error="errors.postal_code" class="pr-6 pb-8 w-full lg:w-1/2" label="Postal code" />
<text-input v-model="form.postal_code" :error="form.errors.postal_code" class="pr-6 pb-8 w-full lg:w-1/2" label="Postal code" />
</div>
<div class="px-8 py-4 bg-gray-100 border-t border-gray-200 flex justify-end items-center">
<loading-button :loading="sending" class="btn-indigo" type="submit">Create Organization</loading-button>
<div class="px-8 py-4 bg-gray-50 border-t border-gray-100 flex justify-end items-center">
<loading-button :loading="form.processing" class="btn-indigo" type="submit">Create Organization</loading-button>
</div>
</form>
</div>
@ -30,26 +30,22 @@
<script>
import Layout from '@/Shared/Layout'
import LoadingButton from '@/Shared/LoadingButton'
import SelectInput from '@/Shared/SelectInput'
import TextInput from '@/Shared/TextInput'
import SelectInput from '@/Shared/SelectInput'
import LoadingButton from '@/Shared/LoadingButton'
export default {
metaInfo: { title: 'Create Organization' },
layout: Layout,
components: {
LoadingButton,
SelectInput,
TextInput,
},
props: {
errors: Object,
},
layout: Layout,
remember: 'form',
data() {
return {
sending: false,
form: {
form: this.$inertia.form({
name: null,
email: null,
phone: null,
@ -58,15 +54,12 @@ export default {
region: null,
country: null,
postal_code: null,
},
}),
}
},
methods: {
submit() {
this.$inertia.post(this.route('organizations.store'), this.form, {
onStart: () => this.sending = true,
onFinish: () => this.sending = false,
})
store() {
this.form.post(this.route('organizations.store'))
},
},
}

View File

@ -8,31 +8,31 @@
<trashed-message v-if="organization.deleted_at" class="mb-6" @restore="restore">
This organization has been deleted.
</trashed-message>
<div class="bg-white rounded shadow overflow-hidden max-w-3xl">
<form @submit.prevent="submit">
<div class="bg-white rounded-md shadow overflow-hidden max-w-3xl">
<form @submit.prevent="update">
<div class="p-8 -mr-6 -mb-8 flex flex-wrap">
<text-input v-model="form.name" :error="errors.name" class="pr-6 pb-8 w-full lg:w-1/2" label="Name" />
<text-input v-model="form.email" :error="errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" />
<text-input v-model="form.phone" :error="errors.phone" class="pr-6 pb-8 w-full lg:w-1/2" label="Phone" />
<text-input v-model="form.address" :error="errors.address" class="pr-6 pb-8 w-full lg:w-1/2" label="Address" />
<text-input v-model="form.city" :error="errors.city" class="pr-6 pb-8 w-full lg:w-1/2" label="City" />
<text-input v-model="form.region" :error="errors.region" class="pr-6 pb-8 w-full lg:w-1/2" label="Province/State" />
<select-input v-model="form.country" :error="errors.country" class="pr-6 pb-8 w-full lg:w-1/2" label="Country">
<text-input v-model="form.name" :error="form.errors.name" class="pr-6 pb-8 w-full lg:w-1/2" label="Name" />
<text-input v-model="form.email" :error="form.errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" />
<text-input v-model="form.phone" :error="form.errors.phone" class="pr-6 pb-8 w-full lg:w-1/2" label="Phone" />
<text-input v-model="form.address" :error="form.errors.address" class="pr-6 pb-8 w-full lg:w-1/2" label="Address" />
<text-input v-model="form.city" :error="form.errors.city" class="pr-6 pb-8 w-full lg:w-1/2" label="City" />
<text-input v-model="form.region" :error="form.errors.region" class="pr-6 pb-8 w-full lg:w-1/2" label="Province/State" />
<select-input v-model="form.country" :error="form.errors.country" class="pr-6 pb-8 w-full lg:w-1/2" label="Country">
<option :value="null" />
<option value="CA">Canada</option>
<option value="US">United States</option>
</select-input>
<text-input v-model="form.postal_code" :error="errors.postal_code" class="pr-6 pb-8 w-full lg:w-1/2" label="Postal code" />
<text-input v-model="form.postal_code" :error="form.errors.postal_code" class="pr-6 pb-8 w-full lg:w-1/2" label="Postal code" />
</div>
<div class="px-8 py-4 bg-gray-100 border-t border-gray-200 flex items-center">
<div class="px-8 py-4 bg-gray-50 border-t border-gray-100 flex items-center">
<button v-if="!organization.deleted_at" class="text-red-600 hover:underline" tabindex="-1" type="button" @click="destroy">Delete Organization</button>
<loading-button :loading="sending" class="btn-indigo ml-auto" type="submit">Update Organization</loading-button>
<loading-button :loading="form.processing" class="btn-indigo ml-auto" type="submit">Update Organization</loading-button>
</div>
</form>
</div>
<h2 class="mt-12 font-bold text-2xl">Contacts</h2>
<div class="mt-6 bg-white rounded shadow overflow-x-auto">
<table class="w-full whitespace-no-wrap">
<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">City</th>
@ -72,16 +72,15 @@
<script>
import Icon from '@/Shared/Icon'
import Layout from '@/Shared/Layout'
import LoadingButton from '@/Shared/LoadingButton'
import SelectInput from '@/Shared/SelectInput'
import TextInput from '@/Shared/TextInput'
import SelectInput from '@/Shared/SelectInput'
import LoadingButton from '@/Shared/LoadingButton'
import TrashedMessage from '@/Shared/TrashedMessage'
export default {
metaInfo() {
return { title: this.form.name }
},
layout: Layout,
components: {
Icon,
LoadingButton,
@ -89,15 +88,14 @@ export default {
TextInput,
TrashedMessage,
},
layout: Layout,
props: {
errors: Object,
organization: Object,
},
remember: 'form',
data() {
return {
sending: false,
form: {
form: this.$inertia.form({
name: this.organization.name,
email: this.organization.email,
phone: this.organization.phone,
@ -106,15 +104,12 @@ export default {
region: this.organization.region,
country: this.organization.country,
postal_code: this.organization.postal_code,
},
}),
}
},
methods: {
submit() {
this.$inertia.put(this.route('organizations.update', this.organization.id), this.form, {
onStart: () => this.sending = true,
onFinish: () => this.sending = false,
})
update() {
this.form.put(this.route('organizations.update', this.organization.id))
},
destroy() {
if (confirm('Are you sure you want to delete this organization?')) {

View File

@ -15,8 +15,8 @@
<span class="hidden md:inline">Organization</span>
</inertia-link>
</div>
<div class="bg-white rounded shadow overflow-x-auto">
<table class="w-full whitespace-no-wrap">
<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">City</th>
@ -50,27 +50,27 @@
</tr>
</table>
</div>
<pagination :links="organizations.links" />
<pagination class="mt-6" :links="organizations.links" />
</div>
</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 pickBy from 'lodash/pickBy'
import SearchFilter from '@/Shared/SearchFilter'
import throttle from 'lodash/throttle'
export default {
metaInfo: { title: 'Organizations' },
layout: Layout,
components: {
Icon,
Pagination,
SearchFilter,
},
layout: Layout,
props: {
organizations: Object,
filters: Object,

View File

@ -4,21 +4,21 @@
<inertia-link class="text-indigo-400 hover:text-indigo-600" :href="route('users')">Users</inertia-link>
<span class="text-indigo-400 font-medium">/</span> Create
</h1>
<div class="bg-white rounded shadow overflow-hidden max-w-3xl">
<form @submit.prevent="submit">
<div class="bg-white rounded-md shadow overflow-hidden max-w-3xl">
<form @submit.prevent="store">
<div class="p-8 -mr-6 -mb-8 flex flex-wrap">
<text-input v-model="form.first_name" :error="errors.first_name" class="pr-6 pb-8 w-full lg:w-1/2" label="First name" />
<text-input v-model="form.last_name" :error="errors.last_name" class="pr-6 pb-8 w-full lg:w-1/2" label="Last name" />
<text-input v-model="form.email" :error="errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" />
<text-input v-model="form.password" :error="errors.password" class="pr-6 pb-8 w-full lg:w-1/2" type="password" autocomplete="new-password" label="Password" />
<select-input v-model="form.owner" :error="errors.owner" class="pr-6 pb-8 w-full lg:w-1/2" label="Owner">
<text-input v-model="form.first_name" :error="form.errors.first_name" class="pr-6 pb-8 w-full lg:w-1/2" label="First name" />
<text-input v-model="form.last_name" :error="form.errors.last_name" class="pr-6 pb-8 w-full lg:w-1/2" label="Last name" />
<text-input v-model="form.email" :error="form.errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" />
<text-input v-model="form.password" :error="form.errors.password" class="pr-6 pb-8 w-full lg:w-1/2" type="password" autocomplete="new-password" label="Password" />
<select-input v-model="form.owner" :error="form.errors.owner" class="pr-6 pb-8 w-full lg:w-1/2" label="Owner">
<option :value="true">Yes</option>
<option :value="false">No</option>
</select-input>
<file-input v-model="form.photo" :error="errors.photo" class="pr-6 pb-8 w-full lg:w-1/2" type="file" accept="image/*" label="Photo" />
<file-input v-model="form.photo" :error="form.errors.photo" class="pr-6 pb-8 w-full lg:w-1/2" type="file" accept="image/*" label="Photo" />
</div>
<div class="px-8 py-4 bg-gray-100 border-t border-gray-200 flex justify-end items-center">
<loading-button :loading="sending" class="btn-indigo" type="submit">Create User</loading-button>
<div class="px-8 py-4 bg-gray-50 border-t border-gray-100 flex justify-end items-center">
<loading-button :loading="form.processing" class="btn-indigo" type="submit">Create User</loading-button>
</div>
</form>
</div>
@ -27,51 +27,36 @@
<script>
import Layout from '@/Shared/Layout'
import LoadingButton from '@/Shared/LoadingButton'
import SelectInput from '@/Shared/SelectInput'
import TextInput from '@/Shared/TextInput'
import FileInput from '@/Shared/FileInput'
import TextInput from '@/Shared/TextInput'
import SelectInput from '@/Shared/SelectInput'
import LoadingButton from '@/Shared/LoadingButton'
export default {
metaInfo: { title: 'Create User' },
layout: Layout,
components: {
FileInput,
LoadingButton,
SelectInput,
TextInput,
FileInput,
},
props: {
errors: Object,
},
layout: Layout,
remember: 'form',
data() {
return {
sending: false,
form: {
form: this.$inertia.form({
first_name: null,
last_name: null,
email: null,
password: null,
owner: false,
photo: null,
},
}),
}
},
methods: {
submit() {
const data = new FormData()
data.append('first_name', this.form.first_name || '')
data.append('last_name', this.form.last_name || '')
data.append('email', this.form.email || '')
data.append('password', this.form.password || '')
data.append('owner', this.form.owner ? '1' : '0')
data.append('photo', this.form.photo || '')
this.$inertia.post(this.route('users.store'), data, {
onStart: () => this.sending = true,
onFinish: () => this.sending = false,
})
store() {
this.form.post(this.route('users.store'))
},
},
}

View File

@ -6,27 +6,27 @@
<span class="text-indigo-400 font-medium">/</span>
{{ form.first_name }} {{ form.last_name }}
</h1>
<img v-if="user.photo" class="block w-8 h-8 rounded-full ml-4" :src="user.photo">
<img v-if="user.photo" class="block w-8 h-8 rounded-full ml-4" :src="user.photo" />
</div>
<trashed-message v-if="user.deleted_at" class="mb-6" @restore="restore">
This user has been deleted.
</trashed-message>
<div class="bg-white rounded shadow overflow-hidden max-w-3xl">
<form @submit.prevent="submit">
<div class="bg-white rounded-md shadow overflow-hidden max-w-3xl">
<form @submit.prevent="update">
<div class="p-8 -mr-6 -mb-8 flex flex-wrap">
<text-input v-model="form.first_name" :error="errors.first_name" class="pr-6 pb-8 w-full lg:w-1/2" label="First name" />
<text-input v-model="form.last_name" :error="errors.last_name" class="pr-6 pb-8 w-full lg:w-1/2" label="Last name" />
<text-input v-model="form.email" :error="errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" />
<text-input v-model="form.password" :error="errors.password" class="pr-6 pb-8 w-full lg:w-1/2" type="password" autocomplete="new-password" label="Password" />
<select-input v-model="form.owner" :error="errors.owner" class="pr-6 pb-8 w-full lg:w-1/2" label="Owner">
<text-input v-model="form.first_name" :error="form.errors.first_name" class="pr-6 pb-8 w-full lg:w-1/2" label="First name" />
<text-input v-model="form.last_name" :error="form.errors.last_name" class="pr-6 pb-8 w-full lg:w-1/2" label="Last name" />
<text-input v-model="form.email" :error="form.errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" />
<text-input v-model="form.password" :error="form.errors.password" class="pr-6 pb-8 w-full lg:w-1/2" type="password" autocomplete="new-password" label="Password" />
<select-input v-model="form.owner" :error="form.errors.owner" class="pr-6 pb-8 w-full lg:w-1/2" label="Owner">
<option :value="true">Yes</option>
<option :value="false">No</option>
</select-input>
<file-input v-model="form.photo" :error="errors.photo" class="pr-6 pb-8 w-full lg:w-1/2" type="file" accept="image/*" label="Photo" />
<file-input v-model="form.photo" :error="form.errors.photo" class="pr-6 pb-8 w-full lg:w-1/2" type="file" accept="image/*" label="Photo" />
</div>
<div class="px-8 py-4 bg-gray-100 border-t border-gray-200 flex items-center">
<div class="px-8 py-4 bg-gray-50 border-t border-gray-100 flex items-center">
<button v-if="!user.deleted_at" class="text-red-600 hover:underline" tabindex="-1" type="button" @click="destroy">Delete User</button>
<loading-button :loading="sending" class="btn-indigo ml-auto" type="submit">Update User</loading-button>
<loading-button :loading="form.processing" class="btn-indigo ml-auto" type="submit">Update User</loading-button>
</div>
</form>
</div>
@ -35,10 +35,10 @@
<script>
import Layout from '@/Shared/Layout'
import LoadingButton from '@/Shared/LoadingButton'
import SelectInput from '@/Shared/SelectInput'
import TextInput from '@/Shared/TextInput'
import FileInput from '@/Shared/FileInput'
import SelectInput from '@/Shared/SelectInput'
import LoadingButton from '@/Shared/LoadingButton'
import TrashedMessage from '@/Shared/TrashedMessage'
export default {
@ -47,52 +47,35 @@ export default {
title: `${this.form.first_name} ${this.form.last_name}`,
}
},
layout: Layout,
components: {
FileInput,
LoadingButton,
SelectInput,
TextInput,
FileInput,
TrashedMessage,
},
layout: Layout,
props: {
errors: Object,
user: Object,
},
remember: 'form',
data() {
return {
sending: false,
form: {
form: this.$inertia.form({
_method: 'put',
first_name: this.user.first_name,
last_name: this.user.last_name,
email: this.user.email,
password: this.user.password,
password: null,
owner: this.user.owner,
photo: null,
},
}),
}
},
methods: {
submit() {
var data = new FormData()
data.append('first_name', this.form.first_name || '')
data.append('last_name', this.form.last_name || '')
data.append('email', this.form.email || '')
data.append('password', this.form.password || '')
data.append('owner', this.form.owner ? '1' : '0')
data.append('photo', this.form.photo || '')
data.append('_method', 'put')
this.$inertia.post(this.route('users.update', this.user.id), data, {
onStart: () => this.sending = true,
onFinish: () => this.sending = false,
onSuccess: () => {
if (Object.keys(this.$page.errors).length === 0) {
this.form.photo = null
this.form.password = null
}
},
update() {
this.form.post(this.route('users.update', this.user.id), {
onSuccess: () => this.form.reset('password', 'photo'),
})
},
destroy() {

View File

@ -21,8 +21,8 @@
<span class="hidden md:inline">User</span>
</inertia-link>
</div>
<div class="bg-white rounded shadow overflow-x-auto">
<table class="w-full whitespace-no-wrap">
<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">Email</th>
@ -31,7 +31,7 @@
<tr v-for="user in users" :key="user.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('users.edit', user.id)">
<img v-if="user.photo" class="block w-5 h-5 rounded-full mr-2 -my-2" :src="user.photo">
<img v-if="user.photo" class="block w-5 h-5 rounded-full mr-2 -my-2" :src="user.photo" />
{{ user.name }}
<icon v-if="user.deleted_at" name="trash" class="flex-shrink-0 w-3 h-3 fill-gray-400 ml-2" />
</inertia-link>
@ -62,19 +62,19 @@
<script>
import Icon from '@/Shared/Icon'
import Layout from '@/Shared/Layout'
import mapValues from 'lodash/mapValues'
import pickBy from 'lodash/pickBy'
import SearchFilter from '@/Shared/SearchFilter'
import Layout from '@/Shared/Layout'
import throttle from 'lodash/throttle'
import mapValues from 'lodash/mapValues'
import SearchFilter from '@/Shared/SearchFilter'
export default {
metaInfo: { title: 'Users' },
layout: Layout,
components: {
Icon,
SearchFilter,
},
layout: Layout,
props: {
users: Array,
filters: Object,

View File

@ -52,7 +52,7 @@ export default {
},
},
mounted() {
document.addEventListener('keydown', (e) => {
document.addEventListener('keydown', e => {
if (e.keyCode === 27) {
this.show = false
}

View File

@ -2,14 +2,16 @@
<div>
<label v-if="label" class="form-label">{{ label }}:</label>
<div class="form-input p-0" :class="{ error: errors.length }">
<input ref="file" type="file" :accept="accept" class="hidden" @change="change">
<input ref="file" type="file" :accept="accept" class="hidden" @change="change" />
<div v-if="!value" class="p-2">
<button type="button" class="px-4 py-1 bg-gray-500 hover:bg-gray-700 rounded-sm text-xs font-medium text-white" @click="browse">
Browse
</button>
</div>
<div v-else class="flex items-center justify-between p-2">
<div class="flex-1 pr-1">{{ value.name }} <span class="text-gray-500 text-xs">({{ filesize(value.size) }})</span></div>
<div class="flex-1 pr-1">
{{ value.name }} <span class="text-gray-500 text-xs">({{ filesize(value.size) }})</span>
</div>
<button type="button" class="px-4 py-1 bg-gray-500 hover:bg-gray-700 rounded-sm text-xs font-medium text-white" @click="remove">
Remove
</button>
@ -40,7 +42,7 @@ export default {
methods: {
filesize(size) {
var i = Math.floor(Math.log(size) / Math.log(1024))
return (size / Math.pow(1024, i) ).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]
return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]
},
browse() {
this.$refs.file.click()

View File

@ -1,21 +1,21 @@
<template>
<div>
<div v-if="$page.flash.success && show" class="mb-8 flex items-center justify-between bg-green-500 rounded max-w-3xl">
<div v-if="$page.props.flash.success && show" class="mb-8 flex items-center justify-between bg-green-500 rounded max-w-3xl">
<div class="flex items-center">
<svg class="ml-4 mr-2 flex-shrink-0 w-4 h-4 fill-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><polygon points="0 11 2 9 7 14 18 3 20 5 7 18" /></svg>
<div class="py-4 text-white text-sm font-medium">{{ $page.flash.success }}</div>
<div class="py-4 text-white text-sm font-medium">{{ $page.props.flash.success }}</div>
</div>
<button type="button" class="group mr-2 p-2" @click="show = false">
<svg class="block w-2 h-2 fill-green-800 group-hover:fill-white" xmlns="http://www.w3.org/2000/svg" width="235.908" height="235.908" viewBox="278.046 126.846 235.908 235.908"><path d="M506.784 134.017c-9.56-9.56-25.06-9.56-34.62 0L396 210.18l-76.164-76.164c-9.56-9.56-25.06-9.56-34.62 0-9.56 9.56-9.56 25.06 0 34.62L361.38 244.8l-76.164 76.165c-9.56 9.56-9.56 25.06 0 34.62 9.56 9.56 25.06 9.56 34.62 0L396 279.42l76.164 76.165c9.56 9.56 25.06 9.56 34.62 0 9.56-9.56 9.56-25.06 0-34.62L430.62 244.8l76.164-76.163c9.56-9.56 9.56-25.06 0-34.62z" /></svg>
</button>
</div>
<div v-if="($page.flash.error || Object.keys($page.errors).length > 0) && show" class="mb-8 flex items-center justify-between bg-red-500 rounded max-w-3xl">
<div v-if="($page.props.flash.error || Object.keys($page.props.errors).length > 0) && show" class="mb-8 flex items-center justify-between bg-red-400 rounded max-w-3xl">
<div class="flex items-center">
<svg class="ml-4 mr-2 flex-shrink-0 w-4 h-4 fill-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zm1.41-1.41A8 8 0 1 0 15.66 4.34 8 8 0 0 0 4.34 15.66zm9.9-8.49L11.41 10l2.83 2.83-1.41 1.41L10 11.41l-2.83 2.83-1.41-1.41L8.59 10 5.76 7.17l1.41-1.41L10 8.59l2.83-2.83 1.41 1.41z" /></svg>
<div v-if="$page.flash.error" class="py-4 text-white text-sm font-medium">{{ $page.flash.error }}</div>
<div v-if="$page.props.flash.error" class="py-4 text-white text-sm font-medium">{{ $page.props.flash.error }}</div>
<div v-else class="py-4 text-white text-sm font-medium">
<span v-if="Object.keys($page.errors).length === 1">There is one form error.</span>
<span v-else>There are {{ Object.keys($page.errors).length }} form errors.</span>
<span v-if="Object.keys($page.props.errors).length === 1">There is one form error.</span>
<span v-else>There are {{ Object.keys($page.props.errors).length }} form errors.</span>
</div>
</div>
<button type="button" class="group mr-2 p-2" @click="show = false">
@ -33,7 +33,7 @@ export default {
}
},
watch: {
'$page.flash': {
'$page.props.flash': {
handler() {
this.show = true
},

View File

@ -2,7 +2,7 @@
<div>
<portal-target name="dropdown" slim />
<div class="md:flex md:flex-col">
<div class="md:h-screen md:flex md:flex-col" @click="hideDropdownMenus">
<div class="md:h-screen md:flex md:flex-col">
<div class="md:flex md:flex-shrink-0">
<div class="bg-indigo-900 md:flex-shrink-0 md:w-56 px-6 py-4 flex items-center justify-between md:justify-center">
<inertia-link class="mt-1" href="/">
@ -11,30 +11,30 @@
<dropdown class="md:hidden" placement="bottom-end">
<svg class="fill-white w-6 h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z" /></svg>
<div slot="dropdown" class="mt-2 px-8 py-4 shadow-lg bg-indigo-800 rounded">
<main-menu :url="url()" />
<main-menu />
</div>
</dropdown>
</div>
<div class="bg-white border-b w-full p-4 md:py-0 md:px-12 text-sm md:text-md flex justify-between items-center">
<div class="mt-1 mr-4">{{ $page.auth.user.account.name }}</div>
<div class="mt-1 mr-4">{{ $page.props.auth.user.account.name }}</div>
<dropdown class="mt-1" placement="bottom-end">
<div class="flex items-center cursor-pointer select-none group">
<div class="text-gray-700 group-hover:text-indigo-600 focus:text-indigo-600 mr-1 whitespace-no-wrap">
<span>{{ $page.auth.user.first_name }}</span>
<span class="hidden md:inline">{{ $page.auth.user.last_name }}</span>
<div class="text-gray-700 group-hover:text-indigo-600 focus:text-indigo-600 mr-1 whitespace-nowrap">
<span>{{ $page.props.auth.user.first_name }}</span>
<span class="hidden md:inline">{{ $page.props.auth.user.last_name }}</span>
</div>
<icon class="w-5 h-5 group-hover:fill-indigo-600 fill-gray-700 focus:fill-indigo-600" name="cheveron-down" />
</div>
<div slot="dropdown" class="mt-2 py-2 shadow-xl bg-white rounded text-sm">
<inertia-link class="block px-6 py-2 hover:bg-indigo-500 hover:text-white" :href="route('users.edit', $page.auth.user.id)">My Profile</inertia-link>
<inertia-link class="block px-6 py-2 hover:bg-indigo-500 hover:text-white" :href="route('users.edit', $page.props.auth.user.id)">My Profile</inertia-link>
<inertia-link class="block px-6 py-2 hover:bg-indigo-500 hover:text-white" :href="route('users')">Manage Users</inertia-link>
<inertia-link class="block px-6 py-2 hover:bg-indigo-500 hover:text-white" :href="route('logout')" method="post">Logout</inertia-link>
<inertia-link class="block px-6 py-2 hover:bg-indigo-500 hover:text-white w-full text-left" :href="route('logout')" method="post" as="button">Logout</inertia-link>
</div>
</dropdown>
</div>
</div>
<div class="md:flex md:flex-grow md:overflow-hidden">
<main-menu :url="url()" class="hidden md:block bg-indigo-800 flex-shrink-0 w-56 p-12 overflow-y-auto" />
<main-menu class="hidden md:block bg-indigo-800 flex-shrink-0 w-56 p-12 overflow-y-auto" />
<div class="md:flex-1 px-4 py-8 md:p-12 md:overflow-y-auto" scroll-region>
<flash-messages />
<slot />
@ -46,11 +46,11 @@
</template>
<script>
import Dropdown from '@/Shared/Dropdown'
import FlashMessages from '@/Shared/FlashMessages'
import Icon from '@/Shared/Icon'
import Logo from '@/Shared/Logo'
import Dropdown from '@/Shared/Dropdown'
import MainMenu from '@/Shared/MainMenu'
import FlashMessages from '@/Shared/FlashMessages'
export default {
components: {
@ -60,19 +60,5 @@ export default {
Logo,
MainMenu,
},
data() {
return {
showUserMenu: false,
accounts: null,
}
},
methods: {
url() {
return location.pathname.substr(1)
},
hideDropdownMenus() {
this.showUserMenu = false
},
},
}
</script>

View File

@ -34,16 +34,13 @@ export default {
components: {
Icon,
},
props: {
url: String,
},
methods: {
isUrl(...urls) {
let currentUrl = this.$page.url.substr(1)
if (urls[0] === '') {
return this.url === ''
return currentUrl === ''
}
return urls.filter(url => this.url.startsWith(url)).length
return urls.filter(url => currentUrl.startsWith(url)).length
},
},
}

View File

@ -1,9 +1,11 @@
<template>
<div class="mt-6 -mb-1 flex flex-wrap">
<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 border rounded text-gray-400" :class="{ 'ml-auto': link.label === 'Next' }">{{ link.label }}</div>
<inertia-link v-else :key="key" class="mr-1 mb-1 px-4 py-3 text-sm border rounded hover:bg-white focus:border-indigo-500 focus:text-indigo-500" :class="{ 'bg-white': link.active, 'ml-auto': link.label === 'Next' }" :href="link.url">{{ link.label }}</inertia-link>
</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-else :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>

View File

@ -1,7 +1,7 @@
<template>
<div class="flex items-center">
<div class="flex w-full bg-white shadow rounded">
<dropdown :auto-close="false" class="px-4 md:px-6 rounded-l border-r hover:bg-gray-100 focus:border-white focus:shadow-outline focus:z-10" placement="bottom-start">
<dropdown :auto-close="false" class="px-4 md:px-6 rounded-l border-r hover:bg-gray-100 focus:border-white focus:ring focus:z-10" placement="bottom-start">
<div class="flex items-baseline">
<span class="text-gray-700 hidden md:inline">Filter</span>
<svg class="w-2 h-2 fill-gray-700 md:ml-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 961.243 599.998">
@ -12,7 +12,7 @@
<slot />
</div>
</dropdown>
<input class="relative w-full px-6 py-3 rounded-r focus:shadow-outline" autocomplete="off" type="text" name="search" placeholder="Search…" :value="value" @input="$emit('input', $event.target.value)">
<input class="relative w-full px-6 py-3 rounded-r focus:ring" autocomplete="off" type="text" name="search" placeholder="Search…" :value="value" @input="$emit('input', $event.target.value)" />
</div>
<button class="ml-3 text-sm text-gray-500 hover:text-gray-700 focus:text-indigo-500" type="button" @click="$emit('reset')">Reset</button>
</div>

View File

@ -1,7 +1,7 @@
<template>
<div>
<label v-if="label" class="form-label" :for="id">{{ label }}:</label>
<input :id="id" ref="input" v-bind="$attrs" class="form-input" :class="{ error: error }" :type="type" :value="value" @input="$emit('input', $event.target.value)">
<input :id="id" ref="input" v-bind="$attrs" class="form-input" :class="{ error: error }" :type="type" :value="value" @input="$emit('input', $event.target.value)" />
<div v-if="error" class="form-error">{{ error }}</div>
</div>
</template>

View File

@ -1,5 +1,5 @@
<template>
<div class="p-4 bg-yellow-400 rounded flex items-center justify-between max-w-3xl">
<div class="p-4 bg-yellow-300 rounded flex items-center justify-between max-w-3xl">
<div class="flex items-center">
<icon name="trash" class="flex-shrink-0 w-4 h-4 fill-yellow-800 mr-2" />
<div class="text-sm font-medium text-yellow-800">

23
resources/js/app.js vendored
View File

@ -1,27 +1,28 @@
import Vue from 'vue'
import VueMeta from 'vue-meta'
import PortalVue from 'portal-vue'
import { InertiaApp } from '@inertiajs/inertia-vue'
import { App, plugin } from '@inertiajs/inertia-vue'
import { InertiaProgress } from '@inertiajs/progress/src'
Vue.config.productionTip = false
Vue.mixin({ methods: { route: window.route } })
Vue.use(InertiaApp)
Vue.use(plugin)
Vue.use(PortalVue)
Vue.use(VueMeta)
InertiaProgress.init()
let app = document.getElementById('app')
const el = document.getElementById('app')
new Vue({
metaInfo: {
titleTemplate: (title) => title ? `${title} - Ping CRM` : 'Ping CRM'
titleTemplate: title => (title ? `${title} - Ping CRM` : 'Ping CRM'),
},
render: h => h(InertiaApp, {
props: {
initialPage: JSON.parse(app.dataset.page),
resolveComponent: name => import(`@/Pages/${name}`).then(module => module.default),
},
}),
}).$mount(app)
render: h =>
h(App, {
props: {
initialPage: JSON.parse(el.dataset.page),
resolveComponent: name => import(`@/Pages/${name}`).then(module => module.default),
},
}),
}).$mount(el)

View File

@ -1,9 +1,9 @@
<!DOCTYPE html>
<html class="h-full bg-gray-200">
<html class="h-full bg-gray-100">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<link href="{{ mix('/css/app.css') }}" rel="stylesheet">
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
{{-- Inertia --}}
<script src="https://polyfill.io/v3/polyfill.min.js?features=smoothscroll,NodeList.prototype.forEach,Promise,Object.values,Object.assign" defer></script>

48
tailwind.config.js vendored
View File

@ -1,22 +1,42 @@
const colors = require('tailwindcss/colors')
const defaultTheme = require('tailwindcss/defaultTheme')
module.exports = {
purge: [
// prettier-ignore
'./resources/**/*.blade.php',
'./resources/**/*.js',
'./resources/**/*.vue',
],
darkMode: false, // or 'media' or 'class'
theme: {
colors: {
transparent: 'transparent',
current: 'currentColor',
black: colors.black,
white: colors.white,
red: colors.red,
orange: colors.orange,
yellow: colors.yellow,
green: colors.green,
gray: colors.blueGray,
indigo: {
100: '#e6e8ff',
300: '#b2b7ff',
400: '#7886d7',
500: '#6574cd',
600: '#5661b3',
800: '#2f365f',
900: '#191e38',
},
},
extend: {
borderColor: theme => ({
DEFAULT: theme('colors.gray.200', 'currentColor'),
}),
fontFamily: {
sans: ['Cerebri Sans', ...defaultTheme.fontFamily.sans],
},
colors: {
indigo: {
'900': '#191e38',
'800': '#2f365f',
'600': '#5661b3',
'500': '#6574cd',
'400': '#7886d7',
'300': '#b2b7ff',
'100': '#e6e8ff',
},
},
boxShadow: theme => ({
outline: '0 0 0 2px ' + theme('colors.indigo.500'),
}),
@ -24,9 +44,9 @@ module.exports = {
},
},
variants: {
fill: ['responsive', 'hover', 'focus', 'group-hover'],
textColor: ['responsive', 'hover', 'focus', 'group-hover'],
zIndex: ['responsive', 'focus'],
extend: {
fill: ['focus', 'group-hover'],
},
},
plugins: [],
}

31
webpack.mix.js vendored
View File

@ -1,9 +1,7 @@
const path = require('path')
const mix = require('laravel-mix')
const cssImport = require('postcss-import')
const cssNesting = require('postcss-nesting')
const mix = require('laravel-mix')
const path = require('path')
const purgecss = require('@fullhuman/postcss-purgecss')
const tailwindcss = require('tailwindcss')
/*
|--------------------------------------------------------------------------
@ -16,22 +14,15 @@ const tailwindcss = require('tailwindcss')
|
*/
mix.js('resources/js/app.js', 'public/js')
.postCss('resources/css/app.css', 'public/css/app.css')
.options({
postCss: [
cssImport(),
cssNesting(),
tailwindcss('tailwind.config.js'),
...mix.inProduction() ? [
purgecss({
content: ['./resources/views/**/*.blade.php', './resources/js/**/*.vue'],
defaultExtractor: content => content.match(/[\w-/:.]+(?<!:)/g) || [],
whitelistPatternsChildren: [/nprogress/],
}),
] : [],
],
})
mix
.js('resources/js/app.js', 'public/js')
.vue()
.postCss('resources/css/app.css', 'public/css', [
// prettier-ignore
cssImport(),
cssNesting(),
require('tailwindcss'),
])
.webpackConfig({
output: { chunkFilename: 'js/[name].js?id=[chunkhash]' },
resolve: {