thank you all for your help. I've learned a lot about uploading images using vue and storing them in Laravel. After reading your helpful responses and searching on the web, the following worked for me:
Vue components:
FileUploder.vue
<template>
<label
class="w-48 flex flex-col items-center px-2 py-4 bg-indigo-100 text-gray-500 rounded tracking-wide border-1 border-indigo-200 cursor-pointer hover:bg-indigo-600 hover:text-gray-200">
<svg class="w-5 h-5" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path d="M16.88 9.1A4 4 0 0 1 16 17H5a5 5 0 0 1-1-9.9V7a3 3 0 0 1 4.52-2.59A4.98 4.98 0 0 1 17 8c0 .38-.04.74-.12 1.1zM11 11h3l-4-4-4 4h3v3h2v-3z" />
</svg>
<span class="mt-2 leading-tight text-xs">Add company logo</span>
<input type='file' @change="onChange" class="hidden">
</label>
</template>
<script>
export default {
methods: {
onChange(e) {
if (! e.target.files.length) return;
let file = e.target.files[0];
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = e => {
let src = e.target.result;
this.$emit('loaded', { src, file });
};
}
}
}
</script>
JobCreate.vue
<template>
<section>
<form @submit.prevent="createJob" enctype="multipart/form-data">
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
<div class="px-4 py-5 border-b border-indigo-100 sm:px-6">
<h3 class="text-lg leading-6 font-medium text-gray-900">
New job form
</h3>
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500">
<sup>*</sup>Required fields
</p>
</div>
<div>
<dl>
<div class="bg-gray-50 px-4 py-5 items-center sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium text-gray-600">
Job title<sup>*</sup>
</dt>
<dd class="mt-1 sm:mt-0 sm:col-span-2">
<input
v-model="title"
type="text"
placeholder="Email Developer"
aria-label="Job title"
class="transition-colors duration-100 ease-in-out focus:outline-0 text-base border border-indigo-200 focus:bg-white focus:outline-none focus:border-indigo-300 text-gray-800 rounded-lg bg-white md:rounded-lg py-2 pr-4 pl-4 block w-full appearance-none leading-normal">
</dd>
</div>
<div class="bg-white px-4 py-5 items-center sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium text-gray-600">
Company<sup>*</sup>
</dt>
<dd class="mt-1 sm:mt-0 sm:col-span-2">
<input
v-model="company"
type="text"
placeholder="Acme Ltd."
aria-label="Company name"
class="transition-colors duration-100 ease-in-out focus:outline-0 text-base border border-indigo-200 focus:bg-white focus:outline-none focus:border-indigo-300 text-gray-800 rounded-lg bg-white md:rounded-lg py-2 pr-4 pl-4 block w-full appearance-none leading-normal">
</dd>
</div>
<div class="bg-white px-4 py-5 items-center sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium text-gray-600">
Company logo
</dt>
<dd class="mt-1 sm:mt-0 sm:col-span-2">
<file-uploader @loaded="onLoad"></file-uploader>
</dd>
</div>
<div class="bg-white px-4 py-5 items-center sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium text-gray-600">
Location<sup>*</sup>
</dt>
<dd class="mt-1 sm:mt-0 sm:col-span-2">
<input
v-model="location"
type="text"
placeholder="Solihull, UK"
aria-label="job location"
class="transition-colors duration-100 ease-in-out focus:outline-0 text-base border border-indigo-200 focus:bg-white focus:outline-none focus:border-indigo-300 text-gray-800 rounded-lg bg-white py-2 pr-4 pl-4 block w-full appearance-none leading-normal">
</dd>
</div>
<div class="bg-gray-50 px-4 py-5 items-center sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium text-gray-600">
Type<sup>*</sup>
</dt>
<dd class="mt-1 text-sm sm:mt-0 sm:col-span-2">
<select
v-model="type"
aria-label="Job category"
class="border text-base border-indigo-200 bg-transparent text-gray-500 leading-normal max-w-sm focus:bg-white focus:outline-none focus:border-indigo-300 rounded-lg bg-white py-2 pr-4 pl-4 block">
<option selected disabled value=''>Select type</option>
<option>Full-time</option>
<option>Part-time</option>
<option>Contract</option>
<option>Other</option>
</select>
</dd>
</div>
<div class="bg-gray-50 px-4 py-5 items-center sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium text-gray-600">
Category<sup>*</sup>
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<select
v-model="categoryId"
aria-label="Job category"
class="border border-indigo-200 bg-transparent text-gray-500 text-base leading-normal max-w-sm focus:bg-white focus:outline-none focus:border-indigo-300 bg-white rounded-lg py-2 pr-4 pl-4 block">
<option selected disabled value=''>Select category</option>
<option
v-for="(category, id) in categories"
:value="category.id">
{{category.name}}
</option>
</select>
</dd>
</div>
<div class="bg-white px-4 py-5 items-center sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium text-gray-600">
Salary (recommended)
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<input
v-model="salary"
type="text"
placeholder="e.g. £50,000"
aria-label="Salary"
class="transition-colors duration-100 text-base ease-in-out focus:outline-0 border border-indigo-200 focus:bg-white focus:outline-none focus:border-indigo-300 text-gray-800 rounded-lg bg-white md:rounded-lg py-2 pr-4 pl-4 appearance-none leading-normal">
</dd>
</div>
<div class="bg-gray-50 px-4 py-5 items-center sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium text-gray-600">
Job description<sup>*</sup>
</dt>
<dd class="mt-1 sm:mt-0 sm:col-span-2">
<wysiwyg
v-model="jobDescription"
name="job description">
</wysiwyg>
</dd>
</div>
<div class="bg-white px-4 py-5 items-center sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium text-gray-600">
Link to apply<sup>*</sup>
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<input
v-model="linkToApply"
type="text"
aria-label="Link to apply"
placeholder="https://www.acme.com/career/apply"
class="transition-colors duration-100 text-base ease-in-out focus:outline-0 border border-indigo-200 focus:bg-white focus:outline-none focus:border-indigo-300 text-gray-800 rounded-lg bg-white md:rounded-lg py-2 pr-4 pl-4 block w-full appearance-none leading-normal">
</dd>
</div>
<div class="bg-gray-50 px-4 py-5 items-center sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium text-gray-600">
Email<sup>*</sup>
</dt>
<dd class="mt-1 sm:mt-0 sm:col-span-2">
<input
v-model="email"
type="text"
placeholder="[email protected]"
aria-label="Email address"
class="transition-colors duration-100 ease-in-out focus:outline-0 text-base border border-indigo-200 focus:bg-white focus:outline-none focus:border-indigo-300 text-gray-800 rounded-lg bg-white md:rounded-lg py-2 pr-4 pl-4 block w-full appearance-none leading-normal">
</dd>
</div>
</dl>
</div>
<div class="px-4 py-5 bg-gray-100 border-t border-indigo-100 sm:px-6 flex items-center justify-end">
<span class="block">
<a
href="/manage/jobs"
class="btn btn-text">
Cancel
</a>
</span>
<span class="ml-6">
<button
type="submit"
class="btn btn-primary btn-small">
Create job
</button>
</span>
</div>
</div>
</form>
</section>
</template>
<script>
import Wysiwyg from '../../Wysiwyg';
import FileUploader from '../../FileUploader';
export default {
components: {
'wysiwyg': Wysiwyg,
'file-uploader': FileUploader
},
data() {
return {
categories: [],
title: '',
company: '',
companyLogo: '',
uploadedFile: '',
email: '',
location: '',
type: '',
categoryId: '',
salary: '',
jobDescription: '',
linkToApply: ''
}
},
mounted() {
this.fetchCategories();
},
methods: {
fetchCategories() {
axios.get('/categories')
.then(response => {
this.categories = response.data;
})
.catch(error => {
console.log(error);
})
},
onLoad(avatar) {
console.log(avatar);
this.companyLogo = avatar.src;
this.uploadedFile = avatar.file;
},
createJob() {
let data = new FormData();
data.append('company_logo', this.uploadedFile);
data.append('title', this.title);
data.append('description', this.jobDescription);
data.append('company', this.company);
data.append('location', this.location);
data.append('salary', this.salary);
data.append('link_to_apply', this.linkToApply);
data.append('type', this.type);
data.append('category_id', this.categoryId);
data.append('email', this.email);
axios.post('/manage/jobs', data)
.then(response => {
console.log(response);
})
.catch(errors => {
console.log(errors.data);
})
}
}
}
</script>
Controller:
public function store(Request $request) {
$validatedInput = $request->validate([
'title' => 'required | max:255',
'description' => 'required',
'location' => 'required',
'link_to_apply' => 'required',
'email' => 'required | email',
'company' => 'required',
'category_id' => 'required'
]);
if ($request->has('company_logo')) {
$job = new Job;
$job->title = request('title');
$job->description = request('description');
$job->location = request('location');
$job->link_to_apply = request('link_to_apply');
$job->type = request('type');
$job->category_id = request('category_id');
$job->access_token = Str::orderedUuid()->toString();
$job->email = request('email');
$job->company = request('company');
$job->about_company = request('about_company');
$job->salary = request('salary');
$job->company_logo = $request->file('company_logo')->store('images/company_logos', 'public');
$job->save();
return 'New job and image saved';
} else {
$job = new Job;
$job->title = request('title');
$job->description = request('description');
$job->location = request('location');
$job->link_to_apply = request('link_to_apply');
$job->type = request('type');
$job->category_id = request('category_id');
$job->access_token = Str::orderedUuid()->toString();
$job->email = request('email');
$job->company = request('company');
$job->about_company = request('about_company');
$job->salary = request('salary');
$job->save();
return 'New job saved, no image';
}
Hope this helps someone who is equally as new to the file upload using Vue/Laravel as I was!