Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

apply first submission modal changes #584

Merged
merged 9 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/app/Http/Controllers/Forms/PublicFormController.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public function showAsset($assetFileName)
public function answer(AnswerFormRequest $request)
{
$form = $request->form;
$isFirstSubmission = ($form->submissions_count === 0);
$submissionId = false;

$submissionData = $request->validated();
Expand All @@ -104,6 +105,7 @@ public function answer(AnswerFormRequest $request)
return $this->success(array_merge([
'message' => 'Form submission saved.',
'submission_id' => $submissionId,
'is_first_submission' => $isFirstSubmission
], $request->form->is_pro && $request->form->redirect_url ? [
'redirect' => true,
'redirect_url' => $request->form->redirect_url,
Expand Down
21 changes: 18 additions & 3 deletions client/components/open/forms/OpenCompleteForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@
</p>
</div>
</v-transition>
<FirstSubmissionModal
:show="showFirstSubmissionModal"
:form="form"
@close="showFirstSubmissionModal=false"
/>
</div>
</template>

Expand All @@ -191,9 +196,10 @@ import VTransition from '~/components/global/transitions/VTransition.vue'
import {pendingSubmission} from "~/composables/forms/pendingSubmission.js"
import clonedeep from "clone-deep"
import ThemeBuilder from "~/lib/forms/themes/ThemeBuilder.js"
import FirstSubmissionModal from '~/components/open/forms/components/FirstSubmissionModal.vue'

export default {
components: { VTransition, OpenFormButton, OpenForm, FormCleanings, FormTimer },
components: { VTransition, OpenFormButton, OpenForm, FormCleanings, FormTimer, FirstSubmissionModal },

props: {
form: { type: Object, required: true },
Expand All @@ -207,7 +213,10 @@ export default {
},

setup(props) {
const authStore = useAuthStore()
return {
authStore,
authenticated: computed(() => authStore.check),
isIframe: useIsIframe(),
pendingSubmission: pendingSubmission(props.form),
confetti: useConfetti()
Expand All @@ -222,7 +231,8 @@ export default {
password: null
}),
hidePasswordDisabledMsg: false,
submissionId: false
submissionId: false,
showFirstSubmissionModal: false
}
},

Expand All @@ -246,6 +256,9 @@ export default {
if(!this.form || !this.form.font_family) return null
const family = this.form?.font_family.replace(/ /g, '+')
return `https://fonts.googleapis.com/css?family=${family}:wght@400,500,700,800,900&display=swap`
},
isFormOwner() {
return this.authenticated && this.form && this.form.creator_id === this.authStore.user.id
}
},

Expand Down Expand Up @@ -291,7 +304,9 @@ export default {
if (data.submission_id) {
this.submissionId = data.submission_id
}

if (this.isFormOwner && !this.isIframe && data?.is_first_submission) {
this.showFirstSubmissionModal = true
}
this.loading = false
this.submitted = true
this.$emit('submitted', true)
Expand Down
45 changes: 25 additions & 20 deletions client/components/open/forms/components/FirstSubmissionModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,29 @@
</template>
<div class="">
<div class="text-sm text-gray-500">
Congratulations on creating your form and receiving your first submission! Your form is now live and ready for action. You can now <span class="font-semibold">share your form</span> with others, or <span class="font-semibold">open your Notion database</span> to view the submitted data.
Congratulations on creating your form and receiving your first submission! Your form is now live and ready for action. You can now <span class="font-semibold">share your form</span> with others, or <span class="font-semibold">open your Form submission page</span> to view the submitted data.
</div>

<div class="flex gap-2 items-center max-w-full">
<p class="text-sm w-48 text-gray-500">
Share form URL:
</p>
<ShareFormUrl
class="flex-grow my-4"
:form="form"
/>
</div>
<div class="flex py-2 items-center max-w-full">
<p class="text-sm w-48 text-gray-500">
Check your submissions:
</p>
<UButton
v-track.form_first_submission_modal_open_db_click
color="white"
icon="i-logos-notion-icon"
:to="form.notion_database_url"
icon="i-heroicons-document"
target="_blank"
@click="trackOpenDbClick"
>
See Notion database
See Submissions
</UButton>
</div>

Expand All @@ -39,19 +46,19 @@
v-for="(item, i) in helpLinks"
:key="i"
role="button"
class="bg-white shadow border border-gray-200 rounded-lg p-4 pb-2 items-center justify-center flex flex-col relative hover:bg-gray-50 group transition-colors"
class="bg-white shadow border border-gray-200 rounded-lg p-4 pb-2 items-center justify-center flex flex-col relative hover:bg-gray-50 dark:hover:bg-gray-800 group transition-colors"
@click="item.action"
>
<div class="flex justify-center">
<div class="h-8 w-8 text-gray-600 group-hover:text-gray-800 transition-colors flex items-center">
<div class="h-8 w-8 text-gray-600 group-hover:text-gray-800 dark:group-hover:text-white transition-colors flex items-center">
<Icon
:name="item.icon"
class=""
size="40px"
/>
</div>
</div>

<p class="text-gray-500 font-medium text-xs text-center my-2">
{{ item.label }}
</p>
Expand All @@ -62,35 +69,27 @@
</template>

<script setup>
import ShareFormUrl from '~/components/notion/forms/components/ShareFormUrl.vue'

import ShareFormUrl from '~/components/open/forms/components/ShareFormUrl.vue'
const props = defineProps({
show: { type: Boolean, required: true },
form: { type: Object, required: true }
})

const emit = defineEmits(['close'])
const confetti = useConfetti()
const crisp = useCrisp()

const amplitude = useAmplitude()
watch(() => props.show, () => {
if (props.show) {
confetti.play()
useAmplitude().logEvent('form_first_submission_modal_viewed')
}
})

const helpLinks = computed(() => {
return [
{
'label': 'Embed form on your website',
'icon': 'heroicons:code-bracket',
'action': () => crisp.openHelpdeskArticle('how-to-embed-your-form-on-your-website-yqa6i')
},
{
'label': 'Embed form in Notion',
'icon': 'ri:notion-fill',
'action': () => crisp.openHelpdeskArticle('can-i-embed-my-form-in-a-notion-page-or-site-11iroj9')
'action': () => crisp.openHelpdeskArticle('https://help.opnform.com/en/article/can-i-embed-my-form-in-a-notion-page-or-site-x7guph/')
},
{
'label': 'Help Center',
Expand All @@ -105,4 +104,10 @@ const helpLinks = computed(() => {
]
})

</script>
const trackOpenDbClick = () => {
const submissionsUrl = `/forms/${props.form.slug}/submissions`
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you tested this...? Not working not the right URL.

To fix:

  • Create a new prop on Model form submission_url (like we did here
    public function getShareUrlAttribute()
    {
    if ($this->custom_domain) {
    return 'https://' . $this->custom_domain . '/forms/' . $this->slug;
    }
    return front_url('/forms/' . $this->slug);
    }
    )
  • If user is logged in and form owner add this prop to FormResource (like we did here
    'redirect_url' => $this->redirect_url,
    'database_fields_update' => $this->database_fields_update,
    )
  • use this to redirect

window.open(submissionsUrl, '_blank')
amplitude.logEvent('form_first_submission_modal_open_db_click')
}

</script>
69 changes: 69 additions & 0 deletions client/components/open/forms/components/ShareFormUrl.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<template>
<div
class="border border-nt-blue-light bg-blue-50 dark:bg-notion-dark-light rounded-md p-2 overflow-hidden"
>
<div class="flex items-center w-full gap-2">
<p class="select-all text-nt-blue flex-grow truncate overflow-hidden">
<a
v-if="link"
:href="share_url"
target="_blank"
>
{{ share_url }}
</a>
<span v-else>
{{ share_url }}
</span>
</p>
<UButton
class="shrink-0"
size="sm"
icon="i-heroicons-clipboard-document"
label="Copy"
@click="copyToClipboard"
/>
</div>
</div>
</template>

<script setup>
import { computed, defineProps } from 'vue'

const props = defineProps({
form: {
type: Object,
required: true,
},
link: {
type: Boolean,
default: false,
},
extraQueryParam: {
type: String,
default: '',
},
})

const { copy } = useClipboard()

const share_url = computed(() => {
return props.extraQueryParam
? `${props.form.share_url}?${props.extraQueryParam}`
: props.form.share_url + props.extraQueryParam
})
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix the logic in the share_url computed property.

The current implementation of the share_url computed property has a logical error. When extraQueryParam is falsy, it's still being concatenated to the share_url, which could lead to unexpected results.

Please update the computed property as follows:

 const share_url = computed(() => {
-  return props.extraQueryParam
-    ? `${props.form.share_url}?${props.extraQueryParam}`
-    : props.form.share_url + props.extraQueryParam
+  if (props.extraQueryParam) {
+    return `${props.form.share_url}?${props.extraQueryParam}`
+  }
+  return props.form.share_url
 })

This change ensures that the extraQueryParam is only added when it has a value.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const share_url = computed(() => {
return props.extraQueryParam
? `${props.form.share_url}?${props.extraQueryParam}`
: props.form.share_url + props.extraQueryParam
})
const share_url = computed(() => {
if (props.extraQueryParam) {
return `${props.form.share_url}?${props.extraQueryParam}`
}
return props.form.share_url
})

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great suggestion

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JhumanJ, glad you found the suggestion helpful!

(_/)
( ^.^ )
(")_(")


function copyToClipboard() {
if (import.meta.server)
return
copy(share_url.value)
if (props.form.visibility == 'draft') {
useAlert().warning(
'Copied! But other people won\'t be able to see the form since it\'s currently in draft mode',
)
}
else {
useAlert().success('Copied!')
}
}
Comment on lines +56 to +68
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider implementing async/await and error handling in copyToClipboard.

While the function logic is correct, it could be improved by implementing async/await and adding error handling as suggested in the past review comments.

Consider updating the function as follows:

async function copyToClipboard() {
  if (import.meta.server)
    return
  try {
    await copy(share_url.value)
    if (props.form.visibility === 'draft') {
      useAlert().warning(
        'Copied! But other people won\'t be able to see the form since it\'s currently in draft mode',
      )
    } else {
      useAlert().success('Copied!')
    }
  } catch (error) {
    useAlert().error('Failed to copy URL')
  }
}

This change adds error handling and ensures the alert is shown only after the copy operation is complete.

</script>

Loading