mirror of
https://github.com/vernu/textbee.git
synced 2026-02-20 07:34:00 -05:00
Merge pull request #147 from vernu/survey-modal
show survey modal in dashboard
This commit is contained in:
@@ -6,6 +6,7 @@ import { usePathname } from 'next/navigation'
|
||||
import AccountDeletionAlert from './(components)/account-deletion-alert'
|
||||
import UpgradeToProAlert from './(components)/upgrade-to-pro-alert'
|
||||
import VerifyEmailAlert from './(components)/verify-email-alert'
|
||||
import { SurveyModal } from '@/components/shared/survey-modal'
|
||||
|
||||
export default function DashboardLayout({
|
||||
children,
|
||||
@@ -88,6 +89,8 @@ export default function DashboardLayout({
|
||||
|
||||
{/* Bottom padding for mobile to account for the fixed navigation */}
|
||||
<div className='h-16 md:hidden'></div>
|
||||
|
||||
<SurveyModal />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
189
web/components/shared/survey-modal.tsx
Normal file
189
web/components/shared/survey-modal.tsx
Normal file
@@ -0,0 +1,189 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from '@/components/ui/dialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import httpBrowserClient from '@/lib/httpBrowserClient'
|
||||
import { ApiEndpoints } from '@/config/api'
|
||||
|
||||
const STORAGE_KEYS = {
|
||||
LAST_SHOWN: 'survey_modal_last_shown',
|
||||
HAS_SUBMITTED: 'survey_modal_has_submitted',
|
||||
}
|
||||
|
||||
const SHOW_INTERVAL = 1 * 60 * 60 * 1000 // 1 hour in milliseconds
|
||||
const RANDOM_CHANCE = 0.5 // 50% chance to show when eligible
|
||||
|
||||
export const SurveyModal = () => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
const {
|
||||
data: currentUser,
|
||||
isLoading: isLoadingUser,
|
||||
error: currentUserError,
|
||||
} = useQuery({
|
||||
queryKey: ['currentUser'],
|
||||
queryFn: () =>
|
||||
httpBrowserClient
|
||||
.get(ApiEndpoints.auth.whoAmI())
|
||||
.then((res) => res.data?.data),
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const checkAndShowModal = () => {
|
||||
// Don't show if user data is still loading or there's an error
|
||||
if (isLoadingUser || currentUserError) {
|
||||
return
|
||||
}
|
||||
|
||||
// Don't show if no user data
|
||||
if (!currentUser) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user has already submitted
|
||||
const hasSubmitted =
|
||||
localStorage.getItem(STORAGE_KEYS.HAS_SUBMITTED) === 'true'
|
||||
if (hasSubmitted) return
|
||||
|
||||
// Check if user account is less than 3 days old
|
||||
if (currentUser?.createdAt) {
|
||||
const createdAt = new Date(currentUser.createdAt)
|
||||
const now = new Date()
|
||||
const daysSinceCreation =
|
||||
(now.getTime() - createdAt.getTime()) / (1000 * 60 * 60 * 24)
|
||||
|
||||
if (daysSinceCreation < 3) {
|
||||
return // Don't show modal for new users
|
||||
}
|
||||
}
|
||||
|
||||
const lastShown = localStorage.getItem(STORAGE_KEYS.LAST_SHOWN)
|
||||
const now = Date.now()
|
||||
const lastShownTime = lastShown ? parseInt(lastShown) : 0
|
||||
|
||||
|
||||
|
||||
if (!lastShown || now - lastShownTime >= SHOW_INTERVAL) {
|
||||
|
||||
if (Math.random() < RANDOM_CHANCE) {
|
||||
|
||||
setIsOpen(true)
|
||||
localStorage.setItem(STORAGE_KEYS.LAST_SHOWN, now.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a small delay to ensure everything is loaded
|
||||
const timer = setTimeout(() => {
|
||||
checkAndShowModal()
|
||||
}, 1000)
|
||||
|
||||
// Also check when tab becomes visible
|
||||
const handleVisibilityChange = () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
setTimeout(checkAndShowModal, 500)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange)
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer)
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange)
|
||||
}
|
||||
}, [currentUser, currentUserError, isLoadingUser])
|
||||
|
||||
const handleSubmitted = () => {
|
||||
localStorage.setItem(STORAGE_KEYS.HAS_SUBMITTED, 'true')
|
||||
setIsOpen(false)
|
||||
}
|
||||
|
||||
const handleRemindLater = () => {
|
||||
setIsOpen(false)
|
||||
}
|
||||
|
||||
// Generate the Google Form URL with prefilled data
|
||||
const getFormUrl = () => {
|
||||
if (!currentUser) return ''
|
||||
|
||||
const baseUrl =
|
||||
'https://docs.google.com/forms/d/e/1FAIpQLSe8Vd6bDvJYxwWFaWHyMYrTrrij0cSquteQiYlvggQLzLJxAw/viewform'
|
||||
const nameParam = encodeURIComponent(currentUser.name || '').replace(
|
||||
/%20/g,
|
||||
'+'
|
||||
)
|
||||
const emailParam = encodeURIComponent(currentUser.email || '').replace(
|
||||
/%20/g,
|
||||
'+'
|
||||
)
|
||||
|
||||
return `${baseUrl}?usp=pp_url&entry.2069418346=${nameParam}&entry.292129827=${emailParam}`
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogContent className='sm:max-w-4xl max-h-[90vh] overflow-hidden flex flex-col'>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Help us improve textbee</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className='flex-1 min-h-0'>
|
||||
<p className='text-muted-foreground mb-4'></p>
|
||||
|
||||
{currentUser && (
|
||||
<div className='h-[500px] w-full'>
|
||||
<iframe
|
||||
src={getFormUrl()}
|
||||
width='100%'
|
||||
height='100%'
|
||||
frameBorder='0'
|
||||
marginHeight={0}
|
||||
marginWidth={0}
|
||||
className='rounded-lg'
|
||||
title='textbee.dev feedback survey'
|
||||
>
|
||||
Loading...
|
||||
</iframe>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter className='sticky bottom-0 bg-background pt-4 border-t mt-4 flex justify-between'>
|
||||
<div className='flex space-x-2'>
|
||||
<Button variant='outline' size='sm' onClick={handleRemindLater}>
|
||||
Remind Me Later
|
||||
</Button>
|
||||
<Button variant='outline' size='sm' onClick={handleSubmitted}>
|
||||
Already Submitted
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
size='sm'
|
||||
onClick={() => {
|
||||
const iframe = document.querySelector(
|
||||
'iframe[title="textbee.dev feedback survey"]'
|
||||
) as HTMLIFrameElement
|
||||
if (iframe) {
|
||||
iframe.focus()
|
||||
alert(
|
||||
'Please scroll down in the form and click the Submit button to complete the survey.'
|
||||
)
|
||||
}
|
||||
}}
|
||||
className='bg-primary text-primary-foreground hover:bg-primary/90'
|
||||
>
|
||||
Complete Survey
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user