feat: add vercel analysis track

This commit is contained in:
Kai
2025-06-10 11:06:10 -07:00
parent 8a6aaf9239
commit a1ece72c9e
5 changed files with 259 additions and 26 deletions

View File

@@ -0,0 +1,166 @@
import noop from '../../utils/noop'
import { hasSeenEnhancedPromotionAtom } from '@/store'
import { Dialog, Transition } from '@headlessui/react'
import { track } from '@vercel/analytics'
import { useAtom } from 'jotai'
import type React from 'react'
import { Fragment, useEffect, useState } from 'react'
import IconStar from '~icons/heroicons/star-solid'
import IconX from '~icons/tabler/x'
const EnhancedPromotionModal: React.FC = () => {
const [hasSeenPromotion, setHasSeenPromotion] = useAtom(hasSeenEnhancedPromotionAtom)
const [isOpen, setIsOpen] = useState(false)
useEffect(() => {
// Only show modal if user hasn't seen it before
if (!hasSeenPromotion) {
const timer = setTimeout(() => {
setIsOpen(true)
}, 2000) // Show after 2 seconds to let the page load
return () => clearTimeout(timer)
}
}, [hasSeenPromotion])
const handleTryNow = () => {
track('promotion_event', {
from: 'enhanced_promotion_modal',
action: 'open',
})
setHasSeenPromotion(true)
// setIsOpen(false)
// Open in new tab
window.open('https://qwertylearner.ai', '_blank')
}
const handleDismiss = () => {
track('promotion_event', {
from: 'enhanced_promotion_modal',
action: 'close',
})
setHasSeenPromotion(true)
setIsOpen(false)
}
return (
<Transition.Root show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-50" onClose={noop}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-40 backdrop-blur-sm transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="dark:via-gray-850 relative transform overflow-hidden rounded-2xl border border-blue-200 bg-gradient-to-br from-white via-blue-50 to-indigo-100 p-0 text-left shadow-2xl transition-all dark:border-gray-700 dark:from-gray-800 dark:to-gray-900 sm:my-8 sm:w-full sm:max-w-xl">
{/* Header with close button */}
<div className="absolute right-4 top-4 z-10">
<button
type="button"
onClick={handleDismiss}
className="rounded-full p-1 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-gray-700 dark:hover:text-gray-300"
title="关闭"
>
<IconX className="h-5 w-5" />
</button>
</div>
{/* Content */}
<div className="px-8 pb-8 pt-10">
{/* Icon and title */}
<div className="mb-6 text-center">
<div className="mx-auto mb-4 flex h-20 w-20 animate-pulse items-center justify-center rounded-full bg-gradient-to-r from-blue-500 to-purple-600 shadow-lg">
<IconStar className="h-10 w-10 text-white" />
</div>
<h3 className="bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-3xl font-bold text-transparent">
QwertyLearner.ai
</h3>
<p className="mt-2 text-sm text-gray-600 dark:text-gray-400"> </p>
</div>
{/* Main content */}
<div className="space-y-4 py-2 text-sm text-gray-700 dark:text-gray-300">
<p className="text-center font-medium text-gray-900 dark:text-white">
<br />
<div className="my-2"></div>
DeepLearningAI QwertyLearner.ai
</p>
<div className="rounded-lg bg-white p-4 shadow-sm dark:bg-gray-800">
<h4 className="mb-3 font-semibold text-gray-900 dark:text-white">🚀 </h4>
<ul className="space-y-2.5">
<li className="flex items-start">
<span className="mr-2 mt-0.5 text-blue-500"></span>
<span>
<strong>AI </strong> -
</span>
</li>
<li className="flex items-start">
<span className="mr-2 mt-0.5 text-blue-500"></span>
<span>
<strong></strong> -
</span>
</li>
<li className="flex items-start">
<span className="mr-2 mt-0.5 text-blue-500"></span>
<span>
<strong></strong> -
</span>
</li>
<li className="flex items-start">
<span className="mr-2 mt-0.5 text-blue-500"></span>
<span>
<strong></strong> -
</span>
</li>
</ul>
</div>
<div className="rounded-lg bg-amber-50 p-3 text-xs text-amber-800 dark:bg-amber-900/20 dark:text-amber-200">
<p>
<strong></strong>QwertyLearner.ai DeepLearningAI QwertyLearner
</p>
</div>
</div>
{/* Action buttons */}
<div className="mt-8 space-y-3">
<button
type="button"
onClick={handleTryNow}
className="w-full transform rounded-lg bg-gradient-to-r from-blue-500 to-purple-600 px-8 py-4 text-lg font-bold text-white shadow-lg transition-all duration-200 hover:scale-105 hover:from-blue-600 hover:to-purple-700 hover:shadow-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
🚀 QwertyLearner.ai
</button>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
)
}
export default EnhancedPromotionModal

