0351d front end linting (#377)
* feat: disable custom script for trial users * after lint fix * frontend linting --------- Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
55
client/composables/forms/initForm.js
vendored
55
client/composables/forms/initForm.js
vendored
@@ -1,35 +1,38 @@
|
||||
import {generateUUID} from "~/lib/utils.js";
|
||||
import { generateUUID } from "~/lib/utils.js"
|
||||
|
||||
export const initForm = (defaultValue = {}, withDefaultProperties = false) => {
|
||||
return useForm({
|
||||
title: 'My Form',
|
||||
title: "My Form",
|
||||
description: null,
|
||||
visibility: 'public',
|
||||
visibility: "public",
|
||||
workspace_id: null,
|
||||
properties: withDefaultProperties ? getDefaultProperties() :[],
|
||||
properties: withDefaultProperties ? getDefaultProperties() : [],
|
||||
|
||||
// Customization
|
||||
theme: 'default',
|
||||
width: 'centered',
|
||||
dark_mode: 'auto',
|
||||
color: '#3B82F6',
|
||||
theme: "default",
|
||||
width: "centered",
|
||||
dark_mode: "auto",
|
||||
color: "#3B82F6",
|
||||
hide_title: false,
|
||||
no_branding: false,
|
||||
uppercase_labels: true,
|
||||
transparent_background: false,
|
||||
closes_at: null,
|
||||
closed_text: 'This form has now been closed by its owner and does not accept submissions anymore.',
|
||||
closed_text:
|
||||
"This form has now been closed by its owner and does not accept submissions anymore.",
|
||||
auto_save: true,
|
||||
|
||||
// Submission
|
||||
submit_button_text: 'Submit',
|
||||
submit_button_text: "Submit",
|
||||
re_fillable: false,
|
||||
re_fill_button_text: 'Fill Again',
|
||||
submitted_text: 'Amazing, we saved your answers. Thank you for your time and have a great day!',
|
||||
re_fill_button_text: "Fill Again",
|
||||
submitted_text:
|
||||
"Amazing, we saved your answers. Thank you for your time and have a great day!",
|
||||
use_captcha: false,
|
||||
max_submissions_count: null,
|
||||
max_submissions_reached_text: 'This form has now reached the maximum number of allowed submissions and is now closed.',
|
||||
editable_submissions_button_text: 'Edit submission',
|
||||
max_submissions_reached_text:
|
||||
"This form has now reached the maximum number of allowed submissions and is now closed.",
|
||||
editable_submissions_button_text: "Edit submission",
|
||||
confetti_on_submission: false,
|
||||
|
||||
// Security & Privacy
|
||||
@@ -38,31 +41,31 @@ export const initForm = (defaultValue = {}, withDefaultProperties = false) => {
|
||||
// Custom SEO
|
||||
seo_meta: {},
|
||||
|
||||
...defaultValue
|
||||
...defaultValue,
|
||||
})
|
||||
}
|
||||
|
||||
function getDefaultProperties () {
|
||||
function getDefaultProperties() {
|
||||
return [
|
||||
{
|
||||
name: 'Name',
|
||||
type: 'text',
|
||||
name: "Name",
|
||||
type: "text",
|
||||
hidden: false,
|
||||
required: true,
|
||||
id: generateUUID()
|
||||
id: generateUUID(),
|
||||
},
|
||||
{
|
||||
name: 'Email',
|
||||
type: 'email',
|
||||
name: "Email",
|
||||
type: "email",
|
||||
hidden: false,
|
||||
id: generateUUID()
|
||||
id: generateUUID(),
|
||||
},
|
||||
{
|
||||
name: 'Message',
|
||||
type: 'text',
|
||||
name: "Message",
|
||||
type: "text",
|
||||
hidden: false,
|
||||
multi_lines: true,
|
||||
id: generateUUID()
|
||||
}
|
||||
id: generateUUID(),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
14
client/composables/forms/pendingSubmission.js
vendored
14
client/composables/forms/pendingSubmission.js
vendored
@@ -1,10 +1,11 @@
|
||||
import {hash} from "~/lib/utils.js"
|
||||
import {useStorage} from "@vueuse/core"
|
||||
import { hash } from "~/lib/utils.js"
|
||||
import { useStorage } from "@vueuse/core"
|
||||
|
||||
export const pendingSubmission = (form) => {
|
||||
|
||||
const formPendingSubmissionKey = computed(() => {
|
||||
return (form) ? form.form_pending_submission_key + '-' + hash(window.location.href) : ''
|
||||
return form
|
||||
? form.form_pending_submission_key + "-" + hash(window.location.href)
|
||||
: ""
|
||||
})
|
||||
|
||||
const enabled = computed(() => {
|
||||
@@ -13,7 +14,8 @@ export const pendingSubmission = (form) => {
|
||||
|
||||
const set = (value) => {
|
||||
if (import.meta.server || !enabled.value) return
|
||||
useStorage(formPendingSubmissionKey.value).value = value === null ? value : JSON.stringify(value)
|
||||
useStorage(formPendingSubmissionKey.value).value =
|
||||
value === null ? value : JSON.stringify(value)
|
||||
}
|
||||
|
||||
const remove = () => {
|
||||
@@ -30,6 +32,6 @@ export const pendingSubmission = (form) => {
|
||||
enabled,
|
||||
set,
|
||||
get,
|
||||
remove
|
||||
remove,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import FormPropertyLogicRule from "~/lib/forms/FormPropertyLogicRule.js";
|
||||
import FormPropertyLogicRule from "~/lib/forms/FormPropertyLogicRule.js"
|
||||
|
||||
export const validatePropertiesLogic = (properties) => {
|
||||
properties.forEach((field) => {
|
||||
const isValid = (new FormPropertyLogicRule(field)).isValid()
|
||||
const isValid = new FormPropertyLogicRule(field).isValid()
|
||||
if (!isValid) {
|
||||
field.logic = {
|
||||
conditions: null,
|
||||
actions: []
|
||||
actions: [],
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
43
client/composables/lib/vForm/Errors.js
vendored
43
client/composables/lib/vForm/Errors.js
vendored
@@ -1,75 +1,74 @@
|
||||
|
||||
function arrayWrap(value) {
|
||||
return Array.isArray(value) ? value : [value];
|
||||
return Array.isArray(value) ? value : [value]
|
||||
}
|
||||
|
||||
export default class Errors {
|
||||
constructor() {
|
||||
this.errors = {};
|
||||
this.errors = {}
|
||||
}
|
||||
|
||||
set(field, messages = undefined) {
|
||||
if (typeof field === 'object') {
|
||||
this.errors = field;
|
||||
if (typeof field === "object") {
|
||||
this.errors = field
|
||||
} else {
|
||||
this.set({ ...this.errors, [field]: arrayWrap(messages) });
|
||||
this.set({ ...this.errors, [field]: arrayWrap(messages) })
|
||||
}
|
||||
}
|
||||
|
||||
all() {
|
||||
return this.errors;
|
||||
return this.errors
|
||||
}
|
||||
|
||||
has(field) {
|
||||
return Object.prototype.hasOwnProperty.call(this.errors, field);
|
||||
return Object.prototype.hasOwnProperty.call(this.errors, field)
|
||||
}
|
||||
|
||||
hasAny(...fields) {
|
||||
return fields.some(field => this.has(field));
|
||||
return fields.some((field) => this.has(field))
|
||||
}
|
||||
|
||||
any() {
|
||||
return Object.keys(this.errors).length > 0;
|
||||
return Object.keys(this.errors).length > 0
|
||||
}
|
||||
|
||||
get(field) {
|
||||
if (this.has(field)) {
|
||||
return this.getAll(field)[0];
|
||||
return this.getAll(field)[0]
|
||||
}
|
||||
}
|
||||
|
||||
getAll(field) {
|
||||
return arrayWrap(this.errors[field] || []);
|
||||
return arrayWrap(this.errors[field] || [])
|
||||
}
|
||||
|
||||
only(...fields) {
|
||||
const messages = [];
|
||||
const messages = []
|
||||
|
||||
fields.forEach((field) => {
|
||||
const message = this.get(field);
|
||||
const message = this.get(field)
|
||||
if (message) {
|
||||
messages.push(message);
|
||||
messages.push(message)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
return messages;
|
||||
return messages
|
||||
}
|
||||
|
||||
flatten() {
|
||||
return Object.values(this.errors).reduce((a, b) => a.concat(b), []);
|
||||
return Object.values(this.errors).reduce((a, b) => a.concat(b), [])
|
||||
}
|
||||
|
||||
clear(field = undefined) {
|
||||
const errors = {};
|
||||
const errors = {}
|
||||
|
||||
if (field) {
|
||||
Object.keys(this.errors).forEach((key) => {
|
||||
if (key !== field) {
|
||||
errors[key] = this.errors[key];
|
||||
errors[key] = this.errors[key]
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
this.set(errors);
|
||||
this.set(errors)
|
||||
}
|
||||
}
|
||||
|
||||
152
client/composables/lib/vForm/Form.js
vendored
152
client/composables/lib/vForm/Form.js
vendored
@@ -1,170 +1,182 @@
|
||||
import {serialize} from 'object-to-formdata';
|
||||
import Errors from './Errors';
|
||||
import cloneDeep from 'clone-deep';
|
||||
import {opnFetch} from "~/composables/useOpnApi.js";
|
||||
import { serialize } from "object-to-formdata"
|
||||
import Errors from "./Errors"
|
||||
import cloneDeep from "clone-deep"
|
||||
import { opnFetch } from "~/composables/useOpnApi.js"
|
||||
function hasFiles(data) {
|
||||
return data instanceof File ||
|
||||
return (
|
||||
data instanceof File ||
|
||||
data instanceof Blob ||
|
||||
data instanceof FileList ||
|
||||
(typeof data === 'object' && data !== null && Object.values(data).find(value => hasFiles(value)) !== undefined);
|
||||
(typeof data === "object" &&
|
||||
data !== null &&
|
||||
Object.values(data).find((value) => hasFiles(value)) !== undefined)
|
||||
)
|
||||
}
|
||||
|
||||
class Form {
|
||||
constructor(data = {}) {
|
||||
this.originalData = {};
|
||||
this.busy = false;
|
||||
this.successful = false;
|
||||
this.recentlySuccessful = false;
|
||||
this.recentlySuccessfulTimeoutId = undefined;
|
||||
this.errors = new Errors();
|
||||
this.update(data);
|
||||
this.originalData = {}
|
||||
this.busy = false
|
||||
this.successful = false
|
||||
this.recentlySuccessful = false
|
||||
this.recentlySuccessfulTimeoutId = undefined
|
||||
this.errors = new Errors()
|
||||
this.update(data)
|
||||
}
|
||||
|
||||
static errorMessage = 'Something went wrong. Please try again.';
|
||||
static recentlySuccessfulTimeout = 2000;
|
||||
static ignore = ['busy', 'successful', 'errors', 'originalData', 'recentlySuccessful', 'recentlySuccessfulTimeoutId'];
|
||||
static errorMessage = "Something went wrong. Please try again."
|
||||
static recentlySuccessfulTimeout = 2000
|
||||
static ignore = [
|
||||
"busy",
|
||||
"successful",
|
||||
"errors",
|
||||
"originalData",
|
||||
"recentlySuccessful",
|
||||
"recentlySuccessfulTimeoutId",
|
||||
]
|
||||
|
||||
static make(augment) {
|
||||
return new this(augment);
|
||||
return new this(augment)
|
||||
}
|
||||
|
||||
update(data) {
|
||||
this.originalData = Object.assign({}, this.originalData, cloneDeep(data));
|
||||
Object.assign(this, data);
|
||||
this.originalData = Object.assign({}, this.originalData, cloneDeep(data))
|
||||
Object.assign(this, data)
|
||||
}
|
||||
|
||||
fill(data = {}) {
|
||||
this.keys().forEach((key) => {
|
||||
this[key] = data[key];
|
||||
});
|
||||
this[key] = data[key]
|
||||
})
|
||||
}
|
||||
|
||||
data() {
|
||||
return this.keys().reduce((data, key) => (
|
||||
{...data, [key]: this[key]}
|
||||
), {});
|
||||
return this.keys().reduce(
|
||||
(data, key) => ({ ...data, [key]: this[key] }),
|
||||
{},
|
||||
)
|
||||
}
|
||||
|
||||
keys() {
|
||||
return Object.keys(this).filter(key => !Form.ignore.includes(key));
|
||||
return Object.keys(this).filter((key) => !Form.ignore.includes(key))
|
||||
}
|
||||
|
||||
startProcessing() {
|
||||
this.errors.clear();
|
||||
this.busy = true;
|
||||
this.successful = false;
|
||||
this.recentlySuccessful = false;
|
||||
clearTimeout(this.recentlySuccessfulTimeoutId);
|
||||
this.errors.clear()
|
||||
this.busy = true
|
||||
this.successful = false
|
||||
this.recentlySuccessful = false
|
||||
clearTimeout(this.recentlySuccessfulTimeoutId)
|
||||
}
|
||||
|
||||
finishProcessing() {
|
||||
this.busy = false;
|
||||
this.successful = true;
|
||||
this.recentlySuccessful = true;
|
||||
this.busy = false
|
||||
this.successful = true
|
||||
this.recentlySuccessful = true
|
||||
this.recentlySuccessfulTimeoutId = setTimeout(() => {
|
||||
this.recentlySuccessful = false;
|
||||
}, Form.recentlySuccessfulTimeout);
|
||||
this.recentlySuccessful = false
|
||||
}, Form.recentlySuccessfulTimeout)
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.errors.clear();
|
||||
this.successful = false;
|
||||
this.recentlySuccessful = false;
|
||||
clearTimeout(this.recentlySuccessfulTimeoutId);
|
||||
this.errors.clear()
|
||||
this.successful = false
|
||||
this.recentlySuccessful = false
|
||||
clearTimeout(this.recentlySuccessfulTimeoutId)
|
||||
}
|
||||
|
||||
reset() {
|
||||
Object.keys(this)
|
||||
.filter(key => !Form.ignore.includes(key))
|
||||
.filter((key) => !Form.ignore.includes(key))
|
||||
.forEach((key) => {
|
||||
this[key] = JSON.parse(JSON.stringify(this.originalData[key]));
|
||||
});
|
||||
this[key] = JSON.parse(JSON.stringify(this.originalData[key]))
|
||||
})
|
||||
}
|
||||
|
||||
get(url, config = {}) {
|
||||
return this.submit('get', url, config);
|
||||
return this.submit("get", url, config)
|
||||
}
|
||||
|
||||
post(url, config = {}) {
|
||||
return this.submit('post', url, config);
|
||||
return this.submit("post", url, config)
|
||||
}
|
||||
|
||||
patch(url, config = {}) {
|
||||
return this.submit('patch', url, config);
|
||||
return this.submit("patch", url, config)
|
||||
}
|
||||
|
||||
put(url, config = {}) {
|
||||
return this.submit('put', url, config);
|
||||
return this.submit("put", url, config)
|
||||
}
|
||||
|
||||
delete(url, config = {}) {
|
||||
return this.submit('delete', url, config);
|
||||
return this.submit("delete", url, config)
|
||||
}
|
||||
|
||||
submit(method, url, config = {}) {
|
||||
this.startProcessing();
|
||||
this.startProcessing()
|
||||
|
||||
config = {
|
||||
body: {},
|
||||
params: {},
|
||||
url: url,
|
||||
method: method,
|
||||
...config
|
||||
};
|
||||
...config,
|
||||
}
|
||||
|
||||
if (method.toLowerCase() === 'get') {
|
||||
config.params = {...this.data(), ...config.params};
|
||||
if (method.toLowerCase() === "get") {
|
||||
config.params = { ...this.data(), ...config.params }
|
||||
} else {
|
||||
config.body = {...this.data(), ...config.data};
|
||||
config.body = { ...this.data(), ...config.data }
|
||||
|
||||
if (hasFiles(config.data) && !config.transformRequest) {
|
||||
config.transformRequest = [data => serialize(data)];
|
||||
config.transformRequest = [(data) => serialize(data)]
|
||||
}
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
opnFetch(config.url, config)
|
||||
.then((data) => {
|
||||
this.finishProcessing();
|
||||
resolve(data);
|
||||
}).catch((error) => {
|
||||
this.handleErrors(error);
|
||||
this.finishProcessing()
|
||||
resolve(data)
|
||||
})
|
||||
.catch((error) => {
|
||||
this.handleErrors(error)
|
||||
reject(error)
|
||||
})
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
handleErrors(error) {
|
||||
this.busy = false;
|
||||
this.busy = false
|
||||
|
||||
if (error) {
|
||||
this.errors.set(this.extractErrors(error.data));
|
||||
this.errors.set(this.extractErrors(error.data))
|
||||
}
|
||||
}
|
||||
|
||||
extractErrors(data) {
|
||||
if (!data || typeof data !== 'object') {
|
||||
return {error: Form.errorMessage};
|
||||
|
||||
if (!data || typeof data !== "object") {
|
||||
return { error: Form.errorMessage }
|
||||
}
|
||||
|
||||
if (data.errors) {
|
||||
return {...data.errors};
|
||||
return { ...data.errors }
|
||||
}
|
||||
|
||||
if (data.message) {
|
||||
return {error: data.message};
|
||||
return { error: data.message }
|
||||
}
|
||||
|
||||
return {...data};
|
||||
return { ...data }
|
||||
}
|
||||
|
||||
onKeydown(event) {
|
||||
const target = event.target;
|
||||
const target = event.target
|
||||
|
||||
if (target.name) {
|
||||
this.errors.clear(target.name);
|
||||
this.errors.clear(target.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Form;
|
||||
export default Form
|
||||
|
||||
14
client/composables/stores/useContentStore.js
vendored
14
client/composables/stores/useContentStore.js
vendored
@@ -1,8 +1,6 @@
|
||||
|
||||
// Composable with all the logic to encapsulate a default content store
|
||||
|
||||
export const useContentStore = (mapKey = 'id') => {
|
||||
|
||||
export const useContentStore = (mapKey = "id") => {
|
||||
const content = ref(new Map())
|
||||
const loading = ref(false)
|
||||
|
||||
@@ -12,7 +10,9 @@ export const useContentStore = (mapKey = 'id') => {
|
||||
})
|
||||
const getByKey = (key) => {
|
||||
if (Array.isArray(key)) {
|
||||
return key.map((k) => content.value.get(k)).filter((item) => item !== undefined)
|
||||
return key
|
||||
.map((k) => content.value.get(k))
|
||||
.filter((item) => item !== undefined)
|
||||
}
|
||||
return content.value.get(key)
|
||||
}
|
||||
@@ -21,7 +21,7 @@ export const useContentStore = (mapKey = 'id') => {
|
||||
|
||||
// Actions
|
||||
function set(items) {
|
||||
content.value = new Map
|
||||
content.value = new Map()
|
||||
save(items)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export const useContentStore = (mapKey = 'id') => {
|
||||
})
|
||||
}
|
||||
function remove(item) {
|
||||
content.value.delete( typeof item === 'object' ? item[mapKey] : item)
|
||||
content.value.delete(typeof item === "object" ? item[mapKey] : item)
|
||||
}
|
||||
|
||||
function startLoading() {
|
||||
@@ -60,6 +60,6 @@ export const useContentStore = (mapKey = 'id') => {
|
||||
remove,
|
||||
startLoading,
|
||||
stopLoading,
|
||||
resetState
|
||||
resetState,
|
||||
}
|
||||
}
|
||||
|
||||
31
client/composables/useAlert.js
vendored
31
client/composables/useAlert.js
vendored
@@ -1,44 +1,43 @@
|
||||
const { notify } = useNotification()
|
||||
|
||||
export const useAlert = () => {
|
||||
|
||||
function success(message, autoClose = 10000) {
|
||||
notify({
|
||||
title: 'Success',
|
||||
title: "Success",
|
||||
text: message,
|
||||
type: 'success',
|
||||
duration: autoClose
|
||||
type: "success",
|
||||
duration: autoClose,
|
||||
})
|
||||
}
|
||||
|
||||
function error(message, autoClose = 10000) {
|
||||
notify({
|
||||
title: 'Error',
|
||||
title: "Error",
|
||||
text: message,
|
||||
type: 'error',
|
||||
duration: autoClose
|
||||
type: "error",
|
||||
duration: autoClose,
|
||||
})
|
||||
}
|
||||
|
||||
function warning(message, autoClose = 10000) {
|
||||
notify({
|
||||
title: 'Warning',
|
||||
title: "Warning",
|
||||
text: message,
|
||||
type: 'warning',
|
||||
duration: autoClose
|
||||
type: "warning",
|
||||
duration: autoClose,
|
||||
})
|
||||
}
|
||||
|
||||
function confirm(message, success, failure = ()=>{}, autoClose = 10000) {
|
||||
function confirm(message, success, failure = () => {}, autoClose = 10000) {
|
||||
notify({
|
||||
title: 'Confirm',
|
||||
title: "Confirm",
|
||||
text: message,
|
||||
type: 'confirm',
|
||||
type: "confirm",
|
||||
duration: autoClose,
|
||||
data: {
|
||||
success,
|
||||
failure
|
||||
}
|
||||
failure,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -46,6 +45,6 @@ export const useAlert = () => {
|
||||
success,
|
||||
error,
|
||||
warning,
|
||||
confirm
|
||||
confirm,
|
||||
}
|
||||
}
|
||||
|
||||
17
client/composables/useAmplitude.js
vendored
17
client/composables/useAmplitude.js
vendored
@@ -1,21 +1,21 @@
|
||||
import amplitude from 'amplitude-js'
|
||||
import amplitude from "amplitude-js"
|
||||
|
||||
export const useAmplitude = () => {
|
||||
const config = useRuntimeConfig()
|
||||
const amplitudeCode = config.public.amplitudeCode
|
||||
const amplitudeClient = amplitudeCode ? amplitude.getInstance() : null;
|
||||
const amplitudeClient = amplitudeCode ? amplitude.getInstance() : null
|
||||
if (amplitudeClient) {
|
||||
amplitudeClient.init(amplitudeCode)
|
||||
}
|
||||
|
||||
const logEvent = function (eventName, eventData) {
|
||||
if (!config.public.env === 'production' || !amplitudeClient) {
|
||||
console.log('[DEBUG] Amplitude logged event:', eventName, eventData)
|
||||
if (!config.public.env === "production" || !amplitudeClient) {
|
||||
console.log("[DEBUG] Amplitude logged event:", eventName, eventData)
|
||||
return
|
||||
}
|
||||
|
||||
if (eventData && typeof eventData !== 'object') {
|
||||
throw new Error('Amplitude event value must be an object.')
|
||||
if (eventData && typeof eventData !== "object") {
|
||||
throw new Error("Amplitude event value must be an object.")
|
||||
}
|
||||
|
||||
amplitudeClient.logEvent(eventName, eventData)
|
||||
@@ -27,14 +27,13 @@ export const useAmplitude = () => {
|
||||
amplitudeClient.setUserProperties({
|
||||
email: user.email,
|
||||
subscribed: user.is_subscribed,
|
||||
enterprise_subscription: user.has_enterprise_subscription
|
||||
enterprise_subscription: user.has_enterprise_subscription,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
logEvent,
|
||||
setUser,
|
||||
amplitude: amplitudeClient
|
||||
amplitude: amplitudeClient,
|
||||
}
|
||||
}
|
||||
|
||||
12
client/composables/useConfetti.js
vendored
12
client/composables/useConfetti.js
vendored
@@ -1,22 +1,22 @@
|
||||
import { ref, onUnmounted } from 'vue'
|
||||
import { ref, onUnmounted } from "vue"
|
||||
|
||||
export const useConfetti = () => {
|
||||
let timeoutId = ref(null)
|
||||
const nuxtApp = useNuxtApp()
|
||||
const $confetti = nuxtApp.vueApp.config.globalProperties.$confetti
|
||||
|
||||
function play(duration=3000) {
|
||||
|
||||
function play(duration = 3000) {
|
||||
$confetti.start({ defaultSize: 6 })
|
||||
timeoutId = setTimeout(() => {
|
||||
timeoutId.value = setTimeout(() => {
|
||||
$confetti.stop()
|
||||
}, duration)
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timeoutId) clearTimeout(timeoutId)
|
||||
if (timeoutId.value) clearTimeout(timeoutId)
|
||||
})
|
||||
|
||||
return {
|
||||
play
|
||||
play,
|
||||
}
|
||||
}
|
||||
|
||||
26
client/composables/useCrisp.js
vendored
26
client/composables/useCrisp.js
vendored
@@ -1,5 +1,4 @@
|
||||
export const useCrisp = () => {
|
||||
|
||||
let crisp = import.meta.client ? window.Crisp : null
|
||||
|
||||
function openChat() {
|
||||
@@ -35,29 +34,32 @@ export const useCrisp = () => {
|
||||
crisp.chat.setHelpdeskView()
|
||||
}
|
||||
|
||||
function openHelpdeskArticle(articleSlug, locale = 'en') {
|
||||
function openHelpdeskArticle(articleSlug, locale = "en") {
|
||||
if (!crisp) return
|
||||
crisp.chat.openHelpdeskArticle(locale, articleSlug);
|
||||
crisp.chat.openHelpdeskArticle(locale, articleSlug)
|
||||
}
|
||||
|
||||
function sendTextMessage(message) {
|
||||
if (!crisp) return
|
||||
crisp.message.send('text', message)
|
||||
crisp.message.send("text", message)
|
||||
}
|
||||
|
||||
function setUser(user) {
|
||||
if (!crisp) return
|
||||
crisp.user.setEmail(user.email);
|
||||
crisp.user.setNickname(user.name);
|
||||
crisp.user.setEmail(user.email)
|
||||
crisp.user.setNickname(user.name)
|
||||
crisp.session.setData({
|
||||
user_id: user.id,
|
||||
'pro-subscription': user?.is_subscribed ?? false,
|
||||
'stripe-id': user?.stripe_id ?? '',
|
||||
'subscription': user?.has_enterprise_subscription ? 'enterprise' : 'pro'
|
||||
});
|
||||
"pro-subscription": user?.is_subscribed ?? false,
|
||||
"stripe-id": user?.stripe_id ?? "",
|
||||
subscription: user?.has_enterprise_subscription ? "enterprise" : "pro",
|
||||
})
|
||||
|
||||
if (user?.is_subscribed ?? false) {
|
||||
setSegments(['subscribed', user?.has_enterprise_subscription ? 'enterprise' : 'pro'])
|
||||
setSegments([
|
||||
"subscribed",
|
||||
user?.has_enterprise_subscription ? "enterprise" : "pro",
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +84,6 @@ export const useCrisp = () => {
|
||||
openHelpdeskArticle,
|
||||
sendTextMessage,
|
||||
pushEvent,
|
||||
setUser
|
||||
setUser,
|
||||
}
|
||||
}
|
||||
|
||||
40
client/composables/useOpnApi.js
vendored
40
client/composables/useOpnApi.js
vendored
@@ -1,20 +1,25 @@
|
||||
import {getDomain, getHost, customDomainUsed} from "~/lib/utils.js";
|
||||
import { getDomain, getHost, customDomainUsed } from "~/lib/utils.js"
|
||||
|
||||
function addAuthHeader(request, options) {
|
||||
const authStore = useAuthStore()
|
||||
if (authStore.token) {
|
||||
options.headers = {Authorization: `Bearer ${authStore.token}`, ...options.headers}
|
||||
options.headers = {
|
||||
Authorization: `Bearer ${authStore.token}`,
|
||||
...options.headers,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addPasswordToFormRequest(request, options) {
|
||||
if (!request || !request.startsWith('/forms/')) return
|
||||
if (!request || !request.startsWith("/forms/")) return
|
||||
|
||||
const slug = request.split('/')[2]
|
||||
const slug = request.split("/")[2]
|
||||
|
||||
const passwordCookie = useCookie('password-' + slug, {maxAge: 60 * 60 * 24 * 30}) // 30 days
|
||||
if (slug !== undefined && slug !== '' && passwordCookie.value !== undefined) {
|
||||
options.headers['form-password'] = passwordCookie.value
|
||||
const passwordCookie = useCookie("password-" + slug, {
|
||||
maxAge: 60 * 60 * 24 * 30,
|
||||
}) // 30 days
|
||||
if (slug !== undefined && slug !== "" && passwordCookie.value !== undefined) {
|
||||
options.headers["form-password"] = passwordCookie.value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +28,7 @@ function addPasswordToFormRequest(request, options) {
|
||||
*/
|
||||
function addCustomDomainHeader(request, options) {
|
||||
if (!customDomainUsed()) return
|
||||
options.headers['x-custom-domain'] = getDomain(getHost())
|
||||
options.headers["x-custom-domain"] = getDomain(getHost())
|
||||
}
|
||||
|
||||
export function getOpnRequestsOptions(request, opts) {
|
||||
@@ -31,16 +36,16 @@ export function getOpnRequestsOptions(request, opts) {
|
||||
|
||||
if (opts.body && opts.body instanceof FormData) {
|
||||
opts.headers = {
|
||||
'charset': 'utf-8',
|
||||
charset: "utf-8",
|
||||
...opts.headers,
|
||||
}
|
||||
}
|
||||
|
||||
opts.headers = {accept: 'application/json', ...opts.headers}
|
||||
opts.headers = { accept: "application/json", ...opts.headers }
|
||||
|
||||
// Authenticate requests coming from the server
|
||||
if (import.meta.server && config.apiSecret) {
|
||||
opts.headers['x-api-secret'] = config.apiSecret
|
||||
opts.headers["x-api-secret"] = config.apiSecret
|
||||
}
|
||||
|
||||
addAuthHeader(request, opts)
|
||||
@@ -50,24 +55,25 @@ export function getOpnRequestsOptions(request, opts) {
|
||||
if (!opts.baseURL) opts.baseURL = config.privateApiBase || config.public.apiBase
|
||||
|
||||
return {
|
||||
async onResponseError({response}) {
|
||||
async onResponseError({ response }) {
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const {status} = response
|
||||
const { status } = response
|
||||
if (status === 401) {
|
||||
if (authStore.check) {
|
||||
console.log("Logging out due to 401")
|
||||
authStore.logout()
|
||||
useRouter().push({name: 'login'})
|
||||
useRouter().push({ name: "login" })
|
||||
}
|
||||
} else if (status === 420) {
|
||||
// If invalid domain, redirect to main domain
|
||||
window.location.href = config.public.appUrl + '?utm_source=failed_custom_domain_redirect'
|
||||
window.location.href =
|
||||
config.public.appUrl + "?utm_source=failed_custom_domain_redirect"
|
||||
} else if (status >= 500) {
|
||||
console.error('Request error', status)
|
||||
console.error("Request error", status)
|
||||
}
|
||||
},
|
||||
...opts
|
||||
...opts,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
28
client/composables/useOpnSeoMeta.js
vendored
28
client/composables/useOpnSeoMeta.js
vendored
@@ -1,16 +1,22 @@
|
||||
export const useOpnSeoMeta = (meta) => {
|
||||
return useSeoMeta({
|
||||
...meta.title ? {
|
||||
ogTitle: meta.title,
|
||||
twitterTitle: meta.title,
|
||||
} : {},
|
||||
...meta.description ? {
|
||||
ogDescription: meta.description,
|
||||
twitterDescription: meta.description,
|
||||
} : {},
|
||||
...meta.ogImage ? {
|
||||
twitterImage: meta.ogImage,
|
||||
} : {},
|
||||
...(meta.title
|
||||
? {
|
||||
ogTitle: meta.title,
|
||||
twitterTitle: meta.title,
|
||||
}
|
||||
: {}),
|
||||
...(meta.description
|
||||
? {
|
||||
ogDescription: meta.description,
|
||||
twitterDescription: meta.description,
|
||||
}
|
||||
: {}),
|
||||
...(meta.ogImage
|
||||
? {
|
||||
twitterImage: meta.ogImage,
|
||||
}
|
||||
: {}),
|
||||
...meta,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user