Fix file submissions preview
This commit is contained in:
parent
bf98497711
commit
91432c4aed
|
@ -48,7 +48,7 @@ class Handler extends ExceptionHandler
|
||||||
{
|
{
|
||||||
return $request->expectsJson()
|
return $request->expectsJson()
|
||||||
? response()->json(['message' => $exception->getMessage()], 401)
|
? response()->json(['message' => $exception->getMessage()], 401)
|
||||||
: redirect()->guest(url('/login'));
|
: redirect(front_url('login'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function report(Throwable $exception)
|
public function report(Throwable $exception)
|
||||||
|
|
|
@ -15,7 +15,8 @@ class FormSubmissionController extends Controller
|
||||||
{
|
{
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->middleware('auth');
|
$this->middleware('auth', ['except' => ['submissionFile']]);
|
||||||
|
$this->middleware('signed', ['only' => ['submissionFile']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submissions(string $id)
|
public function submissions(string $id)
|
||||||
|
@ -51,9 +52,6 @@ class FormSubmissionController extends Controller
|
||||||
|
|
||||||
public function submissionFile($id, $fileName)
|
public function submissionFile($id, $fileName)
|
||||||
{
|
{
|
||||||
$form = Form::findOrFail((int) $id);
|
|
||||||
$this->authorize('view', $form);
|
|
||||||
|
|
||||||
$fileName = Str::of(PublicFormController::FILE_UPLOAD_PATH)->replace('?', $id).'/'
|
$fileName = Str::of(PublicFormController::FILE_UPLOAD_PATH)->replace('?', $id).'/'
|
||||||
.urldecode($fileName);
|
.urldecode($fileName);
|
||||||
|
|
||||||
|
@ -63,6 +61,10 @@ class FormSubmissionController extends Controller
|
||||||
], 404);
|
], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config('filesystems.default') !== 's3') {
|
||||||
|
return response()->file(Storage::path($fileName));
|
||||||
|
}
|
||||||
|
|
||||||
return redirect(
|
return redirect(
|
||||||
Storage::temporaryUrl($fileName, now()->addMinute())
|
Storage::temporaryUrl($fileName, now()->addMinute())
|
||||||
);
|
);
|
||||||
|
|
|
@ -15,7 +15,7 @@ class Authenticate extends Middleware
|
||||||
protected function redirectTo($request)
|
protected function redirectTo($request)
|
||||||
{
|
{
|
||||||
if (! $request->expectsJson()) {
|
if (! $request->expectsJson()) {
|
||||||
return redirect('/login');
|
return redirect(front_url('login'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,11 @@ class FormSubmissionResource extends JsonResource
|
||||||
return $file !== null && $file;
|
return $file !== null && $file;
|
||||||
})->map(function ($file) {
|
})->map(function ($file) {
|
||||||
return [
|
return [
|
||||||
'file_url' => route('open.forms.submissions.file', [$this->form_id, $file]),
|
'file_url' => \URL::signedRoute(
|
||||||
|
'open.forms.submissions.file',
|
||||||
|
[$this->form_id, $file],
|
||||||
|
now()->addMinutes(10)
|
||||||
|
),
|
||||||
'file_name' => $file,
|
'file_name' => $file,
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
|
@ -117,6 +117,7 @@ import VButton from '~/components/global/VButton.vue'
|
||||||
import FormCleanings from '../../pages/forms/show/FormCleanings.vue'
|
import FormCleanings from '../../pages/forms/show/FormCleanings.vue'
|
||||||
import VTransition from '~/components/global/transitions/VTransition.vue'
|
import VTransition from '~/components/global/transitions/VTransition.vue'
|
||||||
import {pendingSubmission} from "~/composables/forms/pendingSubmission.js";
|
import {pendingSubmission} from "~/composables/forms/pendingSubmission.js";
|
||||||
|
import clonedeep from "clone-deep";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { VTransition, VButton, OpenFormButton, OpenForm, FormCleanings },
|
components: { VTransition, VButton, OpenFormButton, OpenForm, FormCleanings },
|
||||||
|
@ -176,29 +177,24 @@ export default {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
// this.closeAlert()
|
// this.closeAlert()
|
||||||
form.post('/forms/' + this.form.slug + '/answer').then((data) => {
|
form.post('/forms/' + this.form.slug + '/answer').then((data) => {
|
||||||
this.$logEvent('form_submission', {
|
useAmplitude().logEvent('form_submission', {
|
||||||
workspace_id: this.form.workspace_id,
|
workspace_id: this.form.workspace_id,
|
||||||
form_id: this.form.id
|
form_id: this.form.id
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const payload = clonedeep({
|
||||||
|
type: 'form-submitted',
|
||||||
|
form: {
|
||||||
|
slug: this.form.slug,
|
||||||
|
id: this.form.id
|
||||||
|
},
|
||||||
|
submission_data: form.data()
|
||||||
|
})
|
||||||
|
|
||||||
if (this.isIframe) {
|
if (this.isIframe) {
|
||||||
window.parent.postMessage({
|
window.parent.postMessage(payload, '*')
|
||||||
type: 'form-submitted',
|
|
||||||
form: {
|
|
||||||
slug: this.form.slug,
|
|
||||||
id: this.form.id
|
|
||||||
},
|
|
||||||
submission_data: form.data()
|
|
||||||
}, '*')
|
|
||||||
}
|
}
|
||||||
window.postMessage({
|
window.postMessage(payload, '*')
|
||||||
type: 'form-submitted',
|
|
||||||
form: {
|
|
||||||
slug: this.form.slug,
|
|
||||||
id: this.form.id
|
|
||||||
},
|
|
||||||
submission_data: form.data()
|
|
||||||
}, '*')
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.pendingSubmission.remove()
|
this.pendingSubmission.remove()
|
||||||
|
@ -221,7 +217,7 @@ export default {
|
||||||
this.confetti.play()
|
this.confetti.play()
|
||||||
}
|
}
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.log('here')
|
console.error(error)
|
||||||
if (error.response && error.data && error.data.message) {
|
if (error.response && error.data && error.data.message) {
|
||||||
useAlert().error(error.data.message)
|
useAlert().error(error.data.message)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
<template>
|
<template>
|
||||||
<p class="text-xs">
|
<p class="text-xs">
|
||||||
<span v-for="file in value" :key="file.file_url"
|
<span v-for="file in parsedFiles" :key="file.file_url"
|
||||||
class="whitespace-nowrap rounded-md transition-colors hover:decoration-none"
|
class="whitespace-nowrap rounded-md transition-colors hover:decoration-none"
|
||||||
:class="{'open-file text-gray-700 dark:text-gray-300 truncate':!isImage(file.file_url), 'open-file-img':isImage(file.file_url)}"
|
:class="{'open-file text-gray-700 dark:text-gray-300 truncate':!file.is_image, 'open-file-img':file.is_image}"
|
||||||
>
|
>
|
||||||
<a class="text-gray-700 dark:text-gray-300" :href="file.file_url" target="_blank"
|
<a class="text-gray-700 dark:text-gray-300" :href="file.file_url" target="_blank"
|
||||||
rel="nofollow"
|
rel="nofollow"
|
||||||
>
|
>
|
||||||
<div v-if="isImage(file.file_url)" class="w-8 h-8">
|
<div v-if="file.is_image" class="w-8 h-8">
|
||||||
<img class="object-cover h-full w-full rounded" :src="file.file_url"/>
|
<img class="object-cover h-full w-full rounded" :src="file.file_url" @error="failedImages.push(file.file_url)"/>
|
||||||
</div>
|
</div>
|
||||||
<span v-else
|
<span v-else
|
||||||
class="py-1 px-2"
|
class="py-1 px-2"
|
||||||
>
|
>
|
||||||
<a :href="file.file_url" target="_blank" download>{{ displayedFileName(file.file_name) }}</a>
|
<a :href="file.file_url" target="_blank" download>{{ file.displayed_file_name }}</a>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
|
@ -31,25 +31,36 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {}
|
return {
|
||||||
|
failedImages: []
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {},
|
computed: {
|
||||||
mounted() {
|
parsedFiles() {
|
||||||
|
return this.value.map((file) => {
|
||||||
|
return {
|
||||||
|
file_name: file.file_name,
|
||||||
|
file_url: file.file_url,
|
||||||
|
displayed_file_name: this.displayedFileName(file.file_name),
|
||||||
|
is_image: !this.failedImages.includes(file.file_url) && this.isImage(file.file_name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
isImage(url) {
|
isImage(fileName) {
|
||||||
return ['png', 'gif', 'jpg', 'jpeg', 'tif'].some((suffix) => {
|
return ['png', 'gif', 'jpg', 'jpeg', 'tif'].some((suffix) => {
|
||||||
return url && url.endsWith(suffix)
|
return fileName && fileName.endsWith(suffix)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
displayedFileName(fileName) {
|
displayedFileName(fileName) {
|
||||||
const extension = fileName.substr(fileName.lastIndexOf(".") + 1)
|
const extension = fileName.substr(fileName.lastIndexOf(".") + 1)
|
||||||
const filename = fileName.substr(0, fileName.lastIndexOf("."))
|
const filename = fileName.substr(0, fileName.lastIndexOf("."))
|
||||||
|
|
||||||
if (filename.length > 12) {
|
if (filename.length > 10) {
|
||||||
return filename.substr(0, 12) + '(...).' + extension
|
return filename.substr(0, 10) + '[...].' + extension
|
||||||
}
|
}
|
||||||
return filename + '.' + extension
|
return filename + '.' + extension
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,8 +47,9 @@ export function getOpnRequestsOptions(request, opts) {
|
||||||
addPasswordToFormRequest(request, opts)
|
addPasswordToFormRequest(request, opts)
|
||||||
addCustomDomainHeader(request, opts)
|
addCustomDomainHeader(request, opts)
|
||||||
|
|
||||||
|
if (!opts.baseURL) opts.baseURL = config.public.apiBase
|
||||||
|
|
||||||
return {
|
return {
|
||||||
baseURL: config.public.apiBase,
|
|
||||||
async onResponseError({response}) {
|
async onResponseError({response}) {
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
import opnformConfig from "~/opnform.config.js";
|
|
||||||
|
|
||||||
export const storeFile = async (file, options = {}) => {
|
async function storeLocalFile(file, options={}) {
|
||||||
if(!opnformConfig.s3_enabled) { // If not s3 then upload to local temp
|
|
||||||
let formData = new FormData()
|
let formData = new FormData()
|
||||||
formData.append('file', file)
|
formData.append('file', file)
|
||||||
const response = await useOpnApi('/upload-file', {
|
const response = await opnFetch('/upload-file', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
})
|
})
|
||||||
response.data.extension = file.name.split('.').pop()
|
response.extension = file.name.split('.').pop()
|
||||||
return response.data
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await useOpnApi(options.signedStorageUrl ? options.signedStorageUrl : '/vapor/signed-storage-url', {
|
export const storeFile = async (file, options = {}) => {
|
||||||
|
if(!useRuntimeConfig().public.s3Enabled) return storeLocalFile(file, options)
|
||||||
|
|
||||||
|
const response = await opnFetch(options.signedStorageUrl || 'vapor/signed-storage-url', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: options.data,
|
body: options.data,
|
||||||
bucket: options.bucket || '',
|
bucket: options.bucket || '',
|
||||||
|
@ -24,26 +25,13 @@ export const storeFile = async (file, options = {}) => {
|
||||||
...options.options
|
...options.options
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(response)
|
|
||||||
|
|
||||||
const headers = response.data.headers
|
|
||||||
|
|
||||||
if ('Host' in headers) {
|
|
||||||
delete headers.Host
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof options.progress === 'undefined') {
|
|
||||||
options.progress = () => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload to S3
|
// Upload to S3
|
||||||
await useFetch(response.data.url,{
|
await useFetch(response.url,{
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: file,
|
body: file,
|
||||||
headers: headers,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
response.data.extension = file.name.split('.').pop()
|
response.extension = file.name.split('.').pop()
|
||||||
|
|
||||||
return response.data
|
return response
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,5 @@ export default {
|
||||||
"zapier_integration": "https://zapier.com/developer/public-invite/146950/58db583730cc46b821614468d94c35de/",
|
"zapier_integration": "https://zapier.com/developer/public-invite/146950/58db583730cc46b821614468d94c35de/",
|
||||||
"book_onboarding": "https://zcal.co/i/YQVGEULQ",
|
"book_onboarding": "https://zcal.co/i/YQVGEULQ",
|
||||||
"feature_requests": "https://opnform.canny.io/feature-requests"
|
"feature_requests": "https://opnform.canny.io/feature-requests"
|
||||||
},
|
}
|
||||||
"production": false
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,7 +138,7 @@ useOpnSeoMeta({
|
||||||
})
|
})
|
||||||
useHead({
|
useHead({
|
||||||
titleTemplate: (titleChunk) => {
|
titleTemplate: (titleChunk) => {
|
||||||
if (form && form.value.is_pro && form.value.seo_meta.page_title) {
|
if (form && form.value?.is_pro && form.value?.seo_meta.page_title) {
|
||||||
// Disable template if custom SEO title
|
// Disable template if custom SEO title
|
||||||
return titleChunk
|
return titleChunk
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ export default {
|
||||||
},
|
},
|
||||||
redirectIfSubscribed () {
|
redirectIfSubscribed () {
|
||||||
if (this.user.is_subscribed) {
|
if (this.user.is_subscribed) {
|
||||||
this.$logEvent('subscribed', { plan: this.user.has_enterprise_subscription ? 'enterprise' : 'pro' })
|
useAmplitude().logEvent('subscribed', { plan: this.user.has_enterprise_subscription ? 'enterprise' : 'pro' })
|
||||||
this.$crisp.push(['set', 'session:event', [[['subscribed', { plan: this.user.has_enterprise_subscription ? 'enterprise' : 'pro' }, 'blue']]]])
|
this.$crisp.push(['set', 'session:event', [[['subscribed', { plan: this.user.has_enterprise_subscription ? 'enterprise' : 'pro' }, 'blue']]]])
|
||||||
this.$router.push({ name: 'home' })
|
this.$router.push({ name: 'home' })
|
||||||
|
|
||||||
|
|
|
@ -86,7 +86,10 @@ Route::group(['middleware' => 'auth:api'], function () {
|
||||||
|
|
||||||
Route::get('/{id}/submissions', [FormSubmissionController::class, 'submissions'])->name('submissions');
|
Route::get('/{id}/submissions', [FormSubmissionController::class, 'submissions'])->name('submissions');
|
||||||
Route::get('/{id}/submissions/export', [FormSubmissionController::class, 'export'])->name('submissions.export');
|
Route::get('/{id}/submissions/export', [FormSubmissionController::class, 'export'])->name('submissions.export');
|
||||||
Route::get('/{id}/submissions/file/{filename}', [FormSubmissionController::class, 'submissionFile'])->name('submissions.file');
|
Route::get('/{id}/submissions/file/{filename}', [FormSubmissionController::class, 'submissionFile'])
|
||||||
|
->middleware('signed')
|
||||||
|
->withoutMiddleware(['auth:api'])
|
||||||
|
->name('submissions.file');
|
||||||
|
|
||||||
Route::delete('/{id}/records/{recordid}/delete', [RecordController::class, 'delete'])->name('records.delete');
|
Route::delete('/{id}/records/{recordid}/delete', [RecordController::class, 'delete'])->name('records.delete');
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue