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:
Julien Nahum
2024-01-15 12:14:47 +01:00
committed by GitHub
parent c01f566ba9
commit 0adce5a2ff
478 changed files with 27676 additions and 34120 deletions

78
client/stores/app.js vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)
}