Migrate to nuxt settings page AND remove axios (#266)

* Settings pages migration

* remove axios and use opnFetch

* Make created form reactive (#267)

* Remove verify pages and axios lib

---------

Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
formsdev
2024-01-02 17:39:41 +05:30
committed by GitHub
parent 6fd2985ff5
commit 178424a184
27 changed files with 622 additions and 888 deletions

View File

@@ -9,49 +9,34 @@
</p>
<!-- Submit Button -->
<v-button :loading="loading" class="mt-4" color="red" @click="alertConfirm('Do you really want to delete your account?',deleteAccount)">
<v-button :loading="loading" class="mt-4" color="red" @click="useAlert().confirm('Do you really want to delete your account?',deleteAccount)">
Delete account
</v-button>
</div>
</template>
<script>
import axios from 'axios'
<script setup>
import { useRouter } from 'vue-router';
export default {
scrollToTop: false,
const router = useRouter()
const authStore = useAuthStore()
const metaTitle = 'Account'
let loading = false
setup () {
const authStore = useAuthStore()
return {
authStore
}
},
const deleteAccount = () => {
loading = true
opnFetch('/user', {method:'DELETE'}).then(async (data) => {
loading = false
useAlert().success(data.message)
// Log out the user.
await authStore.logout()
data: () => ({
metaTitle: 'Account',
form: useForm({
identifier: ''
}),
loading: false
}),
methods: {
async deleteAccount () {
this.loading = true
axios.delete('/api/user').then(async (response) => {
this.loading = false
useAlert().success(response.data.message)
// Log out the user.
await this.authStore.logout()
// Redirect to login.
this.$router.push({ name: 'login' })
}).catch((error) => {
useAlert().error(error.response.data.message)
this.loading = false
})
}
}
// Redirect to login.
router.push({ name: 'login' })
}).catch((error) => {
useAlert().error(error.response.data.message)
loading = false
})
}
</script>

View File

@@ -34,54 +34,40 @@
</div>
</template>
<script>
import axios from 'axios'
<script setup>
import { useRouter } from 'vue-router';
export default {
components: { },
middleware: 'admin',
scrollToTop: false,
definePageMeta({
middleware: "admin"
})
setup () {
const authStore = useAuthStore()
const workspacesStore = useWorkspacesStore()
return {
authStore,
workspacesStore
}
},
const metaTitle = 'Admin'
const authStore = useAuthStore()
const workspacesStore = useWorkspacesStore()
const router = useRouter()
let form = useForm({
identifier: ''
})
let loading = false
data: () => ({
metaTitle: 'Admin',
form: useForm({
identifier: ''
}),
loading: false
}),
const impersonate = () => {
loading = true
authStore.startImpersonating()
opnFetch('/admin/impersonate/' + encodeURI(form.identifier)).then(async (data) => {
loading = false
methods: {
async impersonate () {
this.loading = true
this.authStore.startImpersonating()
axios.get('/api/admin/impersonate/' + encodeURI(this.form.identifier)).then(async (response) => {
this.loading = false
// Save the token.
authStore.saveToken(data.token, false)
// Save the token.
this.authStore.saveToken(response.data.token, false)
// Fetch the user.
await authStore.fetchUser()
// Fetch the user.
await this.authStore.fetchUser()
// Redirect to the dashboard.
this.workspacesStore.set([])
this.$router.push({ name: 'home' })
}).catch((error) => {
useAlert().error(error.response.data.message)
this.loading = false
})
// this.form.reset()
}
}
// Redirect to the dashboard.
workspacesStore.set([])
router.push({ name: 'home' })
}).catch((error) => {
useAlert().error(error.response.data.message)
loading = false
})
}
</script>

View File

@@ -19,45 +19,25 @@
</div>
</template>
<script>
import axios from 'axios'
<script setup>
import { computed } from 'vue'
import { useAuthStore } from '../../stores/auth'
import VButton from '~/components/global/VButton.vue'
import SeoMeta from '../../mixins/seo-meta.js'
import AppSumoBilling from '../../components/vendor/appsumo/AppSumoBilling.vue'
export default {
components: { AppSumoBilling, VButton },
mixins: [SeoMeta],
scrollToTop: false,
const metaTitle = 'Billing'
const authStore = useAuthStore()
let user = computed(() => authStore.user)
let billingLoading = false
setup () {
const authStore = useAuthStore()
return {
user : computed(() => authStore.user)
}
},
data: () => ({
metaTitle: 'Billing',
billingLoading: false
}),
methods: {
openBillingDashboard () {
this.billingLoading = true
axios.get('/api/subscription/billing-portal').then((response) => {
const url = response.data.portal_url
window.location = url
}).catch((error) => {
useAlert().error(error.response.data.message)
}).finally(() => {
this.billingLoading = false
})
}
},
computed: {}
const openBillingDashboard = () => {
billingLoading = true
opnFetch('/subscription/billing-portal').then((data) => {
const url = data.portal_url
window.location = url
}).catch((error) => {
useAlert().error(error.response.data.message)
}).finally(() => {
billingLoading = false
})
}
</script>

View File

@@ -1,105 +1,5 @@
<template>
<div class="bg-white">
<div class="flex bg-gray-50">
<div class="w-full md:w-4/5 lg:w-3/5 md:mx-auto md:max-w-4xl px-4">
<div class="pt-4 pb-0">
<div class="flex">
<h2 class="flex-grow text-gray-900">
My Account
</h2>
</div>
<ul class="flex text-gray-500">
<li>{{ user.email }}</li>
</ul>
<div class="mt-4 border-b border-gray-200 dark:border-gray-700">
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center">
<li v-for="(tab, i) in tabsList" :key="i+1" class="mr-6">
<NuxtLink :to="{ name: tab.route }"
class="hover:no-underline inline-block py-4 rounded-t-lg border-b-2 text-gray-500 hover:text-gray-600"
active-class="text-blue-600 hover:text-blue-900 dark:text-blue-500 dark:hover:text-blue-500 border-blue-600 dark:border-blue-500"
>
{{ tab.name }}
</NuxtLink>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="flex bg-white">
<div class="w-full md:w-4/5 lg:w-3/5 md:mx-auto md:max-w-4xl px-4">
<div class="mt-8 pb-0">
<router-view v-slot="{ Component }">
<transition name="page" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</div>
</div>
</div>
</div>
</template>
<script>
import { computed } from 'vue'
import { useAuthStore } from '../../stores/auth'
export default {
middleware: 'auth',
setup () {
const authStore = useAuthStore()
return {
user: computed(() => authStore.user)
}
},
data () {
return {
}
},
computed: {
tabsList () {
const tabs = [
{
name: 'Profile',
route: 'settings-profile'
},
{
name: 'Workspace Settings',
route: 'settings-workspace'
},
{
name: 'Password',
route: 'settings-password'
},
{
name: 'Delete Account',
route: 'settings-account'
}
]
if (this.user.is_subscribed) {
tabs.splice(1, 0, {
name: 'Billing',
route: 'settings-billing'
})
}
if (this.user.admin) {
tabs.push({
name: 'Admin',
route: 'settings-admin'
})
}
return tabs
}
},
methods: {
}
}
<script setup>
useRouter().push({
name: 'settings-profile'
})
</script>

View File

@@ -6,8 +6,6 @@
<small class="text-gray-600">Manage your password.</small>
<form class="mt-3" @submit.prevent="update" @keydown="form.onKeydown($event)">
<alert-success class="mb-5" :form="form" message="Password updated." />
<!-- Password -->
<text-input native-type="password"
name="password" :form="form" label="Password" :required="true"
@@ -26,27 +24,17 @@
</div>
</template>
<script>
import SeoMeta from '../../mixins/seo-meta.js'
<script setup>
const metaTitle = 'Password'
let form = useForm({
password: '',
password_confirmation: ''
})
export default {
mixins: [SeoMeta],
scrollToTop: false,
data: () => ({
metaTitle: 'Password',
form: useForm({
password: '',
password_confirmation: ''
})
}),
methods: {
async update () {
await this.form.patch('/api/settings/password')
this.form.reset()
}
}
const update = () => {
form.patch('/settings/password').then((response) => {
form.reset()
useAlert().success('Password updated.')
})
}
</script>

View File

@@ -6,8 +6,6 @@
<small class="text-gray-600">Update your username and manage your account details.</small>
<form class="mt-3" @submit.prevent="update" @keydown="form.onKeydown($event)">
<alert-success class="mb-5" :form="form" message="Your info has been updated!" />
<!-- Name -->
<text-input name="name" :form="form" label="Name" :required="true" />
@@ -22,39 +20,26 @@
</div>
</template>
<script>
export default {
scrollToTop: false,
<script setup>
const authStore = useAuthStore()
const user = computed(() => authStore.user)
const metaTitle = 'Profile'
let form = useForm({
name: '',
email: ''
})
setup () {
const authStore = useAuthStore()
return {
authStore,
user : computed(() => authStore.user)
}
},
data: () => ({
metaTitle: 'Profile',
form: useForm({
name: '',
email: ''
})
}),
created () {
// Fill the form with user data.
this.form.keys().forEach(key => {
this.form[key] = this.user[key]
})
},
methods: {
async update () {
const { data } = await this.form.patch('/api/settings/profile')
this.authStore.updateUser(data)
}
}
const update = () => {
form.patch('/settings/profile').then((response) => {
authStore.updateUser(response)
useAlert().success('Your info has been updated!')
})
}
onBeforeMount(() => {
// Fill the form with user data.
form.keys().forEach(key => {
form[key] = user.value[key]
})
})
</script>

View File

@@ -46,7 +46,7 @@
/>
<p class="text-gray-500 text-sm">
Read our <a href="#"
@click.prevent="$crisp.push(['do', 'helpdesk:article:open', ['en', 'how-to-use-my-own-domain-9m77g7']])"
@click.prevent="crisp.openHelpdeskArticle('how-to-use-my-own-domain-9m77g7')"
>custom
domain instructions</a> to learn how to use your own domain.
</p>
@@ -65,7 +65,7 @@
Save Domains
</v-button>
<v-button v-if="workspaces.length > 1" color="white" class="group w-full sm:w-auto" :loading="loading"
@click="deleteWorkspace(workspace)"
@click="deleteWorkspace(workspace.id)"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 -mt-1 inline group-hover:text-red-700" fill="none"
viewBox="0 0 24 24" stroke="currentColor"
@@ -114,103 +114,88 @@
</div>
</template>
<script>
import TextAreaInput from '../../components/forms/TextAreaInput.vue'
import axios from 'axios'
<script setup>
import {watch} from "vue";
import opnformConfig from "~/opnform.config.js";
import {fetchAllWorkspaces} from "~/stores/workspaces.js";
export default {
components: { TextAreaInput },
scrollToTop: false,
const crisp = useCrisp()
const workspacesStore = useWorkspacesStore()
const workspaces = computed(() => workspacesStore.getAll)
let loading = computed(() => workspacesStore.loading)
const metaTitle = 'Workspaces'
let form = useForm({
name: '',
emoji: ''
})
let workspaceModal = ref(false)
let customDomains = ''
let customDomainsLoading = ref(false)
setup () {
const formsStore = useFormsStore()
const workspacesStore = useWorkspacesStore()
return {
formsStore,
workspacesStore,
workspaces: computed(() => workspacesStore.content),
loading: computed(() => workspacesStore.loading)
}
},
let workspace = computed(() => workspacesStore.getCurrent)
let customDomainsEnabled = computed(() => opnformConfig.custom_domains_enabled)
data: () => ({
metaTitle: 'Workspaces',
form: useForm({
name: '',
emoji: ''
}),
workspaceModal: false,
customDomains: '',
customDomainsLoading: false
}),
watch(() => workspace, () => {
initCustomDomains()
})
mounted () {
this.workspacesStore.loadIfEmpty()
this.initCustomDomains()
},
onMounted(() => {
fetchAllWorkspaces()
initCustomDomains()
})
computed: {
workspace () {
return this.workspacesStore.getCurrent()
},
customDomainsEnabled () {
return this.$config.custom_domains_enabled
}
},
methods: {
saveChanges () {
if (this.customDomainsLoading) return
this.customDomainsLoading = true
// Update the workspace custom domain
axios.put('/api/open/workspaces/' + this.workspace.id + '/custom-domains', {
custom_domains: this.customDomains.split('\n')
.map(domain => domain.trim())
.filter(domain => domain && domain.length > 0)
}).then((response) => {
this.workspacesStore.addOrUpdate(response.data)
useAlert().success('Custom domains saved.')
}).catch((error) => {
useAlert().error('Failed to update custom domains: ' + error.response.data.message)
}).finally(() => {
this.customDomainsLoading = false
})
},
initCustomDomains () {
if (!this.workspace) return
this.customDomains = this.workspace.custom_domains.join('\n')
},
deleteWorkspace (workspace) {
if (this.workspaces.length <= 1) {
useAlert().error('You cannot delete your only workspace.')
return
}
useAlert().confirm('Do you really want to delete this workspace? All forms created in this workspace will be removed.', () => {
this.workspacesStore.delete(workspace.id).then(() => {
useAlert().success('Workspace successfully removed.')
})
})
},
isUrl (str) {
const pattern = new RegExp('^(https?:\\/\\/)?' + // protocol
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
'((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
'(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
'(\\#[-a-z\\d_]*)?$', 'i') // fragment locator
return !!pattern.test(str)
},
async createWorkspace() {
const {data} = await this.form.post('/api/open/workspaces/create')
this.workspacesStore.load()
this.workspaceModal = false
}
},
watch: {
workspace () {
this.initCustomDomains()
}
}
const saveChanges = () => {
if (customDomainsLoading.value) return
customDomainsLoading.value = true
// Update the workspace custom domain
opnFetch('/open/workspaces/' + workspace.value.id + '/custom-domains', {
method:'PUT',
custom_domains: customDomains.split('\n')
.map(domain => domain.trim())
.filter(domain => domain && domain.length > 0)
}).then((data) => {
workspacesStore.addOrUpdate(data)
useAlert().success('Custom domains saved.')
}).catch((error) => {
useAlert().error('Failed to update custom domains: ' + error.response.data.message)
}).finally(() => {
customDomainsLoading.value = false
})
}
const initCustomDomains = () => {
if (!workspace || !workspace.value.custom_domains) return
customDomains = workspace.value.custom_domains.join('\n')
}
const deleteWorkspace = (workspaceId) => {
if (workspaces.length <= 1) {
useAlert().error('You cannot delete your only workspace.')
return
}
useAlert().confirm('Do you really want to delete this workspace? All forms created in this workspace will be removed.', () => {
opnFetch('/open/workspaces/' + workspaceId, {method:'DELETE'}).then((data) => {
useAlert().success('Workspace successfully removed.')
workspacesStore.remove(workspaceId)
})
})
}
const isUrl = (str) => {
const pattern = new RegExp('^(https?:\\/\\/)?' + // protocol
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
'((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
'(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
'(\\#[-a-z\\d_]*)?$', 'i') // fragment locator
return !!pattern.test(str)
}
const createWorkspace = () => {
form.post('/open/workspaces/create').then((response) => {
fetchAllWorkspaces()
workspaceModal.value = false
useAlert().success('Workspace successfully created.')
})
}
</script>