View File

@@ -1,4 +1,5 @@
import InfoPanel from '@/components/InfoPanel'
import { track } from '@vercel/analytics'
import { useCallback, useState } from 'react'
import IconBook2 from '~icons/tabler/book-2'
@@ -7,10 +8,18 @@ export default function DictRequest() {
const onOpenPanel = useCallback(() => {
setShowPanel(true)
track('promotion_event', {
from: 'dict_request_button',
action: 'open',
})
}, [])
const onClosePanel = useCallback(() => {
setShowPanel(false)
track('promotion_event', {
from: 'dict_request_panel',
action: 'close',
})
}, [])
return (
@@ -18,36 +27,91 @@ export default function DictRequest() {
{showPanel && (
<InfoPanel
openState={showPanel}
title="申请词典"
title="寻找更多词典"
icon={IconBook2}
buttonClassName="bg-indigo-500 hover:bg-indigo-400"
iconClassName="text-indigo-500 bg-indigo-100 dark:text-indigo-300 dark:bg-indigo-500"
onClose={onClosePanel}
>
<p className="text-sm text-gray-600 dark:text-gray-300">
<a
href="https://github.com/Kaiyiwing/qwerty-learner/blob/master/docs/toBuildDict.md"
className="px-2 text-blue-500"
target="_blank"
rel="noreferrer"
>
</a>
<br />
<br />
{' '}
<a href="mailto:me@kaiyi.cool" className="px-2 text-blue-500" aria-label="发送邮件到 me@kaiyi.cool">
me@kaiyi.cool
</a>
</p>
<br />
<div className="space-y-5">
<div className="rounded-lg bg-white p-4 shadow-sm dark:bg-gray-800">
<h4 className="mb-3 font-semibold text-gray-900 dark:text-white">👨💻 </h4>
<p className="text-sm text-gray-600 dark:text-gray-300">
<a
href="https://github.com/Kaiyiwing/qwerty-learner/blob/master/docs/toBuildDict.md"
className="mx-1 font-medium text-blue-500 hover:text-blue-600"
target="_blank"
rel="noreferrer"
>
</a>
</p>
</div>
<div className="rounded-lg bg-gradient-to-r from-blue-50 to-indigo-50 p-4 shadow-sm dark:from-gray-800 dark:to-gray-700">
<h4 className="mb-3 font-semibold text-gray-900 dark:text-white">🚀 QwertyLearner.ai</h4>
<p className="mb-3 text-sm text-gray-600 dark:text-gray-300">
<br />
<div className="my-2"></div>
DeepLearningAI
<span className="mx-1 font-semibold text-blue-600 dark:text-blue-400">QwertyLearner.ai</span>
</p>
<div className="space-y-2 text-sm">
<div className="flex items-center">
<span className="mr-2 text-blue-500"></span>
<span className="text-gray-700 dark:text-gray-300">
<strong>AI </strong> -
</span>
</div>
<div className="flex items-center">
<span className="mr-2 text-blue-500"></span>
<span className="text-gray-700 dark:text-gray-300">
<strong></strong> -
</span>
</div>
<div className="flex items-center">
<span className="mr-2 text-blue-500"></span>
<span className="text-gray-700 dark:text-gray-300">
<strong></strong> -
</span>
</div>
<div className="flex items-center">
<span className="mr-2 text-blue-500"></span>
<span className="text-gray-700 dark:text-gray-300">
<strong></strong> -
</span>
</div>
</div>
<button
onClick={() => {
window.open('https://qwertylearner.ai', '_blank')
onClosePanel()
}}
className="mt-4 w-full transform rounded-lg bg-gradient-to-r from-blue-500 to-purple-600 px-4 py-2 text-sm font-semibold text-white transition-all duration-200 hover:scale-105 hover:from-blue-600 hover:to-purple-700 hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
🚀 QwertyLearner.ai
</button>
</div>
<div className="rounded-lg bg-amber-50 p-3 text-xs text-amber-800 dark:bg-amber-900/20 dark:text-amber-200">
<p>
<strong></strong>QwertyLearner.ai DeepLearningAI QwertyLearner
</p>
</div>
</div>
</InfoPanel>
)}
<button className="cursor-pointer pr-6 text-sm text-indigo-500" onClick={onOpenPanel}>
<button
onClick={onOpenPanel}
className="group flex items-center space-x-2 rounded-lg border border-indigo-200 bg-gradient-to-r from-indigo-50 to-blue-50 px-4 py-2.5 text-sm font-medium text-indigo-600 shadow-sm transition-all duration-200 hover:scale-105 hover:border-indigo-300 hover:from-indigo-100 hover:to-blue-100 hover:shadow-md focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:border-indigo-400 dark:from-gray-800 dark:to-gray-700 dark:text-indigo-400 dark:hover:from-gray-700 dark:hover:to-gray-600"
>
<IconBook2 className="h-4 w-4" />
<span></span>
<span className="transform transition-transform group-hover:translate-x-1"></span>
</button>
</>
)

View File

@@ -67,7 +67,7 @@ export default function GalleryPage() {
<IconX className="absolute right-20 top-10 mr-2 h-7 w-7 cursor-pointer text-gray-400" onClick={onBack} />
<div className="mt-20 flex w-full flex-1 flex-col items-center justify-center overflow-y-auto">
<div className="flex h-full flex-col overflow-y-auto">
<div className="flex h-20 w-full items-center justify-between pb-6">
<div className="flex h-20 w-full items-center justify-between pb-6 pr-20">
<LanguageTabSwitcher />
<DictRequest />
</div>

View File

@@ -11,8 +11,8 @@ import { useConfetti } from './hooks/useConfetti'
import { useWordList } from './hooks/useWordList'
import { TypingContext, TypingStateActionType, initialState, typingReducer } from './store'
import { DonateCard } from '@/components/DonateCard'
import EnhancedPromotionModal from '@/components/EnhancedPromotionModal'
import Header from '@/components/Header'
import StarCard from '@/components/StarCard'
import Tooltip from '@/components/Tooltip'
import { idDictionaryMap } from '@/resources/dictionary'
import { currentChapterAtom, currentDictIdAtom, isReviewModeAtom, randomConfigAtom, reviewModeInfoAtom } from '@/store'
@@ -129,7 +129,7 @@ const App: React.FC = () => {
return (
<TypingContext.Provider value={{ state: state, dispatch }}>
<StarCard />
<EnhancedPromotionModal />
{state.isFinished && <DonateCard />}
{state.isFinished && <ResultScreen />}
<Layout>

View File

@@ -110,5 +110,8 @@ export const wordDictationConfigAtom = atomForConfig('wordDictationConfig', {
export const dismissStartCardDateAtom = atomWithStorage<Date | null>(DISMISS_START_CARD_DATE_KEY, null)
// Enhanced version promotion popup state
export const hasSeenEnhancedPromotionAtom = atomWithStorage('hasSeenEnhancedPromotion', false)
// for dev test
// dismissStartCardDateAtom = atom<Date | null>(new Date())