diff --git a/src/components/DonateCard/hooks/useWordStats.ts b/src/components/DonateCard/hooks/useWordStats.ts new file mode 100644 index 00000000..548b9acc --- /dev/null +++ b/src/components/DonateCard/hooks/useWordStats.ts @@ -0,0 +1,71 @@ +import { db } from '@/utils/db' +import dayjs from 'dayjs' +import { useEffect, useState } from 'react' + +export function useChapterNumber() { + const [chapterNumber, setChapterNumber] = useState(0) + + useEffect(() => { + const fetchChapterNumber = async () => { + const number = await db.chapterRecords.count() + setChapterNumber(number) + } + + fetchChapterNumber() + }, []) + + return chapterNumber +} + +export function useDayFromFirstWordRecord() { + const [dayFromFirstWordRecord, setDayFromFirstWordRecord] = useState(0) + + useEffect(() => { + const fetchDayFromFirstWordRecord = async () => { + const firstWordRecord = await db.wordRecords.orderBy('timeStamp').first() + const firstWordRecordTimeStamp = firstWordRecord?.timeStamp || 0 + const now = dayjs() + const timestamp = dayjs.unix(firstWordRecordTimeStamp) + const daysPassed = now.diff(timestamp, 'day') + setDayFromFirstWordRecord(daysPassed) + } + + fetchDayFromFirstWordRecord() + }, []) + + return dayFromFirstWordRecord +} + +export function useWordNumber() { + const [wordNumber, setWordNumber] = useState(0) + + useEffect(() => { + const fetchWordNumber = async () => { + const number = await db.wordRecords.count() + setWordNumber(number) + } + + fetchWordNumber() + }, []) + + return wordNumber +} + +export function useSumWrongCount() { + const [sumWrongCount, setSumWrongCount] = useState(0) + + useEffect(() => { + const fetchSumWrongCount = async () => { + let totalWrongCount = 0 + + await db.chapterRecords.each((record) => { + totalWrongCount += record.wrongCount || 0 + }) + setSumWrongCount(totalWrongCount) + } + + fetchSumWrongCount() + }, []) + + return sumWrongCount +} diff --git a/src/components/DonateCard/index.tsx b/src/components/DonateCard/index.tsx new file mode 100644 index 00000000..5b62b7c9 --- /dev/null +++ b/src/components/DonateCard/index.tsx @@ -0,0 +1,147 @@ +import { useChapterNumber, useDayFromFirstWordRecord, useSumWrongCount, useWordNumber } from './hooks/useWordStats' +import alipay from '@/assets/alipay.jpg' +import weChat from '@/assets/weChat.jpg' +import { DONATE_DATE } from '@/constants' +import { reportDonateCard } from '@/utils' +import noop from '@/utils/noop' +import { Dialog, Transition } from '@headlessui/react' +import dayjs from 'dayjs' +import type React from 'react' +import { Fragment, useLayoutEffect, useMemo, useState } from 'react' +import IconParty from '~icons/logos/partytown-icon' + +export const DonateCard = () => { + const [show, setShow] = useState(false) + + const chapterNumber = useChapterNumber() + const wordNumber = useWordNumber() + const sumWrongCount = useSumWrongCount() + const dayFromFirstWord = useDayFromFirstWordRecord() + const dayFromQwerty = useMemo(() => { + const now = dayjs() + const past = dayjs('2021-01-21') + return now.diff(past, 'day') + }, []) + + const HighlightedText = ({ children, className }: { children: React.ReactNode; className?: string }) => { + return {children} + } + + const onClickHasDonated = () => { + reportDonateCard({ + type: 'donate', + chapterNumber, + wordNumber, + sumWrongCount, + dayFromFirstWord, + dayFromQwerty, + }) + + setShow(false) + const now = dayjs() + window.localStorage.setItem(DONATE_DATE, now.format()) + } + + const onClickRemindMeLater = () => { + reportDonateCard({ + type: 'dismiss', + chapterNumber, + wordNumber, + sumWrongCount, + dayFromFirstWord, + dayFromQwerty, + }) + + setShow(false) + } + + useLayoutEffect(() => { + if (chapterNumber && chapterNumber !== 0 && chapterNumber % 10 === 0) { + const storedDate = window.localStorage.getItem(DONATE_DATE) + const date = dayjs(storedDate) + const now = dayjs() + const diff = now.diff(date, 'day') + if (!storedDate || diff > 30) { + setShow(true) + } + } + }, [chapterNumber]) + + return ( + + { + noop() + }} + > + +
+ + +
+
+ + +
+

{`${chapterNumber} Chapters Achievement !`}

+
+

+ 您刚刚完成了 {chapterNumber} 章节的练习, Qwerty Learner 已经陪你走过 + {dayFromFirstWord} 天,一起完成了 + {wordNumber} + 词的练习, 帮助您纠正了 {sumWrongCount} 次错误输入,让我们一起为您的进步欢呼 + + + +
+

+

+ Qwerty Learner 已经坚持 开放源码、无广告、无商业化 运营 + {dayFromQwerty} 天, + 我们的目标是为所有学习者提供一个高效、便捷、无干扰的学习环境。 +

+

+ 如果您喜欢 Qwerty, 那么我们诚挚地邀请您考虑进行捐赠,您的捐赠将直接用于维持 Qwerty 的日常运营以及未来的发展,让 Qwerty + 与您一起成长。 +

+
+ +
+ alipay + weChat +
+
+ + +
+
+
+
+
+
+
+
+ ) +} diff --git a/src/constants/index.ts b/src/constants/index.ts index 96eb5eeb..dce170bd 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -4,6 +4,8 @@ export const CHAPTER_LENGTH = 20 export const DISMISS_START_CARD_DATE_KEY = 'dismissStartCardDate' +export const DONATE_DATE = 'donateDate' + export const CONFETTI_DEFAULTS = { colors: ['#5D8C7B', '#F2D091', '#F2A679', '#D9695F', '#8C4646'], shapes: ['square'], diff --git a/src/index.css b/src/index.css index d9fc852d..47aa3443 100644 --- a/src/index.css +++ b/src/index.css @@ -79,11 +79,11 @@ body, shadow-md dark:bg-gray-800 dark:text-gray-300; } - .btn-primary { + .my-btn-primary { @apply flex items-center justify-center rounded-lg bg-indigo-400 px-6 py-1 text-lg text-white hover:opacity-90 focus:outline-none dark:text-opacity-80; } - .btn-info-panel { + .my-btn-info-panel { @apply mt-3 inline-flex w-full justify-center rounded-md px-3 py-2 text-sm font-semibold text-white shadow-sm transition-colors focus:outline-none dark:bg-opacity-70 dark:text-opacity-80 sm:ml-3 sm:mt-0 sm:w-auto @@ -145,3 +145,20 @@ body, @apply block h-4 w-4 cursor-pointer rounded-full border border-gray-200 bg-white drop-shadow-md; } } + +.gradient-text { + background-image: linear-gradient(90deg, #f66, #f90); + background-clip: text; + + color: transparent; + animation: gradient-text-hue 5s linear infinite; +} + +@keyframes gradient-text-hue { + from { + filter: hue-rotate(0); + } + to { + filter: hue-rotate(-1turn); + } +} diff --git a/src/pages/Typing/components/ResultScreen/index.tsx b/src/pages/Typing/components/ResultScreen/index.tsx index 26de10f3..15aa2f7e 100644 --- a/src/pages/Typing/components/ResultScreen/index.tsx +++ b/src/pages/Typing/components/ResultScreen/index.tsx @@ -247,7 +247,7 @@ const ResultScreen = () => {
- diff --git a/src/pages/Typing/components/ShareButton/SharePicDialog.tsx b/src/pages/Typing/components/ShareButton/SharePicDialog.tsx index 608ce145..023b8a56 100644 --- a/src/pages/Typing/components/ShareButton/SharePicDialog.tsx +++ b/src/pages/Typing/components/ShareButton/SharePicDialog.tsx @@ -145,7 +145,7 @@ export default function SharePicDialog({ showState, setShowState, randomChoose }