From cdbdcd2ac81bad5f0976be7be865457752389fea Mon Sep 17 00:00:00 2001 From: Mary Kate Fain Date: Wed, 12 Feb 2025 13:50:50 -0600 Subject: [PATCH 1/9] streak start mobile mvp --- .../compose/components/compose-form.tsx | 8 ++++++- src/features/ui/components/modal-root.tsx | 2 ++ .../ui/components/modals/compose-modal.tsx | 7 +++++- .../ui/components/modals/streak-modal.tsx | 22 +++++++++++++++++++ src/features/ui/util/async-components.ts | 1 + 5 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 src/features/ui/components/modals/streak-modal.tsx diff --git a/src/features/compose/components/compose-form.tsx b/src/features/compose/components/compose-form.tsx index 1e4b4da68..9c4a0851f 100644 --- a/src/features/compose/components/compose-form.tsx +++ b/src/features/compose/components/compose-form.tsx @@ -15,6 +15,7 @@ import { selectComposeSuggestion, uploadCompose, } from 'soapbox/actions/compose.ts'; +import { openModal } from 'soapbox/actions/modals.ts'; import { useCustomEmojis } from 'soapbox/api/hooks/useCustomEmojis.ts'; import AutosuggestInput, { AutoSuggestion } from 'soapbox/components/autosuggest-input.tsx'; import Button from 'soapbox/components/ui/button.tsx'; @@ -72,9 +73,10 @@ interface IComposeForm { event?: string; group?: string; extra?: React.ReactNode; + streak?: number; } -const ComposeForm = ({ id, shouldCondense, autoFocus, clickableAreaRef, event, group, extra }: IComposeForm) => { +const ComposeForm = ({ id, shouldCondense, autoFocus, clickableAreaRef, event, group, extra, streak }: IComposeForm) => { const history = useHistory(); const intl = useIntl(); const dispatch = useAppDispatch(); @@ -156,6 +158,10 @@ const ComposeForm = ({ id, shouldCondense, autoFocus, clickab dispatch(changeCompose(id, text)); dispatch(submitCompose(id, { history })); + if (streak) { + dispatch(openModal('STREAK')); + } + editorRef.current?.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined); }; diff --git a/src/features/ui/components/modal-root.tsx b/src/features/ui/components/modal-root.tsx index c3c996446..ba5894de8 100644 --- a/src/features/ui/components/modal-root.tsx +++ b/src/features/ui/components/modal-root.tsx @@ -39,6 +39,7 @@ import { ReblogsModal, ReplyMentionsModal, ReportModal, + StreakModal, UnauthorizedModal, VideoModal, EditRuleModal, @@ -92,6 +93,7 @@ const MODAL_COMPONENTS: Record> = { 'REBLOGS': ReblogsModal, 'REPLY_MENTIONS': ReplyMentionsModal, 'REPORT': ReportModal, + 'STREAK': StreakModal, 'UNAUTHORIZED': UnauthorizedModal, 'VIDEO': VideoModal, 'ZAPS': ZapsModal, diff --git a/src/features/ui/components/modals/compose-modal.tsx b/src/features/ui/components/modals/compose-modal.tsx index 58e85a496..c30d81e72 100644 --- a/src/features/ui/components/modals/compose-modal.tsx +++ b/src/features/ui/components/modals/compose-modal.tsx @@ -15,6 +15,7 @@ import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts'; import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts'; import { useCompose } from 'soapbox/hooks/useCompose.ts'; import { useDraggedFiles } from 'soapbox/hooks/useDraggedFiles.ts'; +import { useOwnAccount } from 'soapbox/hooks/useOwnAccount.ts'; import ComposeForm from '../../../compose/components/compose-form.tsx'; @@ -33,6 +34,7 @@ const ComposeModal: React.FC = ({ onClose, composeId = 'compose-m const dispatch = useAppDispatch(); const node = useRef(null); const compose = useCompose(composeId); + const { account } = useOwnAccount(); const { id: statusId, privacy, in_reply_to: inReplyTo, quote, group_id: groupId } = compose!; @@ -40,6 +42,8 @@ const ComposeModal: React.FC = ({ onClose, composeId = 'compose-m dispatch(uploadCompose(composeId, files, intl)); }); + const userStreak = useAppSelector((state) => account?.ditto?.streak?.days ?? 0); + const onClickClose = () => { if (checkComposeContent(compose)) { dispatch(openModal('CONFIRM', { @@ -93,6 +97,7 @@ const ComposeModal: React.FC = ({ onClose, composeId = 'compose-m id={composeId} extra={} autoFocus + streak={userStreak} /> ); @@ -135,4 +140,4 @@ const ComposeFormGroupToggle: React.FC = ({ composeId, ); }; -export default ComposeModal; +export default ComposeModal; \ No newline at end of file diff --git a/src/features/ui/components/modals/streak-modal.tsx b/src/features/ui/components/modals/streak-modal.tsx new file mode 100644 index 000000000..c40c6b54c --- /dev/null +++ b/src/features/ui/components/modals/streak-modal.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; + +import Modal from 'soapbox/components/ui/modal.tsx'; +import Text from 'soapbox/components/ui/text.tsx'; + +interface IStreakModal { + onClose: () => void; +} + +const StreakModal: React.FC = ({ onClose }) => { + + return ( + } onClose={onClose}> + + + + + ); +}; + +export default StreakModal; \ No newline at end of file diff --git a/src/features/ui/util/async-components.ts b/src/features/ui/util/async-components.ts index ff6b05aca..e6717d626 100644 --- a/src/features/ui/util/async-components.ts +++ b/src/features/ui/util/async-components.ts @@ -181,3 +181,4 @@ export const ZapsModal = lazy(() => import('soapbox/features/ui/components/modal export const ZapSplitModal = lazy(() => import('soapbox/features/ui/components/modals/zap-split/zap-split-modal.tsx')); export const CaptchaModal = lazy(() => import('soapbox/features/ui/components/modals/captcha-modal/captcha-modal.tsx')); export const NostrBunkerLogin = lazy(() => import('soapbox/features/nostr/nostr-bunker-login.tsx')); +export const StreakModal = lazy(() => import('soapbox/features/ui/components/modals/streak-modal.tsx')); From 470b4ab463ee391a748a4e63c6076574a030ad53 Mon Sep 17 00:00:00 2001 From: Mary Kate Fain Date: Wed, 12 Feb 2025 14:26:55 -0600 Subject: [PATCH 2/9] streak modal message update --- .../ui/components/modals/streak-modal.tsx | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/src/features/ui/components/modals/streak-modal.tsx b/src/features/ui/components/modals/streak-modal.tsx index c40c6b54c..45c8cf43a 100644 --- a/src/features/ui/components/modals/streak-modal.tsx +++ b/src/features/ui/components/modals/streak-modal.tsx @@ -1,20 +1,49 @@ +import flameIcon from '@tabler/icons/filled/flame.svg'; import React from 'react'; import { FormattedMessage } from 'react-intl'; +import HStack from 'soapbox/components/ui/hstack.tsx'; +import Icon from 'soapbox/components/ui/icon.tsx'; import Modal from 'soapbox/components/ui/modal.tsx'; import Text from 'soapbox/components/ui/text.tsx'; +// import { useOwnAccount } from 'soapbox/hooks/useOwnAccount.ts'; +// import { shortNumberFormat } from 'soapbox/utils/numbers.tsx'; + +// const messages = defineMessages({ +// streak: { id: 'account.streak', defaultMessage: 'Day Streak' }, +// }); interface IStreakModal { onClose: () => void; } const StreakModal: React.FC = ({ onClose }) => { + // const { account } = useOwnAccount(); + // const intl = useIntl(); + // const streakCount = account ? shortNumberFormat(account.ditto.streak.days) : 0; return ( - } onClose={onClose}> - - - + + + + + + + + + + + + } + onClose={onClose} + > + + + + + ); }; From fa8bb335c6bc455d5cf1e6d0029a2aed3ac9b8bb Mon Sep 17 00:00:00 2001 From: Mary Kate Fain Date: Wed, 12 Feb 2025 15:07:44 -0600 Subject: [PATCH 3/9] streak modal has account preview basics --- .../ui/components/modals/streak-modal.tsx | 125 ++++++++++++++++-- 1 file changed, 117 insertions(+), 8 deletions(-) diff --git a/src/features/ui/components/modals/streak-modal.tsx b/src/features/ui/components/modals/streak-modal.tsx index 45c8cf43a..fa1f0f592 100644 --- a/src/features/ui/components/modals/streak-modal.tsx +++ b/src/features/ui/components/modals/streak-modal.tsx @@ -1,25 +1,39 @@ import flameIcon from '@tabler/icons/filled/flame.svg'; +import calendarIcon from '@tabler/icons/outline/calendar.svg'; +import clsx from 'clsx'; import React from 'react'; -import { FormattedMessage } from 'react-intl'; +import { useIntl, FormattedMessage, defineMessages } from 'react-intl'; +import { Link } from 'react-router-dom'; +import { dateFormatOptions } from 'soapbox/components/relative-timestamp.tsx'; +import StillImage from 'soapbox/components/still-image.tsx'; +import Avatar from 'soapbox/components/ui/avatar.tsx'; +import { Card, CardBody } from 'soapbox/components/ui/card.tsx'; import HStack from 'soapbox/components/ui/hstack.tsx'; import Icon from 'soapbox/components/ui/icon.tsx'; import Modal from 'soapbox/components/ui/modal.tsx'; +import Stack from 'soapbox/components/ui/stack.tsx'; import Text from 'soapbox/components/ui/text.tsx'; -// import { useOwnAccount } from 'soapbox/hooks/useOwnAccount.ts'; -// import { shortNumberFormat } from 'soapbox/utils/numbers.tsx'; +import VerificationBadge from 'soapbox/components/verification-badge.tsx'; +import ActionButton from 'soapbox/features/ui/components/action-button.tsx'; +import { useOwnAccount } from 'soapbox/hooks/useOwnAccount.ts'; +import { emojifyText } from 'soapbox/utils/emojify.tsx'; +import { shortNumberFormat } from 'soapbox/utils/numbers.tsx'; -// const messages = defineMessages({ -// streak: { id: 'account.streak', defaultMessage: 'Day Streak' }, -// }); +const messages = defineMessages({ + streak: { id: 'account.streak', defaultMessage: 'Day Streak' }, +}); interface IStreakModal { onClose: () => void; } const StreakModal: React.FC = ({ onClose }) => { - // const { account } = useOwnAccount(); - // const intl = useIntl(); + const { account } = useOwnAccount(); + const intl = useIntl(); + if (!account) return null; + const memberSinceDate = intl.formatDate(account.created_at, { month: 'long', year: 'numeric' }); + // const streakCount = account ? shortNumberFormat(account.ditto.streak.days) : 0; return ( @@ -39,6 +53,101 @@ const StreakModal: React.FC = ({ onClose }) => { } onClose={onClose} > +
+ + +
+ +
+ + + + + + + +
+ +
+
+ + + + + + {emojifyText(account.display_name, account.emojis)} + + + {account.verified && } + + + + + + {account.followers_count >= 0 && ( + + + + {shortNumberFormat(account.followers_count)} + + + + + + + )} + + {account.following_count >= 0 && ( + + + + {shortNumberFormat(account.following_count)} + + + + + + + )} + + {account.ditto?.streak?.days > 0 && ( + + + + + + + + {shortNumberFormat(account.ditto.streak.days)} + + + )} + + + {account.local ? ( + + + + + + + + ) : null} +
+ +
+
+
From 4e0aa87ab38693fb7bf6b98614a77a8e36f34ad9 Mon Sep 17 00:00:00 2001 From: Mary Kate Fain Date: Wed, 12 Feb 2025 15:31:14 -0600 Subject: [PATCH 4/9] finish up formatting on streak modal --- .../compose/components/compose-form.tsx | 2 +- .../ui/components/modals/streak-modal.tsx | 37 ++++--------------- 2 files changed, 8 insertions(+), 31 deletions(-) diff --git a/src/features/compose/components/compose-form.tsx b/src/features/compose/components/compose-form.tsx index 9c4a0851f..bb054c493 100644 --- a/src/features/compose/components/compose-form.tsx +++ b/src/features/compose/components/compose-form.tsx @@ -158,7 +158,7 @@ const ComposeForm = ({ id, shouldCondense, autoFocus, clickab dispatch(changeCompose(id, text)); dispatch(submitCompose(id, { history })); - if (streak) { + if (streak === 0) { dispatch(openModal('STREAK')); } diff --git a/src/features/ui/components/modals/streak-modal.tsx b/src/features/ui/components/modals/streak-modal.tsx index fa1f0f592..805b84e8f 100644 --- a/src/features/ui/components/modals/streak-modal.tsx +++ b/src/features/ui/components/modals/streak-modal.tsx @@ -1,11 +1,8 @@ import flameIcon from '@tabler/icons/filled/flame.svg'; -import calendarIcon from '@tabler/icons/outline/calendar.svg'; -import clsx from 'clsx'; import React from 'react'; import { useIntl, FormattedMessage, defineMessages } from 'react-intl'; import { Link } from 'react-router-dom'; -import { dateFormatOptions } from 'soapbox/components/relative-timestamp.tsx'; import StillImage from 'soapbox/components/still-image.tsx'; import Avatar from 'soapbox/components/ui/avatar.tsx'; import { Card, CardBody } from 'soapbox/components/ui/card.tsx'; @@ -32,34 +29,30 @@ const StreakModal: React.FC = ({ onClose }) => { const { account } = useOwnAccount(); const intl = useIntl(); if (!account) return null; - const memberSinceDate = intl.formatDate(account.created_at, { month: 'long', year: 'numeric' }); // const streakCount = account ? shortNumberFormat(account.ditto.streak.days) : 0; return ( - + + - + } onClose={onClose} > -
- +
+ -
+
@@ -127,29 +120,13 @@ const StreakModal: React.FC = ({ onClose }) => { )} - {account.local ? ( - - - - - - - - ) : null}
- + From 6b8caa0ee912c768b2df7b2deea9ec7f492f0de9 Mon Sep 17 00:00:00 2001 From: Mary Kate Fain Date: Wed, 12 Feb 2025 15:47:21 -0600 Subject: [PATCH 5/9] make it show 1 streak even if zero if you just posted, because the streak post just hasn't gone through yet --- .../ui/components/modals/streak-modal.tsx | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/features/ui/components/modals/streak-modal.tsx b/src/features/ui/components/modals/streak-modal.tsx index 805b84e8f..096a5f6ba 100644 --- a/src/features/ui/components/modals/streak-modal.tsx +++ b/src/features/ui/components/modals/streak-modal.tsx @@ -106,18 +106,18 @@ const StreakModal: React.FC = ({ onClose }) => { )} - {account.ditto?.streak?.days > 0 && ( - - - - - - - - {shortNumberFormat(account.ditto.streak.days)} - - - )} + + + + + + + + {account.ditto?.streak?.days > 0 ? + shortNumberFormat(account.ditto.streak.days) : + } + + From ccd73bcf24c1e3dc2a94493eed657054c61c0bc3 Mon Sep 17 00:00:00 2001 From: Mary Kate Fain Date: Wed, 12 Feb 2025 15:53:10 -0600 Subject: [PATCH 6/9] remove stray comment --- src/features/ui/components/modals/streak-modal.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/features/ui/components/modals/streak-modal.tsx b/src/features/ui/components/modals/streak-modal.tsx index 096a5f6ba..ae5c540ad 100644 --- a/src/features/ui/components/modals/streak-modal.tsx +++ b/src/features/ui/components/modals/streak-modal.tsx @@ -30,8 +30,6 @@ const StreakModal: React.FC = ({ onClose }) => { const intl = useIntl(); if (!account) return null; - // const streakCount = account ? shortNumberFormat(account.ditto.streak.days) : 0; - return ( Date: Wed, 12 Feb 2025 15:55:46 -0600 Subject: [PATCH 7/9] update i18n --- src/locales/en.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/locales/en.json b/src/locales/en.json index 3a531ab37..aca5adb3c 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1597,6 +1597,10 @@ "status_list.queue_label": "Click to see {count} new {count, plural, one {post} other {posts}}", "statuses.quote_tombstone": "Post is unavailable.", "statuses.tombstone": "One or more posts are unavailable.", + "streak_modal.message": "Post every day to keep it going.", + "streak_modal.streak_count": "1", + "streak_modal.sub": "streak!", + "streak_modal.title": "You've unlocked a", "streamfield.add": "Add", "streamfield.remove": "Remove", "suggestions.dismiss": "Dismiss suggestion", From 34ef675ab4293770c42b82ff4902ef2dd84cd9db Mon Sep 17 00:00:00 2001 From: Mary Kate Date: Thu, 13 Feb 2025 17:19:51 +0000 Subject: [PATCH 8/9] Apply 2 suggestion(s) to 2 file(s) Co-authored-by: Alex Gleason --- src/features/compose/components/compose-form.tsx | 2 +- src/features/ui/components/modals/compose-modal.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/compose/components/compose-form.tsx b/src/features/compose/components/compose-form.tsx index bb054c493..c8dde1ef2 100644 --- a/src/features/compose/components/compose-form.tsx +++ b/src/features/compose/components/compose-form.tsx @@ -158,7 +158,7 @@ const ComposeForm = ({ id, shouldCondense, autoFocus, clickab dispatch(changeCompose(id, text)); dispatch(submitCompose(id, { history })); - if (streak === 0) { + if (streak === 0 && features.streak) { dispatch(openModal('STREAK')); } diff --git a/src/features/ui/components/modals/compose-modal.tsx b/src/features/ui/components/modals/compose-modal.tsx index c30d81e72..7d3841a60 100644 --- a/src/features/ui/components/modals/compose-modal.tsx +++ b/src/features/ui/components/modals/compose-modal.tsx @@ -42,7 +42,7 @@ const ComposeModal: React.FC = ({ onClose, composeId = 'compose-m dispatch(uploadCompose(composeId, files, intl)); }); - const userStreak = useAppSelector((state) => account?.ditto?.streak?.days ?? 0); + const userStreak = account?.ditto.streak.days; const onClickClose = () => { if (checkComposeContent(compose)) { From cc555ffb5bcfad4cf5959f71bb3945806e920a11 Mon Sep 17 00:00:00 2001 From: Mary Kate Fain Date: Thu, 13 Feb 2025 11:27:32 -0600 Subject: [PATCH 9/9] add streak to features --- src/utils/features.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/utils/features.ts b/src/utils/features.ts index ccad8d361..c9c8bb6a2 100644 --- a/src/utils/features.ts +++ b/src/utils/features.ts @@ -1017,6 +1017,12 @@ const getInstanceFeatures = (instance: InstanceV1 | InstanceV2) => { */ spoilers: v.software !== TRUTHSOCIAL, + /** + * Can view user streaks. + * @see GET /api/v1/accounts/verify_credentials + */ + streak: v.software === DITTO, + /** * Can display suggested accounts. * @see {@link https://docs.joinmastodon.org/methods/suggestions/}