WIP
This commit is contained in:
parent
5c4dc2a3d6
commit
a3a9254665
|
@ -33,7 +33,7 @@ export function useFormInput (props, context, formPrefixKey = null) {
|
|||
})
|
||||
|
||||
const hasError = computed(() => {
|
||||
return hasValidation && props.form?.errors?.has(name)
|
||||
return hasValidation && props.form?.errors?.has(props.name)
|
||||
})
|
||||
|
||||
const compVal = computed({
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<portal to="modals" :order="portalOrder">
|
||||
<Teleport to="body">
|
||||
<transition @leave="(el,done) => motions.backdrop.leave(done)">
|
||||
<div v-if="show" v-motion="'backdrop'" :variants="motionFadeIn"
|
||||
class="fixed z-30 top-0 inset-0 px-4 sm:px-0 flex items-top justify-center bg-gray-700/75 w-full h-screen overflow-y-scroll"
|
||||
|
@ -46,7 +46,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</portal>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -71,9 +71,6 @@ export default {
|
|||
},
|
||||
closeable: {
|
||||
default: true
|
||||
},
|
||||
portalOrder: {
|
||||
default: 1
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -279,7 +279,7 @@ export default {
|
|||
]
|
||||
},
|
||||
init () {
|
||||
if (this.$route.name === 'forms.create' || this.$route.name === 'forms-create-guest') { // Set Default fields
|
||||
if (this.$route.name === 'forms-create' || this.$route.name === 'forms-create-guest') { // Set Default fields
|
||||
this.formFields = (this.form.properties.length > 0) ? clonedeep(this.form.properties) : this.getDefaultFields()
|
||||
} else {
|
||||
this.formFields = clonedeep(this.form.properties).map((field) => {
|
||||
|
|
|
@ -41,19 +41,12 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { computed } from 'vue'
|
||||
import Form from 'vform'
|
||||
import Cookies from 'js-cookie'
|
||||
import { useAuthStore } from '../../../../stores/auth.js'
|
||||
import OpenFormFooter from '../../OpenFormFooter.vue'
|
||||
import Testimonials from '../../welcome/Testimonials.vue'
|
||||
import ForgotPasswordModal from '../ForgotPasswordModal.vue'
|
||||
|
||||
export default {
|
||||
name: 'LoginForm',
|
||||
components: {
|
||||
OpenFormFooter,
|
||||
Testimonials,
|
||||
ForgotPasswordModal
|
||||
},
|
||||
props: {
|
||||
|
@ -72,7 +65,7 @@ export default {
|
|||
},
|
||||
|
||||
data: () => ({
|
||||
form: new Form({
|
||||
form: useForm({
|
||||
email: '',
|
||||
password: ''
|
||||
}),
|
||||
|
@ -83,10 +76,10 @@ export default {
|
|||
methods: {
|
||||
async login () {
|
||||
// Submit the form.
|
||||
const { data } = await this.form.post('/api/login')
|
||||
const data = await this.form.post('login')
|
||||
|
||||
// Save the token.
|
||||
this.authStore.saveToken(data.token, this.remember)
|
||||
this.authStore.setToken(data.token, this.remember)
|
||||
|
||||
// Fetch the user.
|
||||
await this.authStore.fetchUser()
|
||||
|
|
|
@ -142,7 +142,7 @@ export default {
|
|||
if (this.isQuick) {
|
||||
this.$emit('afterQuickLogin')
|
||||
} else {
|
||||
this.$router.push({ name: 'forms.create' })
|
||||
this.$router.push({ name: 'forms-create' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
<!-- <v-button v-if="!authenticated" class="mr-2 block" :to="{ name: 'forms-create-guest' }" :arrow="true">-->
|
||||
<!-- Get started for free-->
|
||||
<!-- </v-button>-->
|
||||
<!-- <v-button v-else class="mr-2 block" :to="{ name: 'forms.create' }" :arrow="true">-->
|
||||
<!-- <v-button v-else class="mr-2 block" :to="{ name: 'forms-create' }" :arrow="true">-->
|
||||
<!-- Get started for free-->
|
||||
<!-- </v-button>-->
|
||||
<!-- <v-button color="light-gray" class="mr-1 block" :to="{ name: 'aiformbuilder' }">-->
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
|
||||
function arrayWrap(value) {
|
||||
return Array.isArray(value) ? value : [value];
|
||||
}
|
||||
|
||||
export default class Errors {
|
||||
constructor() {
|
||||
this.errors = {};
|
||||
}
|
||||
|
||||
set(field, messages = undefined) {
|
||||
if (typeof field === 'object') {
|
||||
this.errors = field;
|
||||
} else {
|
||||
this.set({ ...this.errors, [field]: arrayWrap(messages) });
|
||||
}
|
||||
}
|
||||
|
||||
all() {
|
||||
return this.errors;
|
||||
}
|
||||
|
||||
has(field) {
|
||||
return Object.prototype.hasOwnProperty.call(this.errors, field);
|
||||
}
|
||||
|
||||
hasAny(...fields) {
|
||||
return fields.some(field => this.has(field));
|
||||
}
|
||||
|
||||
any() {
|
||||
return Object.keys(this.errors).length > 0;
|
||||
}
|
||||
|
||||
get(field) {
|
||||
if (this.has(field)) {
|
||||
return this.getAll(field)[0];
|
||||
}
|
||||
}
|
||||
|
||||
getAll(field) {
|
||||
return arrayWrap(this.errors[field] || []);
|
||||
}
|
||||
|
||||
only(...fields) {
|
||||
const messages = [];
|
||||
|
||||
fields.forEach((field) => {
|
||||
const message = this.get(field);
|
||||
if (message) {
|
||||
messages.push(message);
|
||||
}
|
||||
});
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
flatten() {
|
||||
return Object.values(this.errors).reduce((a, b) => a.concat(b), []);
|
||||
}
|
||||
|
||||
clear(field = undefined) {
|
||||
const errors = {};
|
||||
|
||||
if (field) {
|
||||
Object.keys(this.errors).forEach((key) => {
|
||||
if (key !== field) {
|
||||
errors[key] = this.errors[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.set(errors);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,175 @@
|
|||
import {serialize} from 'object-to-formdata';
|
||||
import Errors from './Errors';
|
||||
import cloneDeep from 'clone-deep';
|
||||
import {useOpnFetch} from "~/composables/useOpnFetch.js";
|
||||
|
||||
function hasFiles(data) {
|
||||
return data instanceof File ||
|
||||
data instanceof Blob ||
|
||||
data instanceof FileList ||
|
||||
(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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
update(data) {
|
||||
this.originalData = Object.assign({}, this.originalData, cloneDeep(data));
|
||||
Object.assign(this, data);
|
||||
}
|
||||
|
||||
fill(data = {}) {
|
||||
this.keys().forEach((key) => {
|
||||
this[key] = data[key];
|
||||
});
|
||||
}
|
||||
|
||||
data() {
|
||||
return this.keys().reduce((data, key) => (
|
||||
{...data, [key]: this[key]}
|
||||
), {});
|
||||
}
|
||||
|
||||
keys() {
|
||||
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);
|
||||
}
|
||||
|
||||
finishProcessing() {
|
||||
this.busy = false;
|
||||
this.successful = true;
|
||||
this.recentlySuccessful = true;
|
||||
this.recentlySuccessfulTimeoutId = setTimeout(() => {
|
||||
this.recentlySuccessful = false;
|
||||
}, Form.recentlySuccessfulTimeout);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.errors.clear();
|
||||
this.successful = false;
|
||||
this.recentlySuccessful = false;
|
||||
clearTimeout(this.recentlySuccessfulTimeoutId);
|
||||
}
|
||||
|
||||
reset() {
|
||||
Object.keys(this)
|
||||
.filter(key => !Form.ignore.includes(key))
|
||||
.forEach((key) => {
|
||||
this[key] = deepCopy(this.originalData[key]);
|
||||
});
|
||||
}
|
||||
|
||||
get(url, config = {}) {
|
||||
return this.submit('get', url, config);
|
||||
}
|
||||
|
||||
post(url, config = {}) {
|
||||
return this.submit('post', url, config);
|
||||
}
|
||||
|
||||
patch(url, config = {}) {
|
||||
return this.submit('patch', url, config);
|
||||
}
|
||||
|
||||
put(url, config = {}) {
|
||||
return this.submit('put', url, config);
|
||||
}
|
||||
|
||||
delete(url, config = {}) {
|
||||
return this.submit('delete', url, config);
|
||||
}
|
||||
|
||||
submit(method, url, config = {}) {
|
||||
this.startProcessing();
|
||||
|
||||
config = {
|
||||
body: {},
|
||||
params: {},
|
||||
url: url,
|
||||
method: method,
|
||||
...config
|
||||
};
|
||||
|
||||
if (method.toLowerCase() === 'get') {
|
||||
config.params = {...this.data(), ...config.params};
|
||||
} else {
|
||||
config.body = {...this.data(), ...config.data};
|
||||
|
||||
if (hasFiles(config.data) && !config.transformRequest) {
|
||||
config.transformRequest = [data => serialize(data)];
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
useOpnFetch(config.url, config)
|
||||
.then(({data, error}) => {
|
||||
if (error.value) {
|
||||
this.handleErrors(error);
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
this.finishProcessing();
|
||||
resolve(data.value);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
handleErrors(error) {
|
||||
this.busy = false;
|
||||
|
||||
if (error.value) {
|
||||
this.errors.set(this.extractErrors(error.value.data));
|
||||
}
|
||||
}
|
||||
|
||||
extractErrors(data) {
|
||||
if (!data || typeof data !== 'object') {
|
||||
return {error: Form.errorMessage};
|
||||
|
||||
}
|
||||
|
||||
if (data.errors) {
|
||||
return {...data.errors};
|
||||
}
|
||||
|
||||
if (data.message) {
|
||||
return {error: data.message};
|
||||
}
|
||||
|
||||
return {...data};
|
||||
}
|
||||
|
||||
onKeydown(event) {
|
||||
const target = event.target;
|
||||
|
||||
if (target.name) {
|
||||
this.errors.clear(target.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Form;
|
|
@ -0,0 +1,5 @@
|
|||
import Form from "~/composables/lib/vForm/Form.js"
|
||||
|
||||
export const useForm = (formData) => {
|
||||
return new Form(formData)
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import config from "~/opnform.config.js";
|
||||
|
||||
export const useOpnFetch = (request, opts) => {
|
||||
return useFetch(request, { baseURL: config.api_url, ...opts })
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import {useAuthStore} from "../../resources/js/stores/auth.js";
|
||||
|
||||
export default defineNuxtRouteMiddleware((to, from) => {
|
||||
const authStore = useAuthStore()
|
||||
if (!authStore.user?.admin) {
|
||||
navigateTo({ name: 'home' })
|
||||
}
|
||||
})
|
|
@ -0,0 +1,11 @@
|
|||
import {useAuthStore} from "../../resources/js/stores/auth.js";
|
||||
|
||||
export default defineNuxtRouteMiddleware((to, from) => {
|
||||
const authStore = useAuthStore()
|
||||
|
||||
if (!authStore.check) {
|
||||
useCookie('intended_url').value = to.path
|
||||
|
||||
navigateTo({ name: 'login' })
|
||||
}
|
||||
})
|
|
@ -0,0 +1,5 @@
|
|||
export default defineNuxtRouteMiddleware((to, from) => {
|
||||
const authStore = useAuthStore()
|
||||
authStore.loadTokenFromCookie()
|
||||
useAuthStore().fetchUserIfNotFetched()
|
||||
})
|
|
@ -1,6 +1,6 @@
|
|||
export default {
|
||||
"appName": "OpnForm",
|
||||
"app_url": "https://opnform.test",
|
||||
"app_ame": "OpnForm",
|
||||
"api_url": "https://opnform.test/api",
|
||||
"locale": "en",
|
||||
"locales": {"en": "EN"},
|
||||
"githubAuth": null,
|
||||
|
|
|
@ -24,14 +24,13 @@
|
|||
"date-fns": "^2.28.0",
|
||||
"debounce": "^1.2.1",
|
||||
"fuse.js": "^6.4.6",
|
||||
"js-cookie": "^2.2.1",
|
||||
"js-sha256": "^0.9.0",
|
||||
"libphonenumber-js": "^1.10.44",
|
||||
"object-to-formdata": "^4.5.1",
|
||||
"prismjs": "^1.24.1",
|
||||
"qrcode": "^1.5.1",
|
||||
"query-builder-vue-3": "^1.0.1",
|
||||
"tinymotion": "^0.2.0",
|
||||
"vform": "^2.1.1",
|
||||
"vue": "^3.2.13",
|
||||
"vue-chartjs": "^5.2.0",
|
||||
"vue-codemirror": "^4.0.6",
|
||||
|
@ -6751,11 +6750,6 @@
|
|||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
},
|
||||
"node_modules/js-cookie": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz",
|
||||
"integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ=="
|
||||
},
|
||||
"node_modules/js-sha256": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz",
|
||||
|
@ -8094,6 +8088,11 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/object-to-formdata": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/object-to-formdata/-/object-to-formdata-4.5.1.tgz",
|
||||
"integrity": "sha512-QiM9D0NiU5jV6J6tjE1g7b4Z2tcUnKs1OPUi4iMb2zH+7jwlcUrASghgkFk9GtzqNNq8rTQJtT8AzjBAvLoNMw=="
|
||||
},
|
||||
"node_modules/ofetch": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.3.3.tgz",
|
||||
|
@ -10950,15 +10949,6 @@
|
|||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vform": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vform/-/vform-2.1.2.tgz",
|
||||
"integrity": "sha512-Nobg/0ckWHYQAjJJqucOyRv/a3enO40f087KBpkSUCl0eRQTE8qmUqk/l5gmbxhD8UO3A8dDiSRWKmKF8+l4YQ==",
|
||||
"peerDependencies": {
|
||||
"axios": "*",
|
||||
"vue": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.0.7.tgz",
|
||||
|
|
|
@ -36,14 +36,13 @@
|
|||
"date-fns": "^2.28.0",
|
||||
"debounce": "^1.2.1",
|
||||
"fuse.js": "^6.4.6",
|
||||
"js-cookie": "^2.2.1",
|
||||
"js-sha256": "^0.9.0",
|
||||
"libphonenumber-js": "^1.10.44",
|
||||
"object-to-formdata": "^4.5.1",
|
||||
"prismjs": "^1.24.1",
|
||||
"qrcode": "^1.5.1",
|
||||
"query-builder-vue-3": "^1.0.1",
|
||||
"tinymotion": "^0.2.0",
|
||||
"vform": "^2.1.1",
|
||||
"vue": "^3.2.13",
|
||||
"vue-chartjs": "^5.2.0",
|
||||
"vue-codemirror": "^4.0.6",
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<v-button v-if="!authenticated" class="mr-1" :to="{ name: 'forms-create-guest' }" :arrow="true">
|
||||
Get started for free
|
||||
</v-button>
|
||||
<v-button v-else class="mr-1" :to="{ name: 'forms.create' }" :arrow="true">
|
||||
<v-button v-else class="mr-1" :to="{ name: 'forms-create' }" :arrow="true">
|
||||
Get started for free
|
||||
</v-button>
|
||||
</div>
|
||||
|
@ -273,7 +273,7 @@
|
|||
<!--<!– <v-button v-if="!authenticated" class="mr-1" :to="{ name: 'forms-create-guest' }" :arrow="true">–>-->
|
||||
<!--<!– Get started for free–>-->
|
||||
<!--<!– </v-button>–>-->
|
||||
<!--<!– <v-button v-else class="mr-1" :to="{ name: 'forms.create' }" :arrow="true">–>-->
|
||||
<!--<!– <v-button v-else class="mr-1" :to="{ name: 'forms-create' }" :arrow="true">–>-->
|
||||
<!--<!– Get started for free–>-->
|
||||
<!--<!– </v-button>–>-->
|
||||
<!--<!– </div>–>-->
|
||||
|
@ -446,7 +446,7 @@
|
|||
<v-button v-if="!authenticated" class="mr-1" :to="{ name: 'forms-create-guest' }" :arrow="true">
|
||||
Get started for free
|
||||
</v-button>
|
||||
<v-button v-else class="mr-1" :to="{ name: 'forms.create' }" :arrow="true">
|
||||
<v-button v-else class="mr-1" :to="{ name: 'forms-create' }" :arrow="true">
|
||||
Get started for free
|
||||
</v-button>
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<h2 class="flex-grow text-gray-900">
|
||||
Your Forms
|
||||
</h2>
|
||||
<v-button v-track.create_form_click :to="{name:'forms.create'}">
|
||||
<v-button v-track.create_form_click :to="{name:'forms-create'}">
|
||||
<svg class="w-4 h-4 text-white inline mr-1 -mt-1" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.99996 1.1665V12.8332M1.16663 6.99984H12.8333" stroke="currentColor" stroke-width="1.67" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
|
@ -48,7 +48,7 @@
|
|||
<div v-if="isFilteringForms && enrichedForms.length === 0 && searchForm.search" class="mt-2 w-full text-center">
|
||||
Your search "{{ searchForm.search }}" did not match any forms. Please try again.
|
||||
</div>
|
||||
<v-button v-if="forms.length === 0" v-track.create_form_click class="mt-4" :to="{name:'forms.create'}">
|
||||
<v-button v-if="forms.length === 0" v-track.create_form_click class="mt-4" :to="{name:'forms-create'}">
|
||||
<svg class="w-4 h-4 text-white inline mr-1 -mt-1" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.99996 1.1665V12.8332M1.16663 6.99984H12.8333" stroke="currentColor" stroke-width="1.67" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
|
@ -119,7 +119,7 @@ const loadForms = function () {
|
|||
const formsStore = useFormsStore()
|
||||
const workspacesStore = useWorkspacesStore()
|
||||
formsStore.startLoading()
|
||||
workspacesStore.loadIfEmpty().then(() => {
|
||||
return workspacesStore.loadIfEmpty().then(() => {
|
||||
formsStore.loadIfEmpty(workspacesStore.currentId)
|
||||
})
|
||||
}
|
||||
|
@ -127,10 +127,6 @@ const loadForms = function () {
|
|||
export default {
|
||||
components: { OpenFormFooter, TextInput, ExtraMenu },
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
loadForms()
|
||||
next()
|
||||
},
|
||||
middleware: 'auth',
|
||||
|
||||
props: {
|
||||
|
@ -138,10 +134,11 @@ export default {
|
|||
metaDescription: { type: String, default: 'All of your OpnForm are here. Create new forms, or update your existing one!' }
|
||||
},
|
||||
|
||||
setup () {
|
||||
async setup () {
|
||||
const authStore = useAuthStore()
|
||||
const formsStore = useFormsStore()
|
||||
const workspacesStore = useWorkspacesStore()
|
||||
loadForms()
|
||||
return {
|
||||
formsStore,
|
||||
workspacesStore,
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
<v-button v-if="!authenticated" class="mr-1" :to="{ name: 'forms-create-guest' }" :arrow="true">
|
||||
Create a form for FREE
|
||||
</v-button>
|
||||
<v-button v-else class="mr-1" :to="{ name: 'forms.create' }" :arrow="true">
|
||||
<v-button v-else class="mr-1" :to="{ name: 'forms-create' }" :arrow="true">
|
||||
Create a form for FREE
|
||||
</v-button>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import { ofetch } from 'ofetch'
|
||||
import {useAuthStore} from "~/stores/auth.js";
|
||||
|
||||
function addAuthHeader(request, options) {
|
||||
const authStore = useAuthStore()
|
||||
if (authStore.check) {
|
||||
options.headers = { Authorization: `Bearer ${authStore.token}` }
|
||||
console.log('addidng auth',options)
|
||||
}
|
||||
}
|
||||
|
||||
function addPasswordToFormRequest (request) {
|
||||
const url = request.url
|
||||
if (!url || !url.startsWith('/api/forms/')) return
|
||||
|
||||
const slug = url.split('/')[3]
|
||||
const passwordCookie = useCookie('password-' + slug, { maxAge: 60 * 60 * 24 * 30 }) // 30 days
|
||||
if (slug !== undefined && slug !== '' && passwordCookie.value !== undefined) {
|
||||
request.headers['form-password'] = passwordCookie.value
|
||||
}
|
||||
}
|
||||
|
||||
export default defineNuxtPlugin((_nuxtApp) => {
|
||||
globalThis.$fetch = ofetch.create({
|
||||
onRequest ({ request, options }) {
|
||||
// TODO: check that it's our own domain called
|
||||
addAuthHeader(request, options)
|
||||
addPasswordToFormRequest(request)
|
||||
},
|
||||
onResponseError ({ response }) {
|
||||
const authStore = useAuthStore()
|
||||
const { status } = response
|
||||
|
||||
if (status === 401 && authStore.check) {
|
||||
// TODO: check that it's our own domain called
|
||||
authStore.logout()
|
||||
useRouter().push({ name: 'login' })
|
||||
}
|
||||
|
||||
if (status >= 500) {
|
||||
console.error('Request error', status)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
|
@ -1,15 +1,14 @@
|
|||
import {defineStore} from 'pinia'
|
||||
import axios from 'axios'
|
||||
import Cookies from 'js-cookie'
|
||||
|
||||
export const useAuthStore = defineStore('auth', {
|
||||
state: () => ({
|
||||
state: () => {
|
||||
return {
|
||||
token: null,
|
||||
admin_token: null,
|
||||
user: null,
|
||||
token: Cookies.get('token'),
|
||||
|
||||
// For admin impersonation
|
||||
admin_token: Cookies.get('admin_token') ?? null
|
||||
}),
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
check: (state) => (state.user !== null && state.user !== undefined),
|
||||
isImpersonating: (state) => (state.admin_token !== null && state.admin_token !== undefined)
|
||||
|
@ -17,21 +16,28 @@ export const useAuthStore = defineStore('auth', {
|
|||
actions: {
|
||||
// Stores admin token temporarily for impersonation
|
||||
startImpersonating() {
|
||||
this.admin_token = this.token
|
||||
Cookies.set('admin_token', this.token, { expires: 365 })
|
||||
this.setAdminToken(this.token)
|
||||
},
|
||||
// Stop admin impersonation
|
||||
stopImpersonating() {
|
||||
this.token = this.admin_token
|
||||
this.admin_token = null
|
||||
Cookies.set('token', this.token, { expires: 365 })
|
||||
Cookies.remove('admin_token')
|
||||
this.fetchUser()
|
||||
},
|
||||
|
||||
saveToken (token, remember) {
|
||||
setToken(token) {
|
||||
useCookie('token', {maxAge: 60 * 60 * 24 * 30}).value = token
|
||||
this.token = token
|
||||
Cookies.set('token', token, { expires: remember ? 365 : null })
|
||||
},
|
||||
|
||||
setAdminToken(token) {
|
||||
useCookie('admin_token', {maxAge: 60 * 60 * 24 * 30}).value = token
|
||||
this.admin_token = token
|
||||
},
|
||||
|
||||
loadTokenFromCookie() {
|
||||
this.token = useCookie('token').value
|
||||
this.admin_token = useCookie('admin_token').value
|
||||
},
|
||||
|
||||
async fetchUser() {
|
||||
|
@ -42,8 +48,13 @@ export const useAuthStore = defineStore('auth', {
|
|||
|
||||
return data
|
||||
} catch (e) {
|
||||
this.token = null
|
||||
Cookies.remove('token')
|
||||
this.setToken(null)
|
||||
}
|
||||
},
|
||||
|
||||
async fetchUserIfNotFetched() {
|
||||
if (this.user === null && this.token) {
|
||||
await this.fetchUser()
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -56,16 +67,25 @@ export const useAuthStore = defineStore('auth', {
|
|||
if (!this.user) return
|
||||
useAmplitude().setUser(this.user)
|
||||
useCrisp().setUser(this.user)
|
||||
|
||||
// Init sentry
|
||||
Sentry.configureScope((scope) => {
|
||||
scope.setUser({
|
||||
id: this.user.id,
|
||||
email: this.user.email,
|
||||
subscription: this.user?.is_subscribed
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
async logout() {
|
||||
try {
|
||||
await axios.post('/api/logout')
|
||||
} catch (e) { }
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
this.user = null
|
||||
this.token = null
|
||||
Cookies.remove('token')
|
||||
this.setToken(null)
|
||||
},
|
||||
|
||||
async fetchOauthUrl(provider) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {defineStore} from 'pinia'
|
||||
import axios from 'axios'
|
||||
import {useOpnFetch} from "~/composables/useOpnFetch.js";
|
||||
|
||||
export const formsEndpoint = '/api/open/workspaces/{workspaceId}/forms'
|
||||
export const formsEndpoint = '/open/workspaces/{workspaceId}/forms'
|
||||
export let currentPage = 1
|
||||
|
||||
export const useFormsStore = defineStore('forms', {
|
||||
|
@ -56,8 +56,8 @@ export const useFormsStore = defineStore('forms', {
|
|||
},
|
||||
load(workspaceId) {
|
||||
this.startLoading()
|
||||
return axios.get(formsEndpoint.replace('{workspaceId}', workspaceId)+'?page='+currentPage).then((response) => {
|
||||
if (currentPage == 1) {
|
||||
return useOpnFetch(formsEndpoint.replace('{workspaceId}', workspaceId) + '?page=' + currentPage).get().then((response) => {
|
||||
if (currentPage === 1) {
|
||||
this.set(response.data.data)
|
||||
} else {
|
||||
this.append(response.data.data)
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import axios from 'axios'
|
||||
import {defineStore} from 'pinia'
|
||||
export const workspaceEndpoint = '/api/open/workspaces/'
|
||||
import {useOpnFetch} from "~/composables/useOpnFetch.js"
|
||||
import {useStorage} from "@vueuse/core"
|
||||
|
||||
const localStorageCurrentWorkspaceKey = 'currentWorkspace'
|
||||
export const workspaceEndpoint = 'open/workspaces/'
|
||||
|
||||
const storedWorkspaceId = useStorage('currentWorkspace', 0)
|
||||
|
||||
export const useWorkspacesStore = defineStore('workspaces', {
|
||||
state: () => ({
|
||||
|
@ -26,38 +28,34 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
|||
if (this.currentId == null && this.content.length > 0) {
|
||||
// If one only, set it
|
||||
if (this.content.length === 1) {
|
||||
this.currentId = items[0].id
|
||||
localStorage.setItem(localStorageCurrentWorkspaceKey, this.currentId)
|
||||
} else if (localStorage.getItem(localStorageCurrentWorkspaceKey) && this.content.find(item => item.id === parseInt(localStorage.getItem(localStorageCurrentWorkspaceKey)))) {
|
||||
this.setCurrentId(items[0].id)
|
||||
} else if (storedWorkspaceId && this.content.find(item => item.id === parseInt(storedWorkspaceId.value))) {
|
||||
// Check local storage for current workspace, or take first
|
||||
this.currentId = parseInt(localStorage.getItem(localStorageCurrentWorkspaceKey))
|
||||
localStorage.setItem(localStorageCurrentWorkspaceKey, this.currentId)
|
||||
this.setCurrentId(parseInt(storedWorkspaceId.value))
|
||||
} else {
|
||||
// Else, take first
|
||||
this.currentId = items[0].id
|
||||
localStorage.setItem(localStorageCurrentWorkspaceKey, this.currentId)
|
||||
this.setCurrentId(items[0].id)
|
||||
}
|
||||
} else {
|
||||
localStorage.removeItem(localStorageCurrentWorkspaceKey)
|
||||
this.setCurrentId(null)
|
||||
}
|
||||
},
|
||||
setCurrentId(id) {
|
||||
this.currentId = id
|
||||
localStorage.setItem(localStorageCurrentWorkspaceKey, id)
|
||||
storedWorkspaceId.value = id
|
||||
},
|
||||
addOrUpdate(item) {
|
||||
this.content = this.content.filter((val) => val.id !== item.id)
|
||||
this.content.push(item)
|
||||
if (this.currentId == null) {
|
||||
this.currentId = item.id
|
||||
localStorage.setItem(localStorageCurrentWorkspaceKey, this.currentId)
|
||||
storedWorkspaceId.value = this.currentId
|
||||
}
|
||||
},
|
||||
remove(itemId) {
|
||||
this.content = this.content.filter((val) => val.id !== itemId)
|
||||
if (this.currentId === itemId) {
|
||||
this.currentId = this.content.length > 0 ? this.content[0].id : null
|
||||
localStorage.setItem(localStorageCurrentWorkspaceKey, this.currentId)
|
||||
this.setCurrentId(this.content.length > 0 ? this.content[0].id : null)
|
||||
}
|
||||
},
|
||||
startLoading() {
|
||||
|
@ -73,7 +71,7 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
|||
load() {
|
||||
this.set([])
|
||||
this.startLoading()
|
||||
return axios.get(workspaceEndpoint).then((response) => {
|
||||
return useOpnFetch(workspaceEndpoint).then((response) => {
|
||||
this.set(response.data)
|
||||
this.stopLoading()
|
||||
})
|
||||
|
@ -86,7 +84,7 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
|||
},
|
||||
delete(id) {
|
||||
this.startLoading()
|
||||
return axios.delete(workspaceEndpoint + id).then((response) => {
|
||||
return useOpnFetch(workspaceEndpoint + id, {method: 'DELETE'}).then((response) => {
|
||||
this.remove(response.data.workspace_id)
|
||||
this.stopLoading()
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue