Migrate front-end to Nuxt app (#284)
* wip * Managed to load a page * Stuck at changing routes * Fixed the router, and editable div * WIP * Fix app loader * WIP * Fix check-auth middleware * Started to refactor input components * WIP * Added select input, v-click-outside for vselect * update vselect & phone input * Fixed the mixin * input component updates * Fix signature input import * input component updates in vue3 * image input in vue3 * small fixes * fix useFormInput watcher * scale input in vue3 * Vue3: migrating from vuex to Pinia (#249) * Vue3: migrating from vuex to Pinia * toggle input fixes * update configureCompat --------- Co-authored-by: Forms Dev <chirag+new@notionforms.io> * support vue3 query builder * Refactor inpus * fix: Vue3 Query Builder - Logic Editor (#251) * support vue3 query builder * upgrade * remove local from middleware * Submission table pagination & migrate chart to vue3 (#254) * Submission table Pagination in background * migrate chart to vue3 * Form submissions pagination * Form submissions * Fix form starts * Fix openSelect key issue --------- Co-authored-by: Forms Dev <chirag+new@notionforms.io> Co-authored-by: Julien Nahum <julien@nahum.net> * Vue 3 better animation (#257) * vue-3-better-animation * Working on migration to vueuse/motion * Form sidebar animations * Clean code * Added animations for modal * Finished implementing better animations --------- Co-authored-by: Forms Dev <chirag+new@notionforms.io> * Work in progress * Migrating amplitude and crisp plugin/composable * Started to refactor pages * WIP * vue3-scroll-shadow-fixes (#260) * WIP * WIP * WIP * Figured out auth & middlewares * WI * Refactoring stores and templates pages to comp. api * Finishing the templates pages * fix collapsible * Finish reworking most templates pages * Reworked workspaces store * Working on home page and modal * Fix dropdown * Fix modal * Fixed form creation * Fixed most of the form/show pages * Updated cors dependency * fix custom domain warning * NuxtLink migration (#262) Co-authored-by: Forms Dev <chirag+new@notionforms.io> * Tiny fixes + start pre-rendering * migrate-to-nuxt-useappconfig (#263) * migrate-to-nuxt-useappconfig * defineAppConfig --------- Co-authored-by: Forms Dev <chirag+new@notionforms.io> * Working on form/show and editor * Globally import form inputs to fix resolve * Remove vform - working on form public page * Remove initform mixin * Work in progress for form create guess user * Nuxt Migration notifications (#265) * Nuxt Migration notifications * @input to @update:model-value * change field type fixes * @update:model-value * Enable form-block-logic-editor * vue-confetti migration * PR request changes * useAlert in setup * 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> * Fix alert styling + bug fixes and cleaning * Refactor notifications + add shadow * Fix vselect issue * Working on page pre-rendering * Created NotionPages store * Added sitemap on nuxt side * Sitemap done, working on aws amplify * Adding missing module * Remove axios and commit backend changes to sitemap * Fix notifications * fix guestpage editor (#269) Co-authored-by: Julien Nahum <julien@nahum.net> * Remove appconfig in favor of runtimeconfig * Fixed amplitude bugs, and added staging environment * Added amplify file * Change basdirectory amplify * Fix loading bar position * Fix custom redirect (#273) * Dirty form handling - nuxt migration (#272) * SEO meta nuxt migration (#274) * SEO meta nuxt migration * Polish seo metas, add defaults for OG and twitter --------- Co-authored-by: Julien Nahum <julien@nahum.net> * migrate to nuxt useClipboard (#268) * Set middleware on pages (#278) * Se middleware on pages * Se middleware on account page * add robots.txt (#276) * 404 page migration (#277) * Templates pages migration (#275) * NuxtImg Migration (#279) Co-authored-by: Julien Nahum <julien@nahum.net> * Update package json * Fix build script * Add loglevel param * Disable page pre-rendering * Attempt to allow svgs * Fix SVGs with NuxtImage * Add .env file at AWS build time * tRGIGGER deploy * Fix issue * ANother attrempt * Fix typo * Fix env? * Attempt to simplify build * Enable swr caching instead of prerenderign * Better image compression * Last attempt at nuxt images efficiency * Improve image optimization again * Remove NuxtImg for non asset files * Restore templates pages cache * Remove useless images + fix templates show page * image optimization caching + fix hydratation issue form template page * URL generation (front&back) + fixed authJWT for SSR * Fix composable issue * Fix form share page * Embeddable form as a nuxt middleware * Fix URL for embeddable middleware * Debugging embeddable on amplify * Add custom domain support * No follow for non-production env * Fix sentry nuxt and custom domain redirect * remove api prefix from routes (#280) * remove api prefix from routes * PR changes --------- Co-authored-by: Julien Nahum <julien@nahum.net> * nuxt migration -file upload - WIP (#271) Co-authored-by: Julien Nahum <julien@nahum.net> * Fix local file upload * Fix file submissions preview * API redirect to back-end from nuxt * API redirect to back-end from nuxt * Remove old JS app, update deploy script * Fix tests, added gh action nuxt step * Updated package-lock.json * Setup node in GH Nuxt action * Setup client directory for GH workflow --------- Co-authored-by: Forms Dev <chirag+new@notionforms.io> Co-authored-by: Chirag Chhatrala <60499540+chiragchhatrala@users.noreply.github.com> Co-authored-by: Rishi Raj Jain <rishi18304@iiitd.ac.in> Co-authored-by: formsdev <136701234+formsdev@users.noreply.github.com>
This commit is contained in:
78
client/stores/app.js
vendored
Normal file
78
client/stores/app.js
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
|
||||
import { defineStore } from 'pinia'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
export const useAppStore = defineStore('app', {
|
||||
state: () => ({
|
||||
layout: 'default',
|
||||
navbarHidden: false,
|
||||
|
||||
// App Loader
|
||||
loader: {
|
||||
percent: 0,
|
||||
show: false,
|
||||
canSuccess: true,
|
||||
duration: 3000,
|
||||
_timer: null,
|
||||
_cut: null
|
||||
}
|
||||
}),
|
||||
actions: {
|
||||
hideNavbar () {
|
||||
this.navbarHidden = true
|
||||
},
|
||||
showNavbar () {
|
||||
this.navbarHidden = false
|
||||
},
|
||||
setLayout (layout) {
|
||||
this.layout = layout ?? 'default'
|
||||
},
|
||||
loaderIncrease (num) {
|
||||
this.loader.percent = this.loader.percent + Math.floor(num)
|
||||
},
|
||||
loaderDecrease (num) {
|
||||
this.loader.percent = this.loader.percent - Math.floor(num)
|
||||
},
|
||||
loaderFinish () {
|
||||
this.loader.percent = 100
|
||||
this.loaderHide()
|
||||
},
|
||||
loaderSetTimer (timerVal) {
|
||||
this.loader._timer = timerVal
|
||||
},
|
||||
loaderPause () {
|
||||
clearInterval(this.loader._timer)
|
||||
},
|
||||
loaderHide () {
|
||||
this.loaderPause()
|
||||
this.loader._timer = null
|
||||
setTimeout(() => {
|
||||
this.loader.show = false
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.loader.percent = 0
|
||||
}, 200)
|
||||
})
|
||||
}, 500)
|
||||
},
|
||||
loaderFail () {
|
||||
this.loader.canSuccess = false
|
||||
},
|
||||
loaderStart () {
|
||||
this.loader.show = true
|
||||
this.loader.canSuccess = true
|
||||
if (this.loader._timer) {
|
||||
clearInterval(this.loader._timer)
|
||||
this.loader.percent = 0
|
||||
}
|
||||
this.loader._cut = 10000 / Math.floor(this.loader.duration)
|
||||
|
||||
this.loaderSetTimer(setInterval(() => {
|
||||
this.loaderIncrease(this.loader._cut * Math.random())
|
||||
if (this.loader.percent > 95) {
|
||||
this.loaderFinish()
|
||||
}
|
||||
}, 100))
|
||||
}
|
||||
}
|
||||
})
|
||||
94
client/stores/auth.js
vendored
Normal file
94
client/stores/auth.js
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
import {defineStore} from 'pinia'
|
||||
|
||||
export const useAuthStore = defineStore('auth', {
|
||||
state: () => {
|
||||
return {
|
||||
token: null,
|
||||
admin_token: null,
|
||||
user: null,
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
check: (state) => (state.user !== null && state.user !== undefined),
|
||||
isImpersonating: (state) => (state.admin_token !== null && state.admin_token !== undefined)
|
||||
},
|
||||
actions: {
|
||||
// Stores admin token temporarily for impersonation
|
||||
startImpersonating() {
|
||||
this.setAdminToken(this.token)
|
||||
},
|
||||
// Stop admin impersonation
|
||||
stopImpersonating() {
|
||||
this.token = this.admin_token
|
||||
this.admin_token = null
|
||||
// TODO: re-fetch user
|
||||
},
|
||||
|
||||
setToken(token) {
|
||||
this.setCookie('token', token)
|
||||
this.token = token
|
||||
},
|
||||
|
||||
setAdminToken(token) {
|
||||
this.setCookie('admin_token', token)
|
||||
this.admin_token = token
|
||||
},
|
||||
|
||||
setCookie(name, value) {
|
||||
if (process.client) {
|
||||
useCookie(name).value = value
|
||||
}
|
||||
},
|
||||
|
||||
initStore(token, adminToken) {
|
||||
this.token = token
|
||||
this.admin_token = adminToken
|
||||
},
|
||||
|
||||
setUser(user) {
|
||||
if (!user) {
|
||||
console.error('No user, logging out.')
|
||||
this.setToken(null)
|
||||
}
|
||||
|
||||
this.user = user
|
||||
this.initServiceClients()
|
||||
},
|
||||
|
||||
updateUser(payload) {
|
||||
this.user = payload
|
||||
this.initServiceClients()
|
||||
},
|
||||
|
||||
initServiceClients() {
|
||||
if (!this.user) return
|
||||
useAmplitude().setUser(this.user)
|
||||
useCrisp().setUser(this.user)
|
||||
|
||||
// Init sentry
|
||||
// console.log(process)
|
||||
// $sentry.configureScope((scope) => {
|
||||
// scope.setUser({
|
||||
// id: this.user.id,
|
||||
// email: this.user.email,
|
||||
// subscription: this.user?.is_subscribed
|
||||
// })
|
||||
// })
|
||||
},
|
||||
|
||||
async logout() {
|
||||
try {
|
||||
await useOpnApi('logout', {method: 'POST'})
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
this.user = null
|
||||
this.setToken(null)
|
||||
},
|
||||
|
||||
async fetchOauthUrl(provider) {
|
||||
// const {data} = await axios.post(`/api/oauth/${provider}`)
|
||||
// return data.url
|
||||
}
|
||||
}
|
||||
})
|
||||
15
client/stores/errors.js
vendored
Normal file
15
client/stores/errors.js
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useErrorsStore = defineStore('errors', {
|
||||
state: () => ({
|
||||
content: null
|
||||
}),
|
||||
actions: {
|
||||
set (error) {
|
||||
this.content = error
|
||||
},
|
||||
clear () {
|
||||
this.content = null
|
||||
}
|
||||
}
|
||||
})
|
||||
71
client/stores/forms.js
vendored
Normal file
71
client/stores/forms.js
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
import {defineStore} from 'pinia'
|
||||
import {useContentStore} from "~/composables/stores/useContentStore.js";
|
||||
|
||||
export const formsEndpoint = '/open/workspaces/{workspaceId}/forms'
|
||||
|
||||
export const useFormsStore = defineStore('forms', () => {
|
||||
|
||||
const contentStore = useContentStore('slug')
|
||||
const allLoaded = ref(false)
|
||||
const currentPage = ref(1)
|
||||
|
||||
const loadAll = (workspaceId) => {
|
||||
contentStore.startLoading()
|
||||
return opnFetch(formsEndpoint.replace('{workspaceId}', workspaceId),{query: {page: currentPage.value}})
|
||||
.then((response) => {
|
||||
if (currentPage.value === 1) {
|
||||
contentStore.resetState()
|
||||
contentStore.save(response.data)
|
||||
} else {
|
||||
contentStore.save(response.data)
|
||||
}
|
||||
if (currentPage.value < response.meta.last_page) {
|
||||
currentPage.value++
|
||||
loadAll(workspaceId)
|
||||
} else {
|
||||
allLoaded.value = true
|
||||
contentStore.stopLoading()
|
||||
currentPage.value = 1
|
||||
}
|
||||
}).catch((error) => {
|
||||
contentStore.stopLoading()
|
||||
currentPage.value = 1
|
||||
throw error
|
||||
})
|
||||
}
|
||||
|
||||
const load = (workspaceId, slug) => {
|
||||
contentStore.startLoading()
|
||||
return opnFetch(formsEndpoint.replace('{workspaceId}', workspaceId) + '/' + slug)
|
||||
.finally(() => {
|
||||
contentStore.stopLoading()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a form from the public API
|
||||
*/
|
||||
const publicLoad = (slug) => {
|
||||
contentStore.startLoading()
|
||||
return useOpnApi('/forms/' + slug)
|
||||
}
|
||||
|
||||
const allTags = computed(() => {
|
||||
let tags = []
|
||||
contentStore.getAll.value.forEach((form) => {
|
||||
if (form.tags && form.tags.length) {
|
||||
tags = tags.concat(form.tags.split(','))
|
||||
}
|
||||
})
|
||||
return [...new Set(tags)]
|
||||
})
|
||||
|
||||
return {
|
||||
...contentStore,
|
||||
allLoaded,
|
||||
allTags,
|
||||
publicLoad,
|
||||
loadAll,
|
||||
load,
|
||||
}
|
||||
})
|
||||
27
client/stores/notion_pages.js
vendored
Normal file
27
client/stores/notion_pages.js
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
import {defineStore} from 'pinia'
|
||||
import {useContentStore} from "~/composables/stores/useContentStore.js";
|
||||
import opnformConfig from "~/opnform.config.js";
|
||||
export const useNotionPagesStore = defineStore('notion_pages', () => {
|
||||
|
||||
const contentStore = useContentStore()
|
||||
|
||||
const load = (pageId) => {
|
||||
contentStore.startLoading()
|
||||
|
||||
const apiUrl = opnformConfig.notion.worker
|
||||
return useFetch(`${apiUrl}/page/${pageId}`)
|
||||
.then(({data, error})=> {
|
||||
const val = data.value
|
||||
val['id'] = pageId
|
||||
contentStore.save(val)
|
||||
})
|
||||
.finally(() => {
|
||||
contentStore.stopLoading()
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
...contentStore,
|
||||
load
|
||||
}
|
||||
})
|
||||
49
client/stores/records.js
vendored
Normal file
49
client/stores/records.js
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const namespaced = true
|
||||
|
||||
/**
|
||||
* Loads records from database
|
||||
*/
|
||||
export const useRecordsStore = defineStore('records', {
|
||||
state: () => ({
|
||||
content: [],
|
||||
loading: false
|
||||
}),
|
||||
getters: {
|
||||
getById: (state) => (id) => {
|
||||
if (state.content.length === 0) return null
|
||||
return state.content.find(item => item.submission_id === id)
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
set (items) {
|
||||
this.content = items
|
||||
},
|
||||
addOrUpdate (item) {
|
||||
this.content = this.content.filter((val) => val.id !== item.id)
|
||||
this.content.push(item)
|
||||
},
|
||||
remove (itemId) {
|
||||
this.content = this.content.filter((val) => val.id !== itemId)
|
||||
},
|
||||
startLoading () {
|
||||
this.loading = true
|
||||
},
|
||||
stopLoading () {
|
||||
this.loading = false
|
||||
},
|
||||
resetState () {
|
||||
this.set([])
|
||||
this.stopLoading()
|
||||
},
|
||||
loadRecord (request) {
|
||||
this.set([])
|
||||
this.startLoading()
|
||||
return request.then((data) => {
|
||||
this.addOrUpdate(data)
|
||||
this.stopLoading()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
63
client/stores/templates.js
vendored
Normal file
63
client/stores/templates.js
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
import {defineStore} from 'pinia'
|
||||
import {useContentStore} from "~/composables/stores/useContentStore.js";
|
||||
import templateTypes from "~/data/forms/templates/types.json"
|
||||
import industryTypes from "~/data/forms/templates/industries.json"
|
||||
|
||||
const templatesEndpoint = 'templates'
|
||||
export const useTemplatesStore = defineStore('templates', () => {
|
||||
|
||||
const contentStore = useContentStore('slug')
|
||||
|
||||
const allLoaded = ref(false)
|
||||
const industries = ref(new Map)
|
||||
const types = ref(new Map)
|
||||
|
||||
const getTemplateTypes = (slugs) => {
|
||||
return slugs.map((slug) => {
|
||||
return types.value.get(slug)
|
||||
}).filter((item) => item !== undefined)
|
||||
}
|
||||
const getTemplateIndustries = (slugs) => {
|
||||
return slugs.map((slug) => {
|
||||
return industries.value.get(slug)
|
||||
}).filter((item) => item !== undefined)
|
||||
}
|
||||
|
||||
const initTypesAndIndustries = () => {
|
||||
if (types.value.size === 0) {
|
||||
types.value = new Map(Object.entries(templateTypes))
|
||||
}
|
||||
if (industries.value.size === 0) {
|
||||
industries.value = new Map(Object.entries(industryTypes))
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...contentStore,
|
||||
industries,
|
||||
types,
|
||||
allLoaded,
|
||||
getTemplateTypes,
|
||||
getTemplateIndustries,
|
||||
initTypesAndIndustries
|
||||
}
|
||||
})
|
||||
|
||||
export const fetchTemplate = (slug, options = {}) => {
|
||||
return useOpnApi(templatesEndpoint + '/' + slug, options)
|
||||
}
|
||||
|
||||
export const fetchAllTemplates = (options = {}) => {
|
||||
return useOpnApi(templatesEndpoint, options)
|
||||
}
|
||||
|
||||
export const loadAllTemplates = async (store, options={}) => {
|
||||
if (!store.allLoaded) {
|
||||
store.startLoading()
|
||||
store.initTypesAndIndustries()
|
||||
const {data,error} = await fetchAllTemplates(options)
|
||||
store.set(data.value)
|
||||
store.stopLoading()
|
||||
store.allLoaded = true
|
||||
}
|
||||
}
|
||||
50
client/stores/working_form.js
vendored
Normal file
50
client/stores/working_form.js
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
|
||||
|
||||
export const useWorkingFormStore = defineStore('working_form', {
|
||||
state: () => ({
|
||||
content: null,
|
||||
|
||||
// Field being edited
|
||||
selectedFieldIndex: null,
|
||||
showEditFieldSidebar: null,
|
||||
showAddFieldSidebar: null
|
||||
}),
|
||||
actions: {
|
||||
set (form) {
|
||||
this.content = form
|
||||
},
|
||||
setProperties (properties) {
|
||||
this.content.properties = properties
|
||||
},
|
||||
openSettingsForField (index) {
|
||||
// If field is passed, compute index
|
||||
if (typeof index === 'object') {
|
||||
index = this.content.properties.findIndex(prop => prop.id === index.id)
|
||||
}
|
||||
this.selectedFieldIndex = index
|
||||
this.showEditFieldSidebar = true
|
||||
this.showAddFieldSidebar = false
|
||||
},
|
||||
closeEditFieldSidebar () {
|
||||
this.selectedFieldIndex = null
|
||||
this.showEditFieldSidebar = false
|
||||
this.showAddFieldSidebar = false
|
||||
},
|
||||
openAddFieldSidebar (index) {
|
||||
// If field is passed, compute index
|
||||
if (index !== null && typeof index === 'object') {
|
||||
index = this.content.properties.findIndex(prop => prop.id === index.id)
|
||||
}
|
||||
this.selectedFieldIndex = index
|
||||
this.showAddFieldSidebar = true
|
||||
this.showEditFieldSidebar = false
|
||||
},
|
||||
closeAddFieldSidebar () {
|
||||
this.selectedFieldIndex = null
|
||||
this.showAddFieldSidebar = false
|
||||
this.showEditFieldSidebar = false
|
||||
}
|
||||
}
|
||||
})
|
||||
55
client/stores/workspaces.js
vendored
Normal file
55
client/stores/workspaces.js
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
import {defineStore} from 'pinia'
|
||||
import {useStorage} from "@vueuse/core"
|
||||
import {useContentStore} from "~/composables/stores/useContentStore.js";
|
||||
|
||||
export const workspaceEndpoint = 'open/workspaces/'
|
||||
|
||||
export const useWorkspacesStore = defineStore('workspaces', () => {
|
||||
|
||||
const storedWorkspaceId = useCookie('currentWorkspace')
|
||||
|
||||
const contentStore = useContentStore()
|
||||
const currentId = ref(storedWorkspaceId)
|
||||
|
||||
const getCurrent = computed(() => {
|
||||
return contentStore.getByKey(currentId.value)
|
||||
})
|
||||
|
||||
const setCurrentId = (id) => {
|
||||
currentId.value = id
|
||||
storedWorkspaceId.value = id
|
||||
}
|
||||
|
||||
const set = (items) => {
|
||||
contentStore.content.value = new Map
|
||||
save(items)
|
||||
}
|
||||
|
||||
const save = (items) => {
|
||||
contentStore.save(items)
|
||||
if ((getCurrent.value == null) && contentStore.length.value) {
|
||||
setCurrentId(items[0].id)
|
||||
}
|
||||
}
|
||||
|
||||
const remove = (itemId) => {
|
||||
contentStore.remove(itemId)
|
||||
if (currentId.value === itemId) {
|
||||
setCurrentId(contentStore.length.value > 0 ? contentStore.getAll.value[0].id : null)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...contentStore,
|
||||
currentId,
|
||||
getCurrent,
|
||||
setCurrentId,
|
||||
set,
|
||||
save,
|
||||
remove
|
||||
}
|
||||
})
|
||||
|
||||
export const fetchAllWorkspaces = (options = {}) => {
|
||||
return useOpnApi(workspaceEndpoint, options)
|
||||
}
|
||||
Reference in New Issue
Block a user