From 0cf3050ecfe54800a3edb5f9c53efe2c2e0dc1d2 Mon Sep 17 00:00:00 2001 From: Marcin Date: Tue, 8 Oct 2024 15:14:10 +0200 Subject: [PATCH 01/17] Change screen when community does not have stake enabled --- .../AdminContestsPage/AdminContestsPage.tsx | 43 +++++++++++++++---- .../Contests/ContestsList/ContestsList.tsx | 10 ++++- .../EmptyContestsList/EmptyContestsList.tsx | 31 ++++++++++--- .../Contests/ManageContest/ManageContest.tsx | 4 +- .../scripts/views/pages/Contests/Contests.tsx | 11 ++++- 5 files changed, 79 insertions(+), 20 deletions(-) diff --git a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/AdminContestsPage/AdminContestsPage.tsx b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/AdminContestsPage/AdminContestsPage.tsx index 740a6d467a5..6300f808ac1 100644 --- a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/AdminContestsPage/AdminContestsPage.tsx +++ b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/AdminContestsPage/AdminContestsPage.tsx @@ -12,6 +12,7 @@ import { } from 'shared/analytics/types'; import app from 'state'; import useGetFeeManagerBalanceQuery from 'state/api/communityStake/getFeeManagerBalance'; +import { useFetchTopicsQuery } from 'state/api/topics'; import useUserStore from 'state/ui/user'; import Permissions from 'utils/Permissions'; import { useCommunityStake } from 'views/components/CommunityStake'; @@ -41,6 +42,7 @@ const AdminContestsPage = () => { const ethChainId = app?.chain?.meta?.ChainNode?.eth_chain_id || 0; const { stakeData } = useCommunityStake(); const namespace = stakeData?.Community?.namespace; + const communityId = app.activeChainId() || ''; const { trackAnalytics } = useBrowserAnalyticsTrack({ onAction: true, @@ -53,11 +55,23 @@ const AdminContestsPage = () => { isContestDataLoading, } = useCommunityContests(); + const { data: topicData } = useFetchTopicsQuery({ + communityId, + apiEnabled: !!communityId, + }); + + const hasAtLeastOneWeightedVotingTopic = topicData?.some( + (t) => t.weightedVoting, + ); + const { data: feeManagerBalance, isLoading: isFeeManagerBalanceLoading } = useGetFeeManagerBalanceQuery({ ethChainId: ethChainId!, namespace, - apiEnabled: !!ethChainId && !!namespace && stakeEnabled, + apiEnabled: + !!ethChainId && !!namespace && farcasterContestEnabled + ? true + : stakeEnabled, }); const handleCreateContestClicked = () => { @@ -76,7 +90,12 @@ const AdminContestsPage = () => { } const showBanner = - stakeEnabled && isContestAvailable && ethChainId && namespace; + (farcasterContestEnabled + ? hasAtLeastOneWeightedVotingTopic + : stakeEnabled) && + isContestAvailable && + ethChainId && + namespace; return ( @@ -84,13 +103,16 @@ const AdminContestsPage = () => {
Contests - {stakeEnabled && contestView !== ContestView.TypeSelection && ( - - )} + {(farcasterContestEnabled + ? hasAtLeastOneWeightedVotingTopic + : stakeEnabled) && + contestView !== ContestView.TypeSelection && ( + + )}
{contestView === ContestView.List ? ( @@ -106,6 +128,7 @@ const AdminContestsPage = () => { contests={contestsData} isLoading={isContestDataLoading} isAdmin={isAdmin} + hasWeightedTopic={!!hasAtLeastOneWeightedVotingTopic} isContestAvailable={isContestAvailable} stakeEnabled={stakeEnabled} feeManagerBalance={feeManagerBalance} @@ -148,3 +171,5 @@ const AdminContestsPage = () => { }; export default AdminContestsPage; + +// fix breadcrumbs diff --git a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ContestsList/ContestsList.tsx b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ContestsList/ContestsList.tsx index 1f5b1360538..449c8345324 100644 --- a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ContestsList/ContestsList.tsx +++ b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ContestsList/ContestsList.tsx @@ -1,6 +1,7 @@ import moment from 'moment'; import React, { useState } from 'react'; +import { useFlag } from 'hooks/useFlag'; import { Skeleton } from 'views/components/Skeleton'; import EmptyContestsList from '../EmptyContestsList'; @@ -42,21 +43,25 @@ interface ContestsListProps { contests: Contest[]; isAdmin: boolean; isLoading: boolean; + hasWeightedTopic: boolean; stakeEnabled: boolean; isContestAvailable: boolean; feeManagerBalance?: string; onSetContestSelectionView?: () => void; } + const ContestsList = ({ contests, isAdmin, isLoading, + hasWeightedTopic, stakeEnabled, isContestAvailable, feeManagerBalance, onSetContestSelectionView, }: ContestsListProps) => { const [fundDrawerContest, setFundDrawerContest] = useState(); + const farcasterContestEnabled = useFlag('farcasterContest'); if (isLoading) { return ( @@ -71,8 +76,11 @@ const ContestsList = ({ return ( <>
- {isAdmin && (!stakeEnabled || !isContestAvailable) ? ( + {isAdmin && + ((farcasterContestEnabled ? !hasWeightedTopic : !stakeEnabled) || + !isContestAvailable) ? ( void; + hasWeightedTopic: boolean; } const EmptyContestsList = ({ isStakeEnabled, isContestAvailable, onSetContestSelectionView, + hasWeightedTopic, }: EmptyContestsListProps) => { const navigate = useCommonNavigate(); const farcasterContestEnabled = useFlag('farcasterContest'); return (
- {!isStakeEnabled ? ( + {(farcasterContestEnabled ? !hasWeightedTopic : !isStakeEnabled) ? ( navigate('/manage/integrations'), - }} + title={ + farcasterContestEnabled + ? 'You must have at least one topic with weighted voting enabled to run contest' + : 'You must enable Community Stake' + } + subtitle={ + farcasterContestEnabled + ? 'Setting up a contest just takes a few minutes and can be a huge boost to your community.' + : 'Contests require Community Stake...' + } + button={ + farcasterContestEnabled + ? { + label: 'Create a topic', + handler: () => navigate('/manage/topics'), + } + : { + label: 'Enable Community Stake', + handler: () => navigate('/manage/integrations'), + } + } /> ) : !isContestAvailable ? ( { const [launchContestStep, setLaunchContestStep] = useState('DetailsForm'); const [createdContestAddress, setCreatedContestAddress] = useState(''); + const farcasterContestEnabled = useFlag('farcasterContest'); const user = useUserStore(); @@ -44,7 +46,7 @@ const ManageContest = ({ contestAddress }: ManageContestProps) => { if ( !user.isLoggedIn || - !stakeEnabled || + (farcasterContestEnabled ? false : !stakeEnabled) || !(Permissions.isSiteAdmin() || Permissions.isCommunityAdmin()) || contestNotFound ) { diff --git a/packages/commonwealth/client/scripts/views/pages/Contests/Contests.tsx b/packages/commonwealth/client/scripts/views/pages/Contests/Contests.tsx index 60da59cbc24..e339173c1f9 100644 --- a/packages/commonwealth/client/scripts/views/pages/Contests/Contests.tsx +++ b/packages/commonwealth/client/scripts/views/pages/Contests/Contests.tsx @@ -7,9 +7,12 @@ import CWPageLayout from 'views/components/component_kit/new_designs/CWPageLayou import ContestsList from 'views/pages/CommunityManagement/Contests/ContestsList'; import useCommunityContests from 'views/pages/CommunityManagement/Contests/useCommunityContests'; +import { useFlag } from 'hooks/useFlag'; import './Contests.scss'; const Contests = () => { + const farcasterContestEnabled = useFlag('farcasterContest'); + const { stakeEnabled, contestsData, @@ -17,7 +20,10 @@ const Contests = () => { isContestDataLoading, } = useCommunityContests(); - if (!isContestDataLoading && (!stakeEnabled || !isContestAvailable)) { + if ( + !isContestDataLoading && + ((farcasterContestEnabled ? false : !stakeEnabled) || !isContestAvailable) + ) { return ; } @@ -34,8 +40,9 @@ const Contests = () => {
From ece2f721bd7340a6c885cc182fd1c49231197fd7 Mon Sep 17 00:00:00 2001 From: Marcin Date: Wed, 9 Oct 2024 11:11:32 +0200 Subject: [PATCH 02/17] UI changes for contest launch --- .../Contests/ManageContest/ManageContest.tsx | 2 +- .../DetailsFormStep/DetailsFormStep.scss | 42 ++-- .../steps/DetailsFormStep/DetailsFormStep.tsx | 238 ++++++++++++++---- .../steps/DetailsFormStep/utils.ts | 19 +- .../steps/DetailsFormStep/validation.ts | 7 + .../Contests/ManageContest/types.ts | 2 +- 6 files changed, 238 insertions(+), 72 deletions(-) diff --git a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/ManageContest.tsx b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/ManageContest.tsx index a587ff500ac..677eaff7b20 100644 --- a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/ManageContest.tsx +++ b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/ManageContest.tsx @@ -83,7 +83,7 @@ const ManageContest = ({ contestAddress }: ManageContestProps) => { return ( diff --git a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/DetailsFormStep.scss b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/DetailsFormStep.scss index b9f720f5eb2..4186b195fae 100644 --- a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/DetailsFormStep.scss +++ b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/DetailsFormStep.scss @@ -52,6 +52,10 @@ margin-bottom: 64px; } + .contest-section-topic { + margin-bottom: 64px; + } + .contest-section-description { margin-bottom: 64px; @@ -83,7 +87,7 @@ } .contest-section-funding { - margin-bottom: 64px; + margin-bottom: 32px; } .contest-section-duration { @@ -97,28 +101,26 @@ } } - .contest-section-recurring { - .prize-subsection { - margin-top: 24px; + .prize-subsection { + margin-top: 24px; - .percentage-buttons { - margin-top: 8px; - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; - flex-wrap: wrap; + .percentage-buttons { + margin-top: 8px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + flex-wrap: wrap; - @include extraSmall { - gap: unset; - } + @include extraSmall { + gap: unset; + } - .Button { - padding-inline: 36px; + .Button { + padding-inline: 36px; - @include extraSmall { - padding-inline: 10px; - } + @include extraSmall { + padding-inline: 10px; } } } @@ -252,6 +254,8 @@ .contest-name-input, .funding-token-address-input { + margin-bottom: 32px; + .MessageRow:first-child { display: none; // hide input label } diff --git a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/DetailsFormStep.tsx b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/DetailsFormStep.tsx index 3dcc63b955d..0784721da85 100644 --- a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/DetailsFormStep.tsx +++ b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/DetailsFormStep.tsx @@ -1,11 +1,13 @@ import React, { useState } from 'react'; import { useSearchParams } from 'react-router-dom'; +import { TopicWeightedVoting } from '@hicommonwealth/schemas'; import { notifyError } from 'controllers/app/notifications'; -import { useFlag } from 'hooks/useFlag'; import { useCommonNavigate } from 'navigation/helpers'; import app from 'state'; import useUpdateContestMutation from 'state/api/contests/updateContest'; +import { useFetchTopicsQuery } from 'state/api/topics'; +import TokenFinder, { useTokenFinder } from 'views/components/TokenFinder'; import { CWCoverImageUploader, ImageBehavior, @@ -16,6 +18,7 @@ import { CWText } from 'views/components/component_kit/cw_text'; import { CWTextArea } from 'views/components/component_kit/cw_text_area'; import { CWButton } from 'views/components/component_kit/new_designs/CWButton'; import { CWForm } from 'views/components/component_kit/new_designs/CWForm'; +import { CWSelectList } from 'views/components/component_kit/new_designs/CWSelectList'; import { CWTextInput } from 'views/components/component_kit/new_designs/CWTextInput'; import { MessageRow } from 'views/components/component_kit/new_designs/CWTextInput/MessageRow'; import { CWRadioButton } from 'views/components/component_kit/new_designs/cw_radio_button'; @@ -24,7 +27,6 @@ import { openConfirmation } from 'views/modals/confirmation_modal'; import { ContestType } from 'views/pages/CommunityManagement/Contests/types'; import CommunityManagementLayout from 'views/pages/CommunityManagement/common/CommunityManagementLayout'; -import TokenFinder, { useTokenFinder } from 'views/components/TokenFinder'; import { CONTEST_FAQ_URL } from '../../../utils'; import { ContestFeeType, @@ -40,10 +42,11 @@ import { INITIAL_PERCENTAGE_VALUE, MAX_WINNERS, MIN_WINNERS, - farcasterDurationOptions, - initialFarcasterDuration, + contestDurationOptions, + initialContestDuration, initialPayoutStructure, prizePercentageOptions, + weightedVotingValueToLabel, } from './utils'; import { detailsFormValidationSchema } from './validation'; @@ -61,7 +64,8 @@ const DetailsFormStep = ({ onSetContestFormData, }: DetailsFormStepProps) => { const navigate = useCommonNavigate(); - const farcasterContestEnabled = useFlag('farcasterContest'); + // const farcasterContestEnabled = useFlag('farcasterContest'); + const farcasterContestEnabled = true; const [searchParams] = useSearchParams(); const contestType = searchParams.get('type'); const isFarcasterContest = contestType === ContestType.Farcaster; @@ -72,11 +76,9 @@ const DetailsFormStep = ({ const [prizePercentage, setPrizePercentage] = useState< ContestFormData['prizePercentage'] >(contestFormData?.prizePercentage || INITIAL_PERCENTAGE_VALUE); - const [farcasterContestDuration, setFarcasterContestDuration] = useState< - number | undefined - >( - isFarcasterContest - ? contestFormData?.farcasterContestDuration || initialFarcasterDuration + const [contestDuration, setContestDuration] = useState( + farcasterContestEnabled + ? contestFormData?.contestDuration || initialContestDuration : undefined, ); @@ -106,6 +108,12 @@ const DetailsFormStep = ({ chainId: chainId, }); + const communityId = app.activeChainId() || ''; + const { data: topicsData } = useFetchTopicsQuery({ + communityId, + apiEnabled: !!communityId, + }); + const editMode = !!contestAddress; const payoutRowError = payoutStructure.some((payout) => payout < 1); const totalPayoutPercentage = payoutStructure.reduce( @@ -114,9 +122,19 @@ const DetailsFormStep = ({ ); const totalPayoutPercentageError = totalPayoutPercentage !== 100; + const weightedTopics = (topicsData || []) + .filter((t) => t?.weightedVoting) + .map((t) => ({ + value: t.id, + label: t.name, + weightedVoting: t.weightedVoting, + helpText: weightedVotingValueToLabel(t.weightedVoting!), + })); + const getInitialValues = () => { return { contestName: contestFormData?.contestName, + contestTopic: contestFormData?.contestTopic, contestDescription: contestFormData?.contestDescription, contestImage: contestFormData?.contestImage, feeType: @@ -167,7 +185,7 @@ const DetailsFormStep = ({ }; const handleSubmit = async (values: ContestFormValidationSubmitValues) => { - const topicsError = !isFarcasterContest && topicsEnabledError; + const topicsError = !farcasterContestEnabled && topicsEnabledError; if (totalPayoutPercentageError || payoutRowError || topicsError) { return; @@ -183,7 +201,7 @@ const DetailsFormStep = ({ prizePercentage, payoutStructure, toggledTopicList, - farcasterContestDuration, + contestDuration, }; if (editMode) { @@ -253,6 +271,26 @@ const DetailsFormStep = ({ > {({ watch, setValue }) => ( <> + {farcasterContestEnabled && !isFarcasterContest && ( +
+ Choose a topic + + Select which topic you would like to include in this + contest. Only threads posted to this topic will be eligible + for the contest prizes. + + + +
+ )} +
Name your contest @@ -307,46 +345,148 @@ const DetailsFormStep = ({ - {farcasterContestEnabled && isFarcasterContest ? ( - <> -
- Fund your contest - - Enter the address of the token you would like to use to - fund your contest - - - -
- -
-
- Contest duration + {farcasterContestEnabled ? ( + isFarcasterContest ? ( + <> +
+ Fund your contest - How long would you like your contest to run? + Enter the address of the token you would like to use to + fund your contest + +
- o.value === farcasterContestDuration, - )} - onChange={(newValue) => { - setFarcasterContestDuration(newValue?.value); - }} - /> -
- +
+
+ Contest duration + + How long would you like your contest to run? + +
+ + o.value === contestDuration, + )} + onChange={(newValue) => { + setContestDuration(newValue?.value); + }} + /> +
+ + ) : ( + <> + {watch('contestTopic')?.weightedVoting === + TopicWeightedVoting.ERC20 ? ( + <> +
+ Contest Funding + + Enter the token address to set as your funding + method. + +
+ + Token address + + + + ) : watch('contestTopic')?.weightedVoting === + TopicWeightedVoting.Stake ? ( + <> +
+ Contest Funding + + Set the amount of community stake you want to + allocate for your contest. + + +
+ + How much of the funds would you like to use + weekly? + + + All community stake funded contests are recurring + weekly. +
+ Tip: smaller prizes makes the contest run longer +
+
+ {prizePercentageOptions.map( + ({ value, label }) => ( + setPrizePercentage(value)} + buttonType={ + prizePercentage === value + ? 'primary' + : 'secondary' + } + /> + ), + )} +
+
+
+ + ) : ( + <> + )} + +
+
+ Contest duration + + How long would you like your contest to run? + +
+ + o.value === contestDuration, + )} + onChange={(newValue) => { + setContestDuration(newValue?.value); + }} + /> +
+ + + + ) ) : ( <>
@@ -546,7 +686,7 @@ const DetailsFormStep = ({
- {farcasterContestEnabled && isFarcasterContest ? ( + {farcasterContestEnabled ? ( <> ) : ( <> diff --git a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/utils.ts b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/utils.ts index d54c5df673b..e29d756e93b 100644 --- a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/utils.ts +++ b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/utils.ts @@ -1,3 +1,4 @@ +import { TopicWeightedVoting } from '@hicommonwealth/schemas'; import colors from '../../../../../../../../styles/mixins/colors.module.scss'; export const INITIAL_PERCENTAGE_VALUE = 10; @@ -47,7 +48,7 @@ export const getPrizeColor = (index: number) => { export const DAY_IN_SECONDS = 24 * 60 * 60; -export const farcasterDurationOptions = Array.from({ length: 7 }, (_, i) => { +export const contestDurationOptions = Array.from({ length: 7 }, (_, i) => { const days = i + 1; return { label: `${days} Day${days > 1 ? 's' : ''}`, @@ -55,4 +56,18 @@ export const farcasterDurationOptions = Array.from({ length: 7 }, (_, i) => { }; }); -export const initialFarcasterDuration = farcasterDurationOptions[6].value; +export const initialContestDuration = contestDurationOptions[6].value; + +export const weightedVotingValueToLabel = ( + weightedVoting: TopicWeightedVoting, +) => { + if (weightedVoting === TopicWeightedVoting.Stake) { + return 'Community Stake'; + } + + if (weightedVoting === TopicWeightedVoting.ERC20) { + return 'ERC20'; + } + + return ''; +}; diff --git a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/validation.ts b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/validation.ts index c39e7974d75..3a9910fe891 100644 --- a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/validation.ts +++ b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/validation.ts @@ -9,6 +9,13 @@ export const detailsFormValidationSchema = z.object({ .max(255, { message: VALIDATION_MESSAGES.MAX_CHAR_LIMIT_REACHED }), contestDescription: z.string().optional(), contestImage: z.string().optional(), + contestTopic: z + .object({ + value: z.number(), + label: z.string(), + }) + .optional() + .refine((value) => !!value, { message: 'You must select a topic' }), feeType: z.enum([ ContestFeeType.CommunityStake, ContestFeeType.DirectDeposit, diff --git a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/types.ts b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/types.ts index e2ab62d6b61..5445a7d5138 100644 --- a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/types.ts +++ b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/types.ts @@ -23,6 +23,6 @@ export type ContestFormValidationSubmitValues = z.infer< export type ContestFormData = ContestFormValidationSubmitValues & { prizePercentage: number; payoutStructure: number[]; - farcasterContestDuration?: number; + contestDuration?: number; toggledTopicList: { name: string; id?: number; checked: boolean }[]; }; From 8d7628ddbbd6f6a87966122ce7844487932bbe35 Mon Sep 17 00:00:00 2001 From: Marcin Date: Wed, 9 Oct 2024 11:52:24 +0200 Subject: [PATCH 03/17] Add separate feature flag for weighted votting --- libs/model/src/community/CreateTopic.command.ts | 2 +- libs/model/src/config.ts | 6 +++--- .../client/scripts/helpers/feature-flags.ts | 1 + .../scripts/navigation/CommonDomainRoutes.tsx | 4 ++-- .../scripts/navigation/CustomDomainRoutes.tsx | 4 ++-- .../client/scripts/navigation/Router.tsx | 9 +++------ .../NewThreadFormLegacy/NewThreadForm.tsx | 2 +- .../AdminContestsPage/AdminContestsPage.tsx | 7 +++---- .../Contests/ContestsList/ContestsList.tsx | 4 ++-- .../EmptyContestsList/EmptyContestsList.tsx | 9 +++++---- .../Contests/ManageContest/ManageContest.tsx | 4 ++-- .../steps/DetailsFormStep/DetailsFormStep.tsx | 15 ++++++++------- .../scripts/views/pages/Contests/Contests.tsx | 4 ++-- .../pages/CreateCommunity/useCreateCommunity.ts | 4 ++-- .../views/pages/discussions/DiscussionsPage.tsx | 4 ++-- packages/commonwealth/client/vite.config.ts | 3 +++ 16 files changed, 42 insertions(+), 40 deletions(-) diff --git a/libs/model/src/community/CreateTopic.command.ts b/libs/model/src/community/CreateTopic.command.ts index 6e8299cd1e2..35f7aaf4cd4 100644 --- a/libs/model/src/community/CreateTopic.command.ts +++ b/libs/model/src/community/CreateTopic.command.ts @@ -57,7 +57,7 @@ export function CreateTopic(): Command< throw new InvalidState(Errors.StakeNotAllowed); } - if (config.CONTESTS.FLAG_FARCASTER_CONTEST) { + if (config.CONTESTS.FLAG_WEIGHTED_TOPICS) { // new path: stake or ERC20 if (payload.weighted_voting) { options = { diff --git a/libs/model/src/config.ts b/libs/model/src/config.ts index 2e0af8ea92e..2d76b79bf93 100644 --- a/libs/model/src/config.ts +++ b/libs/model/src/config.ts @@ -30,7 +30,7 @@ const { ETH_RPC, COSMOS_REGISTRY_API, REACTION_WEIGHT_OVERRIDE, - FLAG_FARCASTER_CONTEST, + FLAG_WEIGHTED_TOPICS, ALCHEMY_PRIVATE_APP_KEY, ALCHEMY_PUBLIC_APP_KEY, MEMBERSHIP_REFRESH_BATCH_SIZE, @@ -85,7 +85,7 @@ export const config = configure( MAX_USER_POSTS_PER_CONTEST: MAX_USER_POSTS_PER_CONTEST ? parseInt(MAX_USER_POSTS_PER_CONTEST, 10) : 2, - FLAG_FARCASTER_CONTEST: FLAG_FARCASTER_CONTEST === 'true', + FLAG_WEIGHTED_TOPICS: FLAG_WEIGHTED_TOPICS === 'true', }, AUTH: { JWT_SECRET: JWT_SECRET || DEFAULTS.JWT_SECRET, @@ -183,7 +183,7 @@ export const config = configure( CONTESTS: z.object({ MIN_USER_ETH: z.number(), MAX_USER_POSTS_PER_CONTEST: z.number().int(), - FLAG_FARCASTER_CONTEST: z.boolean(), + FLAG_WEIGHTED_TOPICS: z.boolean(), }), AUTH: z .object({ diff --git a/packages/commonwealth/client/scripts/helpers/feature-flags.ts b/packages/commonwealth/client/scripts/helpers/feature-flags.ts index 86abccac51f..0e3d0900634 100644 --- a/packages/commonwealth/client/scripts/helpers/feature-flags.ts +++ b/packages/commonwealth/client/scripts/helpers/feature-flags.ts @@ -23,6 +23,7 @@ const buildFlag = (env: string | undefined) => { const featureFlags = { contest: buildFlag(process.env.FLAG_CONTEST), contestDev: buildFlag(process.env.FLAG_CONTEST_DEV), + weightedTopics: buildFlag(process.env.FLAG_WEIGHTED_TOPICS), knockPushNotifications: buildFlag( process.env.FLAG_KNOCK_PUSH_NOTIFICATIONS_ENABLED, ), diff --git a/packages/commonwealth/client/scripts/navigation/CommonDomainRoutes.tsx b/packages/commonwealth/client/scripts/navigation/CommonDomainRoutes.tsx index 1beadf2bb36..3e218fa3a19 100644 --- a/packages/commonwealth/client/scripts/navigation/CommonDomainRoutes.tsx +++ b/packages/commonwealth/client/scripts/navigation/CommonDomainRoutes.tsx @@ -118,7 +118,7 @@ const CommunityNotFoundPage = lazy( const CommonDomainRoutes = ({ contestEnabled, - farcasterContestEnabled, + weightedTopicsEnabled, tokenizedCommunityEnabled, }: RouteFeatureFlags) => [ import('views/pages/profile_redirect')); const CustomDomainRoutes = ({ contestEnabled, - farcasterContestEnabled, + weightedTopicsEnabled, tokenizedCommunityEnabled, }: RouteFeatureFlags) => { return [ @@ -307,7 +307,7 @@ const CustomDomainRoutes = ({ key="/manage/topics" path="/manage/topics" element={withLayout( - farcasterContestEnabled ? CommunityTopics : CommunityTopicsOld, + weightedTopicsEnabled ? CommunityTopics : CommunityTopicsOld, { scoped: true, }, diff --git a/packages/commonwealth/client/scripts/navigation/Router.tsx b/packages/commonwealth/client/scripts/navigation/Router.tsx index 2a05f794587..3b9189af29f 100644 --- a/packages/commonwealth/client/scripts/navigation/Router.tsx +++ b/packages/commonwealth/client/scripts/navigation/Router.tsx @@ -14,7 +14,7 @@ import GeneralRoutes from './GeneralRoutes'; export type RouteFeatureFlags = { contestEnabled: boolean; - farcasterContestEnabled: boolean; + weightedTopicsEnabled: boolean; tokenizedCommunityEnabled: boolean; }; @@ -22,10 +22,7 @@ const Router = () => { const client = OpenFeature.getClient(); const contestEnabled = client.getBooleanValue('contest', false); - const farcasterContestEnabled = client.getBooleanValue( - 'farcasterContest', - false, - ); + const weightedTopicsEnabled = client.getBooleanValue('weightedTopics', false); const tokenizedCommunityEnabled = client.getBooleanValue( 'tokenizedCommunity', @@ -34,7 +31,7 @@ const Router = () => { const flags = { contestEnabled, - farcasterContestEnabled, + weightedTopicsEnabled, tokenizedCommunityEnabled, }; diff --git a/packages/commonwealth/client/scripts/views/components/NewThreadFormLegacy/NewThreadForm.tsx b/packages/commonwealth/client/scripts/views/components/NewThreadFormLegacy/NewThreadForm.tsx index 907a8e3dca9..3d87a41baaf 100644 --- a/packages/commonwealth/client/scripts/views/components/NewThreadFormLegacy/NewThreadForm.tsx +++ b/packages/commonwealth/client/scripts/views/components/NewThreadFormLegacy/NewThreadForm.tsx @@ -145,7 +145,7 @@ export const NewThreadForm = () => { ); const isActionAllowedInGatedTopic = !!(memberships || []).find( (membership) => - threadTopic.id && + threadTopic && threadTopic?.id && membership.topicIds.includes(threadTopic?.id) && membership.isAllowed, diff --git a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/AdminContestsPage/AdminContestsPage.tsx b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/AdminContestsPage/AdminContestsPage.tsx index 6300f808ac1..63afa96e52a 100644 --- a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/AdminContestsPage/AdminContestsPage.tsx +++ b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/AdminContestsPage/AdminContestsPage.tsx @@ -31,6 +31,7 @@ import './AdminContestsPage.scss'; const AdminContestsPage = () => { const farcasterContestEnabled = useFlag('farcasterContest'); + const weightedTopicsEnabled = useFlag('weightedTopics'); const [contestView, setContestView] = useState(ContestView.List); const navigate = useCommonNavigate(); @@ -69,7 +70,7 @@ const AdminContestsPage = () => { ethChainId: ethChainId!, namespace, apiEnabled: - !!ethChainId && !!namespace && farcasterContestEnabled + !!ethChainId && !!namespace && weightedTopicsEnabled ? true : stakeEnabled, }); @@ -90,9 +91,7 @@ const AdminContestsPage = () => { } const showBanner = - (farcasterContestEnabled - ? hasAtLeastOneWeightedVotingTopic - : stakeEnabled) && + (weightedTopicsEnabled ? hasAtLeastOneWeightedVotingTopic : stakeEnabled) && isContestAvailable && ethChainId && namespace; diff --git a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ContestsList/ContestsList.tsx b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ContestsList/ContestsList.tsx index 449c8345324..f749f8b8b8c 100644 --- a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ContestsList/ContestsList.tsx +++ b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ContestsList/ContestsList.tsx @@ -61,7 +61,7 @@ const ContestsList = ({ onSetContestSelectionView, }: ContestsListProps) => { const [fundDrawerContest, setFundDrawerContest] = useState(); - const farcasterContestEnabled = useFlag('farcasterContest'); + const weightedTopicsEnabled = useFlag('weightedTopics'); if (isLoading) { return ( @@ -77,7 +77,7 @@ const ContestsList = ({ <>
{isAdmin && - ((farcasterContestEnabled ? !hasWeightedTopic : !stakeEnabled) || + ((weightedTopicsEnabled ? !hasWeightedTopic : !stakeEnabled) || !isContestAvailable) ? ( { const navigate = useCommonNavigate(); const farcasterContestEnabled = useFlag('farcasterContest'); + const weightedTopicsEnabled = useFlag('weightedTopics'); return (
- {(farcasterContestEnabled ? !hasWeightedTopic : !isStakeEnabled) ? ( + {(weightedTopicsEnabled ? !hasWeightedTopic : !isStakeEnabled) ? ( navigate('/manage/topics'), diff --git a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/ManageContest.tsx b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/ManageContest.tsx index 677eaff7b20..5c7bbbedcf2 100644 --- a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/ManageContest.tsx +++ b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/ManageContest.tsx @@ -23,7 +23,7 @@ const ManageContest = ({ contestAddress }: ManageContestProps) => { const [launchContestStep, setLaunchContestStep] = useState('DetailsForm'); const [createdContestAddress, setCreatedContestAddress] = useState(''); - const farcasterContestEnabled = useFlag('farcasterContest'); + const weightedTopicsEnabled = useFlag('weightedTopics'); const user = useUserStore(); @@ -46,7 +46,7 @@ const ManageContest = ({ contestAddress }: ManageContestProps) => { if ( !user.isLoggedIn || - (farcasterContestEnabled ? false : !stakeEnabled) || + (weightedTopicsEnabled ? false : !stakeEnabled) || !(Permissions.isSiteAdmin() || Permissions.isCommunityAdmin()) || contestNotFound ) { diff --git a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/DetailsFormStep.tsx b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/DetailsFormStep.tsx index 0784721da85..40e58df3b96 100644 --- a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/DetailsFormStep.tsx +++ b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/DetailsFormStep.tsx @@ -3,6 +3,7 @@ import { useSearchParams } from 'react-router-dom'; import { TopicWeightedVoting } from '@hicommonwealth/schemas'; import { notifyError } from 'controllers/app/notifications'; +import { useFlag } from 'hooks/useFlag'; import { useCommonNavigate } from 'navigation/helpers'; import app from 'state'; import useUpdateContestMutation from 'state/api/contests/updateContest'; @@ -64,8 +65,8 @@ const DetailsFormStep = ({ onSetContestFormData, }: DetailsFormStepProps) => { const navigate = useCommonNavigate(); - // const farcasterContestEnabled = useFlag('farcasterContest'); - const farcasterContestEnabled = true; + const farcasterContestEnabled = useFlag('farcasterContest'); + const weightedTopicsEnabled = useFlag('weightedTopics'); const [searchParams] = useSearchParams(); const contestType = searchParams.get('type'); const isFarcasterContest = contestType === ContestType.Farcaster; @@ -77,7 +78,7 @@ const DetailsFormStep = ({ ContestFormData['prizePercentage'] >(contestFormData?.prizePercentage || INITIAL_PERCENTAGE_VALUE); const [contestDuration, setContestDuration] = useState( - farcasterContestEnabled + weightedTopicsEnabled ? contestFormData?.contestDuration || initialContestDuration : undefined, ); @@ -185,7 +186,7 @@ const DetailsFormStep = ({ }; const handleSubmit = async (values: ContestFormValidationSubmitValues) => { - const topicsError = !farcasterContestEnabled && topicsEnabledError; + const topicsError = !weightedTopicsEnabled && topicsEnabledError; if (totalPayoutPercentageError || payoutRowError || topicsError) { return; @@ -271,7 +272,7 @@ const DetailsFormStep = ({ > {({ watch, setValue }) => ( <> - {farcasterContestEnabled && !isFarcasterContest && ( + {weightedTopicsEnabled && !isFarcasterContest && (
Choose a topic @@ -345,7 +346,7 @@ const DetailsFormStep = ({ - {farcasterContestEnabled ? ( + {weightedTopicsEnabled ? ( isFarcasterContest ? ( <>
@@ -686,7 +687,7 @@ const DetailsFormStep = ({
- {farcasterContestEnabled ? ( + {weightedTopicsEnabled ? ( <> ) : ( <> diff --git a/packages/commonwealth/client/scripts/views/pages/Contests/Contests.tsx b/packages/commonwealth/client/scripts/views/pages/Contests/Contests.tsx index e339173c1f9..728cd4d750f 100644 --- a/packages/commonwealth/client/scripts/views/pages/Contests/Contests.tsx +++ b/packages/commonwealth/client/scripts/views/pages/Contests/Contests.tsx @@ -11,7 +11,7 @@ import { useFlag } from 'hooks/useFlag'; import './Contests.scss'; const Contests = () => { - const farcasterContestEnabled = useFlag('farcasterContest'); + const weightedTopicsEnabled = useFlag('weightedTopics'); const { stakeEnabled, @@ -22,7 +22,7 @@ const Contests = () => { if ( !isContestDataLoading && - ((farcasterContestEnabled ? false : !stakeEnabled) || !isContestAvailable) + ((weightedTopicsEnabled ? false : !stakeEnabled) || !isContestAvailable) ) { return ; } diff --git a/packages/commonwealth/client/scripts/views/pages/CreateCommunity/useCreateCommunity.ts b/packages/commonwealth/client/scripts/views/pages/CreateCommunity/useCreateCommunity.ts index 7493ef7c1f0..8ff216ab2f8 100644 --- a/packages/commonwealth/client/scripts/views/pages/CreateCommunity/useCreateCommunity.ts +++ b/packages/commonwealth/client/scripts/views/pages/CreateCommunity/useCreateCommunity.ts @@ -13,7 +13,7 @@ const useCreateCommunity = () => { { type: null, chainBase: null }, ); - const weightedVotingEnabled = useFlag('farcasterContest'); + const weightedTopicsEnabled = useFlag('weightedTopics'); // @ts-expect-error StrictNullChecks const [selectedAddress, setSelectedAddress] = useState(null); @@ -49,7 +49,7 @@ const useCreateCommunity = () => { ); const showCommunityStakeStep = - !weightedVotingEnabled && + !weightedTopicsEnabled && isValidStepToShowCommunityStakeFormStep && isSupportedChainSelected; diff --git a/packages/commonwealth/client/scripts/views/pages/discussions/DiscussionsPage.tsx b/packages/commonwealth/client/scripts/views/pages/discussions/DiscussionsPage.tsx index 126aa48bf7b..18c17665678 100644 --- a/packages/commonwealth/client/scripts/views/pages/discussions/DiscussionsPage.tsx +++ b/packages/commonwealth/client/scripts/views/pages/discussions/DiscussionsPage.tsx @@ -57,7 +57,7 @@ const DiscussionsPage = ({ topicName }: DiscussionsPageProps) => { // @ts-expect-error const stageName: string = searchParams.get('stage'); - const weightedVotingEnabled = useFlag('farcasterContest'); + const weightedTopicsEnabled = useFlag('weightedTopics'); const featuredFilter: ThreadFeaturedFilterTypes = searchParams.get( 'featured', @@ -175,7 +175,7 @@ const DiscussionsPage = ({ topicName }: DiscussionsPageProps) => { useManageDocumentTitle('Discussions'); const isTopicWeighted = - weightedVotingEnabled && + weightedTopicsEnabled && topicId && topicObj.weightedVoting === TopicWeightedVoting.ERC20; diff --git a/packages/commonwealth/client/vite.config.ts b/packages/commonwealth/client/vite.config.ts index cfcc96e5945..3ba9d184d69 100644 --- a/packages/commonwealth/client/vite.config.ts +++ b/packages/commonwealth/client/vite.config.ts @@ -35,6 +35,9 @@ export default defineConfig(({ mode }) => { 'process.env.FLAG_NEW_EDITOR': JSON.stringify(env.FLAG_NEW_EDITOR), 'process.env.FLAG_CONTEST': JSON.stringify(env.FLAG_CONTEST), 'process.env.FLAG_CONTEST_DEV': JSON.stringify(env.FLAG_CONTEST_DEV), + 'process.env.FLAG_WEIGHTED_TOPICS': JSON.stringify( + env.FLAG_WEIGHTED_TOPICS, + ), 'process.env.FLAG_KNOCK_PUSH_NOTIFICATIONS_ENABLED': JSON.stringify( env.FLAG_KNOCK_PUSH_NOTIFICATIONS_ENABLED, ), From 68c5572498d2d32240ca41638884df6747da96c2 Mon Sep 17 00:00:00 2001 From: Kevin Burton Date: Wed, 9 Oct 2024 14:23:53 -0700 Subject: [PATCH 04/17] function to get the current subscription settings. --- .../api/trpc/subscription/useGetSubscriptionPreferences.ts | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/commonwealth/client/scripts/state/api/trpc/subscription/useGetSubscriptionPreferences.ts diff --git a/packages/commonwealth/client/scripts/state/api/trpc/subscription/useGetSubscriptionPreferences.ts b/packages/commonwealth/client/scripts/state/api/trpc/subscription/useGetSubscriptionPreferences.ts new file mode 100644 index 00000000000..c9490b9d2f6 --- /dev/null +++ b/packages/commonwealth/client/scripts/state/api/trpc/subscription/useGetSubscriptionPreferences.ts @@ -0,0 +1,5 @@ +import { trpc } from 'utils/trpcClient'; + +export function useGetSubscriptionPreferences() { + return trpc.subscription.getSubscriptionPreferences.useQuery({}); +} From acd3b7d0aaf02c3f0366f965c13131b04d6482d2 Mon Sep 17 00:00:00 2001 From: Kevin Burton Date: Wed, 9 Oct 2024 14:58:50 -0700 Subject: [PATCH 05/17] big refactor so I can use three individual toggles. --- ...ences.ts => useSubscriptionPreferences.ts} | 2 +- .../PushNotificationsToggle.tsx | 22 +++++--------- .../computeChannelTypeFromBrowserType.ts | 14 +++++++++ .../pages/NotificationSettings/index.tsx | 2 ++ .../usePushNotificationToggleCallback.ts | 25 ++++++++++++++++ ...terPushNotificationSubscriptionCallback.ts | 28 ++++++++++++++++++ ...terPushNotificationSubscriptionCallback.ts | 29 +++++++++++++++++++ 7 files changed, 106 insertions(+), 16 deletions(-) rename packages/commonwealth/client/scripts/state/api/trpc/subscription/{useGetSubscriptionPreferences.ts => useSubscriptionPreferences.ts} (69%) create mode 100644 packages/commonwealth/client/scripts/views/pages/NotificationSettings/computeChannelTypeFromBrowserType.ts create mode 100644 packages/commonwealth/client/scripts/views/pages/NotificationSettings/usePushNotificationToggleCallback.ts create mode 100644 packages/commonwealth/client/scripts/views/pages/NotificationSettings/useRegisterPushNotificationSubscriptionCallback.ts create mode 100644 packages/commonwealth/client/scripts/views/pages/NotificationSettings/useUnregisterPushNotificationSubscriptionCallback.ts diff --git a/packages/commonwealth/client/scripts/state/api/trpc/subscription/useGetSubscriptionPreferences.ts b/packages/commonwealth/client/scripts/state/api/trpc/subscription/useSubscriptionPreferences.ts similarity index 69% rename from packages/commonwealth/client/scripts/state/api/trpc/subscription/useGetSubscriptionPreferences.ts rename to packages/commonwealth/client/scripts/state/api/trpc/subscription/useSubscriptionPreferences.ts index c9490b9d2f6..3500e90df10 100644 --- a/packages/commonwealth/client/scripts/state/api/trpc/subscription/useGetSubscriptionPreferences.ts +++ b/packages/commonwealth/client/scripts/state/api/trpc/subscription/useSubscriptionPreferences.ts @@ -1,5 +1,5 @@ import { trpc } from 'utils/trpcClient'; -export function useGetSubscriptionPreferences() { +export function useSubscriptionPreferences() { return trpc.subscription.getSubscriptionPreferences.useQuery({}); } diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/PushNotificationsToggle.tsx b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/PushNotificationsToggle.tsx index a97947b6af8..2a1fea9db6c 100644 --- a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/PushNotificationsToggle.tsx +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/PushNotificationsToggle.tsx @@ -1,27 +1,15 @@ -import { BrowserType, getBrowserType } from 'helpers/browser'; +import { getBrowserType } from 'helpers/browser'; import React, { useCallback, useState } from 'react'; // eslint-disable-next-line max-len import { useRegisterClientRegistrationTokenMutation } from 'state/api/trpc/subscription/useRegisterClientRegistrationTokenMutation'; // eslint-disable-next-line max-len import { useUnregisterClientRegistrationTokenMutation } from 'state/api/trpc/subscription/useUnregisterClientRegistrationTokenMutation'; import { CWToggle } from 'views/components/component_kit/cw_toggle'; +import { computeChannelTypeFromBrowserType } from 'views/pages/NotificationSettings/computeChannelTypeFromBrowserType'; import { getFirebaseMessagingToken } from 'views/pages/NotificationSettings/getFirebaseMessagingToken'; const LOCAL_STORAGE_KEY = 'pushNotificationsEnabled'; -function computeChannelTypeFromBrowserType( - browserType: BrowserType | undefined, -): 'FCM' | 'APNS' | undefined { - switch (browserType) { - case 'safari': - return 'APNS'; - case 'chrome': - return 'FCM'; - } - - return undefined; -} - export const PushNotificationsToggle = () => { const registerClientRegistrationToken = useRegisterClientRegistrationTokenMutation(); @@ -30,7 +18,7 @@ export const PushNotificationsToggle = () => { useUnregisterClientRegistrationTokenMutation(); const browserType = getBrowserType(); - const channelType = computeChannelTypeFromBrowserType(browserType) ?? 'APNS'; //cannot be undefined + const channelType = computeChannelTypeFromBrowserType(browserType); const [checked, setChecked] = useState( () => localStorage.getItem(LOCAL_STORAGE_KEY) === 'on', @@ -43,6 +31,8 @@ export const PushNotificationsToggle = () => { const handleRegisterPushNotificationSubscription = useCallback(() => { async function doAsync() { + if (!channelType) return; + const permission = await Notification.requestPermission(); if (permission === 'granted') { console.log('Notification permission granted.'); @@ -60,6 +50,8 @@ export const PushNotificationsToggle = () => { const handleUnregisterPushNotificationSubscription = useCallback(() => { async function doAsync() { + if (!channelType) return; + const permission = await Notification.requestPermission(); if (permission === 'granted') { console.log('Notification permission granted.'); diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/computeChannelTypeFromBrowserType.ts b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/computeChannelTypeFromBrowserType.ts new file mode 100644 index 00000000000..c87a1b59c3e --- /dev/null +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/computeChannelTypeFromBrowserType.ts @@ -0,0 +1,14 @@ +import { BrowserType } from 'helpers/browser'; + +export function computeChannelTypeFromBrowserType( + browserType: BrowserType | undefined, +): 'FCM' | 'APNS' | undefined { + switch (browserType) { + case 'safari': + return 'APNS'; + case 'chrome': + return 'FCM'; + } + + return undefined; +} diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/index.tsx b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/index.tsx index 91486a7bf4c..db76e706eb3 100644 --- a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/index.tsx +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/index.tsx @@ -2,6 +2,7 @@ import { CommunityAlert } from '@hicommonwealth/schemas'; import { useFlag } from 'hooks/useFlag'; import React, { useState } from 'react'; import { useCommunityAlertsQuery } from 'state/api/trpc/subscription/useCommunityAlertsQuery'; +import { useSubscriptionPreferences } from 'state/api/trpc/subscription/useSubscriptionPreferences'; import useUserStore from 'state/ui/user'; import ScrollContainer from 'views/components/ScrollContainer'; import CWPageLayout from 'views/components/component_kit/new_designs/CWPageLayout'; @@ -33,6 +34,7 @@ const NotificationSettings = () => { const communityAlerts = useCommunityAlertsQuery({}); const enableKnockPushNotifications = useFlag('knockPushNotifications'); const user = useUserStore(); + const subscriptionPreference = useSubscriptionPreferences(); const communityAlertsIndex = createIndexForCommunityAlerts( communityAlerts.data || [], diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/usePushNotificationToggleCallback.ts b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/usePushNotificationToggleCallback.ts new file mode 100644 index 00000000000..cf751ac9acc --- /dev/null +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/usePushNotificationToggleCallback.ts @@ -0,0 +1,25 @@ +import { useCallback } from 'react'; +// eslint-disable-next-line max-len +import { useRegisterPushNotificationSubscriptionCallback } from 'views/pages/NotificationSettings/useRegisterPushNotificationSubscriptionCallback'; +import { useUnregisterPushNotificationSubscriptionCallback } from 'views/pages/NotificationSettings/useUnregisterPushNotificationSubscriptionCallback'; + +export function usePushNotificationToggleCallback() { + const registerCallback = useRegisterPushNotificationSubscriptionCallback(); + const unregisterCallback = + useUnregisterPushNotificationSubscriptionCallback(); + + return useCallback( + (active: boolean) => { + async function doAsync() { + if (active) { + await registerCallback(); + } else { + await unregisterCallback(); + } + } + + doAsync().catch(console.error); + }, + [registerCallback, unregisterCallback], + ); +} diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useRegisterPushNotificationSubscriptionCallback.ts b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useRegisterPushNotificationSubscriptionCallback.ts new file mode 100644 index 00000000000..3638f064e23 --- /dev/null +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useRegisterPushNotificationSubscriptionCallback.ts @@ -0,0 +1,28 @@ +import { getBrowserType } from 'helpers/browser'; +import { useCallback } from 'react'; +import { useRegisterClientRegistrationTokenMutation } from 'state/api/trpc/subscription/useRegisterClientRegistrationTokenMutation'; +import { computeChannelTypeFromBrowserType } from 'views/pages/NotificationSettings/computeChannelTypeFromBrowserType'; +import { getFirebaseMessagingToken } from 'views/pages/NotificationSettings/getFirebaseMessagingToken'; + +export function useRegisterPushNotificationSubscriptionCallback() { + const registerClientRegistrationToken = + useRegisterClientRegistrationTokenMutation(); + + return useCallback(async () => { + const browserType = getBrowserType(); + const channelType = computeChannelTypeFromBrowserType(browserType); + if (!channelType) return; + + const permission = await Notification.requestPermission(); + if (permission === 'granted') { + console.log('Notification permission granted.'); + const token = await getFirebaseMessagingToken(); + await registerClientRegistrationToken.mutateAsync({ + id: 0, // this should be the aggregate id (user?) + token, + channelType, + }); + console.log('Push notifications registered.'); + } + }, [registerClientRegistrationToken]); +} diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useUnregisterPushNotificationSubscriptionCallback.ts b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useUnregisterPushNotificationSubscriptionCallback.ts new file mode 100644 index 00000000000..d7ac06ad1df --- /dev/null +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useUnregisterPushNotificationSubscriptionCallback.ts @@ -0,0 +1,29 @@ +import { getBrowserType } from 'helpers/browser'; +import { useCallback } from 'react'; +import { useUnregisterClientRegistrationTokenMutation } from 'state/api/trpc/subscription/useUnregisterClientRegistrationTokenMutation'; +import { computeChannelTypeFromBrowserType } from 'views/pages/NotificationSettings/computeChannelTypeFromBrowserType'; +import { getFirebaseMessagingToken } from 'views/pages/NotificationSettings/getFirebaseMessagingToken'; + +export function useUnregisterPushNotificationSubscriptionCallback() { + const unregisterClientRegistrationToken = + useUnregisterClientRegistrationTokenMutation(); + + return useCallback(async () => { + const browserType = getBrowserType(); + const channelType = computeChannelTypeFromBrowserType(browserType); + + if (!channelType) return; + + const permission = await Notification.requestPermission(); + if (permission === 'granted') { + console.log('Notification permission granted.'); + const token = await getFirebaseMessagingToken(); + await unregisterClientRegistrationToken.mutateAsync({ + id: 0, // this should be the aggregate id (user?) + token, + channelType, + }); + console.log('Push notifications unregistered.'); + } + }, [unregisterClientRegistrationToken]); +} From 3d7ddc5b4475d25fbf52f58b23dd884728db6eff Mon Sep 17 00:00:00 2001 From: Kevin Burton Date: Wed, 9 Oct 2024 15:29:51 -0700 Subject: [PATCH 06/17] this code doesn't work and trying to track down why now. --- .../useSubscriptionPreferences.ts | 3 ++- .../pages/NotificationSettings/index.tsx | 3 ++- .../usePushNotificationToggleCallback.ts | 1 + ...seSubscriptionPreferenceSettingCallback.ts | 27 +++++++++++++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts diff --git a/packages/commonwealth/client/scripts/state/api/trpc/subscription/useSubscriptionPreferences.ts b/packages/commonwealth/client/scripts/state/api/trpc/subscription/useSubscriptionPreferences.ts index 3500e90df10..ff6dfad1e3e 100644 --- a/packages/commonwealth/client/scripts/state/api/trpc/subscription/useSubscriptionPreferences.ts +++ b/packages/commonwealth/client/scripts/state/api/trpc/subscription/useSubscriptionPreferences.ts @@ -1,5 +1,6 @@ import { trpc } from 'utils/trpcClient'; export function useSubscriptionPreferences() { - return trpc.subscription.getSubscriptionPreferences.useQuery({}); + const foo = trpc.subscription.getSubscriptionPreferences.useQuery({}); + return foo; } diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/index.tsx b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/index.tsx index db76e706eb3..0a00b68e118 100644 --- a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/index.tsx +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/index.tsx @@ -34,8 +34,9 @@ const NotificationSettings = () => { const communityAlerts = useCommunityAlertsQuery({}); const enableKnockPushNotifications = useFlag('knockPushNotifications'); const user = useUserStore(); - const subscriptionPreference = useSubscriptionPreferences(); + const subscriptionPreferences = useSubscriptionPreferences(); + // console.log("FIXME: ", JSON.stringify(subscriptionPreferences, null, 2)) const communityAlertsIndex = createIndexForCommunityAlerts( communityAlerts.data || [], ); diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/usePushNotificationToggleCallback.ts b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/usePushNotificationToggleCallback.ts index cf751ac9acc..c402514f6f8 100644 --- a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/usePushNotificationToggleCallback.ts +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/usePushNotificationToggleCallback.ts @@ -1,6 +1,7 @@ import { useCallback } from 'react'; // eslint-disable-next-line max-len import { useRegisterPushNotificationSubscriptionCallback } from 'views/pages/NotificationSettings/useRegisterPushNotificationSubscriptionCallback'; +// eslint-disable-next-line max-len import { useUnregisterPushNotificationSubscriptionCallback } from 'views/pages/NotificationSettings/useUnregisterPushNotificationSubscriptionCallback'; export function usePushNotificationToggleCallback() { diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts new file mode 100644 index 00000000000..d495f84de94 --- /dev/null +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts @@ -0,0 +1,27 @@ +/** + * Return a boolean indicating if we're active, and a callback to toggle + * the activation. + */ +type UseSubscriptionPreferenceSettingCallbackResult = Readonly< + [boolean, () => void] +>; +// +// type SubscriptionPreferenceType = typeof SubscriptionPreference; +// type PushNotificationType = Pick< +// SubscriptionPreferenceType, +// | 'mobile_push_notifications_enabled' +// | 'mobile_push_discussion_activity_enabled' +// | 'mobile_push_admin_alerts_enabled' +// >; + +// export function useSubscriptionPreferenceSettingCallback( +// pref: keyof PushNotificationType, +// ): UseSubscriptionPreferenceSettingCallbackResult { +// const subscriptionPreferences = useSubscriptionPreferences(); +// +// const toggle = () => { +// console.log('FIXME toggle'); +// }; +// +// return subscriptionPreferences.data ? [subscriptionPreferences.data[pref] : false, toggle]; +// } From 6f2c556ba16e92e153a0459ae944e1b5d3d4a636 Mon Sep 17 00:00:00 2001 From: israellund Date: Wed, 9 Oct 2024 18:36:51 -0400 Subject: [PATCH 07/17] updated 404/PageNotFound --- .../client/scripts/views/pages/404.scss | 6 +++ .../client/scripts/views/pages/404.tsx | 41 +++++++++++++++---- 2 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 packages/commonwealth/client/scripts/views/pages/404.scss diff --git a/packages/commonwealth/client/scripts/views/pages/404.scss b/packages/commonwealth/client/scripts/views/pages/404.scss new file mode 100644 index 00000000000..29d692239b0 --- /dev/null +++ b/packages/commonwealth/client/scripts/views/pages/404.scss @@ -0,0 +1,6 @@ +.PageNotFound { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} diff --git a/packages/commonwealth/client/scripts/views/pages/404.tsx b/packages/commonwealth/client/scripts/views/pages/404.tsx index b1ab5d4cfeb..85a513ddee6 100644 --- a/packages/commonwealth/client/scripts/views/pages/404.tsx +++ b/packages/commonwealth/client/scripts/views/pages/404.tsx @@ -1,21 +1,44 @@ import React from 'react'; +import useUserStore from 'state/ui/user'; +import { useAuthModalStore } from '../../state/ui/modals'; import { CWEmptyState } from '../components/component_kit/cw_empty_state'; +import { CWButton } from '../components/component_kit/new_designs/CWButton'; +import { AuthModal, AuthModalType } from '../modals/AuthModal'; +import './404.scss'; type PageNotFoundProps = { title?: string; message?: string }; export const PageNotFound = (props: PageNotFoundProps) => { const { message } = props; + const user = useUserStore(); + + const { authModalType, setAuthModalType } = useAuthModalStore(); + return ( - + + } + /> + {!user.isLoggedIn && ( + setAuthModalType(AuthModalType.SignIn)} + /> + )} + setAuthModalType(undefined)} + isOpen={!!authModalType} + /> +
); }; From 4653833f68091ca1ffb4ceaffd8e3d07853d9100 Mon Sep 17 00:00:00 2001 From: Kevin Burton Date: Wed, 9 Oct 2024 16:51:14 -0700 Subject: [PATCH 08/17] all the wiring is now setup for the push notifications. --- .../useSubscriptionPreferences.ts | 3 +- .../PushNotificationsToggle.tsx | 102 +++--------------- .../pages/NotificationSettings/index.tsx | 2 +- .../usePushNotificationActivated.ts | 18 ++++ .../usePushNotificationToggleCallback.ts | 14 +-- ...seSubscriptionPreferenceSettingCallback.ts | 89 +++++++++++---- 6 files changed, 107 insertions(+), 121 deletions(-) create mode 100644 packages/commonwealth/client/scripts/views/pages/NotificationSettings/usePushNotificationActivated.ts diff --git a/packages/commonwealth/client/scripts/state/api/trpc/subscription/useSubscriptionPreferences.ts b/packages/commonwealth/client/scripts/state/api/trpc/subscription/useSubscriptionPreferences.ts index ff6dfad1e3e..3500e90df10 100644 --- a/packages/commonwealth/client/scripts/state/api/trpc/subscription/useSubscriptionPreferences.ts +++ b/packages/commonwealth/client/scripts/state/api/trpc/subscription/useSubscriptionPreferences.ts @@ -1,6 +1,5 @@ import { trpc } from 'utils/trpcClient'; export function useSubscriptionPreferences() { - const foo = trpc.subscription.getSubscriptionPreferences.useQuery({}); - return foo; + return trpc.subscription.getSubscriptionPreferences.useQuery({}); } diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/PushNotificationsToggle.tsx b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/PushNotificationsToggle.tsx index 2a1fea9db6c..2c40eecdd96 100644 --- a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/PushNotificationsToggle.tsx +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/PushNotificationsToggle.tsx @@ -1,95 +1,21 @@ -import { getBrowserType } from 'helpers/browser'; -import React, { useCallback, useState } from 'react'; +import React from 'react'; // eslint-disable-next-line max-len -import { useRegisterClientRegistrationTokenMutation } from 'state/api/trpc/subscription/useRegisterClientRegistrationTokenMutation'; -// eslint-disable-next-line max-len -import { useUnregisterClientRegistrationTokenMutation } from 'state/api/trpc/subscription/useUnregisterClientRegistrationTokenMutation'; import { CWToggle } from 'views/components/component_kit/cw_toggle'; -import { computeChannelTypeFromBrowserType } from 'views/pages/NotificationSettings/computeChannelTypeFromBrowserType'; -import { getFirebaseMessagingToken } from 'views/pages/NotificationSettings/getFirebaseMessagingToken'; - -const LOCAL_STORAGE_KEY = 'pushNotificationsEnabled'; - -export const PushNotificationsToggle = () => { - const registerClientRegistrationToken = - useRegisterClientRegistrationTokenMutation(); - - const unregisterClientRegistrationToken = - useUnregisterClientRegistrationTokenMutation(); - - const browserType = getBrowserType(); - const channelType = computeChannelTypeFromBrowserType(browserType); - - const [checked, setChecked] = useState( - () => localStorage.getItem(LOCAL_STORAGE_KEY) === 'on', - ); - - const handleButtonToggle = useCallback((newValue: boolean) => { - localStorage.setItem(LOCAL_STORAGE_KEY, newValue ? 'on' : 'off'); - setChecked(newValue); - }, []); - - const handleRegisterPushNotificationSubscription = useCallback(() => { - async function doAsync() { - if (!channelType) return; - - const permission = await Notification.requestPermission(); - if (permission === 'granted') { - console.log('Notification permission granted.'); - const token = await getFirebaseMessagingToken(); - await registerClientRegistrationToken.mutateAsync({ - id: 0, // this should be the aggregate id (user?) - token, - channelType, - }); - } - } - - doAsync().catch(console.error); - }, [channelType, registerClientRegistrationToken]); - - const handleUnregisterPushNotificationSubscription = useCallback(() => { - async function doAsync() { - if (!channelType) return; - - const permission = await Notification.requestPermission(); - if (permission === 'granted') { - console.log('Notification permission granted.'); - const token = await getFirebaseMessagingToken(); - await unregisterClientRegistrationToken.mutateAsync({ - id: 0, // this should be the aggregate id (user?) - token, - channelType, - }); - } - } +import { + SubscriptionPrefType, + useSubscriptionPreferenceSettingCallback, +} from 'views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback'; - doAsync().catch(console.error); - }, [channelType, unregisterClientRegistrationToken]); +interface PushNotificationsToggleProps { + readonly pref: SubscriptionPrefType; +} - const handleRegistration = useCallback( - (newValue: boolean) => { - if (newValue) { - handleRegisterPushNotificationSubscription(); - } else { - handleUnregisterPushNotificationSubscription(); - } - }, - [ - handleRegisterPushNotificationSubscription, - handleUnregisterPushNotificationSubscription, - ], - ); +export const PushNotificationsToggle = ( + props: PushNotificationsToggleProps, +) => { + const { pref } = props; - const handleChecked = useCallback( - (newValue: boolean) => { - handleButtonToggle(newValue); - handleRegistration(newValue); - }, - [handleButtonToggle, handleRegistration], - ); + const [checked, activate] = useSubscriptionPreferenceSettingCallback(pref); - return ( - handleChecked(!checked)} /> - ); + return activate(!checked)} />; }; diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/index.tsx b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/index.tsx index 0a00b68e118..9210ffbe114 100644 --- a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/index.tsx +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/index.tsx @@ -130,7 +130,7 @@ const NotificationSettings = () => {
- +
diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/usePushNotificationActivated.ts b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/usePushNotificationActivated.ts new file mode 100644 index 00000000000..f75b6708762 --- /dev/null +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/usePushNotificationActivated.ts @@ -0,0 +1,18 @@ +import { useCallback, useState } from 'react'; + +const LOCAL_STORAGE_KEY = 'pushNotificationsEnabled'; + +export function usePushNotificationActivated(): Readonly< + [boolean, (newValue: boolean) => void] +> { + const [checked, setChecked] = useState( + () => localStorage.getItem(LOCAL_STORAGE_KEY) === 'on', + ); + + const toggle = useCallback((newValue: boolean) => { + localStorage.setItem(LOCAL_STORAGE_KEY, newValue ? 'on' : 'off'); + setChecked(newValue); + }, []); + + return [checked, toggle]; +} diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/usePushNotificationToggleCallback.ts b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/usePushNotificationToggleCallback.ts index c402514f6f8..bfbd1a0b606 100644 --- a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/usePushNotificationToggleCallback.ts +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/usePushNotificationToggleCallback.ts @@ -10,16 +10,12 @@ export function usePushNotificationToggleCallback() { useUnregisterPushNotificationSubscriptionCallback(); return useCallback( - (active: boolean) => { - async function doAsync() { - if (active) { - await registerCallback(); - } else { - await unregisterCallback(); - } + async (active: boolean) => { + if (active) { + await registerCallback(); + } else { + await unregisterCallback(); } - - doAsync().catch(console.error); }, [registerCallback, unregisterCallback], ); diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts index d495f84de94..3556698ad59 100644 --- a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts @@ -1,27 +1,74 @@ +import { useCallback } from 'react'; +import { useSubscriptionPreferences } from 'state/api/trpc/subscription/useSubscriptionPreferences'; +import { useUpdateSubscriptionPreferencesMutation } from 'state/api/trpc/subscription/useUpdateSubscriptionPreferencesMutation'; +import useUserStore from 'state/ui/user'; +import { usePushNotificationActivated } from 'views/pages/NotificationSettings/usePushNotificationActivated'; +import { usePushNotificationToggleCallback } from 'views/pages/NotificationSettings/usePushNotificationToggleCallback'; + /** * Return a boolean indicating if we're active, and a callback to toggle * the activation. */ type UseSubscriptionPreferenceSettingCallbackResult = Readonly< - [boolean, () => void] + [boolean, (activate: boolean) => void] >; -// -// type SubscriptionPreferenceType = typeof SubscriptionPreference; -// type PushNotificationType = Pick< -// SubscriptionPreferenceType, -// | 'mobile_push_notifications_enabled' -// | 'mobile_push_discussion_activity_enabled' -// | 'mobile_push_admin_alerts_enabled' -// >; - -// export function useSubscriptionPreferenceSettingCallback( -// pref: keyof PushNotificationType, -// ): UseSubscriptionPreferenceSettingCallbackResult { -// const subscriptionPreferences = useSubscriptionPreferences(); -// -// const toggle = () => { -// console.log('FIXME toggle'); -// }; -// -// return subscriptionPreferences.data ? [subscriptionPreferences.data[pref] : false, toggle]; -// } + +export type SubscriptionPrefType = + | 'mobile_push_notifications_enabled' + | 'mobile_push_discussion_activity_enabled' + | 'mobile_push_admin_alerts_enabled'; + +export function useSubscriptionPreferenceSettingCallback( + pref: SubscriptionPrefType, +): UseSubscriptionPreferenceSettingCallbackResult { + const subscriptionPreferences = useSubscriptionPreferences(); + const { mutateAsync: updateSubscriptionPreferences } = + useUpdateSubscriptionPreferencesMutation(); + const pushNotificationToggleCallback = usePushNotificationToggleCallback(); + const [pushNotificationActivated, togglePushNotificationActivated] = + usePushNotificationActivated(); + const user = useUserStore(); + + const toggle = useCallback( + (activate: boolean) => { + async function doAsync() { + await pushNotificationToggleCallback(activate); + + await updateSubscriptionPreferences({ + id: user.id, + ...subscriptionPreferences.data, + [pref]: activate, + }); + togglePushNotificationActivated(true); + } + + doAsync().catch(console.error); + }, + [ + pref, + pushNotificationToggleCallback, + subscriptionPreferences.data, + togglePushNotificationActivated, + updateSubscriptionPreferences, + user.id, + ], + ); + + function computeSubscriptionPreferenceActivated() { + if (subscriptionPreferences.data) { + // NOTE: trpc types are mangled again for some reason. I'm not sure why + // because useSubscriptionPreferences looks just like other hooks we've + // been using. I verified this typing is correct manually. We will have + // to look into why tRPC keeps mangling our types. + return subscriptionPreferences.data[pref] as boolean; + } else { + return false; + } + } + + return [ + // the local device has to be and the feature toggle has to be on. + pushNotificationActivated && computeSubscriptionPreferenceActivated(), + toggle, + ]; +} From 24987094382ea237614974109e8dc710f696b12f Mon Sep 17 00:00:00 2001 From: Kevin Burton Date: Wed, 9 Oct 2024 17:15:02 -0700 Subject: [PATCH 09/17] this PR should be ready... --- .../pages/NotificationSettings/index.scss | 3 +- .../pages/NotificationSettings/index.tsx | 40 +++++++++++++++---- ...seSubscriptionPreferenceSettingCallback.ts | 18 +++++++-- 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/index.scss b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/index.scss index fa8d4750205..65aff5f9e9d 100644 --- a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/index.scss +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/index.scss @@ -5,7 +5,8 @@ .setting-container { display: flex; - margin-block: 8px; + margin-top: 16px; + margin-bottom: 32px; } .setting-container-right { diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/index.tsx b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/index.tsx index 9210ffbe114..f8311767de6 100644 --- a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/index.tsx +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/index.tsx @@ -2,7 +2,6 @@ import { CommunityAlert } from '@hicommonwealth/schemas'; import { useFlag } from 'hooks/useFlag'; import React, { useState } from 'react'; import { useCommunityAlertsQuery } from 'state/api/trpc/subscription/useCommunityAlertsQuery'; -import { useSubscriptionPreferences } from 'state/api/trpc/subscription/useSubscriptionPreferences'; import useUserStore from 'state/ui/user'; import ScrollContainer from 'views/components/ScrollContainer'; import CWPageLayout from 'views/components/component_kit/new_designs/CWPageLayout'; @@ -34,9 +33,7 @@ const NotificationSettings = () => { const communityAlerts = useCommunityAlertsQuery({}); const enableKnockPushNotifications = useFlag('knockPushNotifications'); const user = useUserStore(); - const subscriptionPreferences = useSubscriptionPreferences(); - // console.log("FIXME: ", JSON.stringify(subscriptionPreferences, null, 2)) const communityAlertsIndex = createIndexForCommunityAlerts( communityAlerts.data || [], ); @@ -118,14 +115,13 @@ const NotificationSettings = () => { <> {enableKnockPushNotifications && supportsPushNotifications && (
- - Push Notifications - -
+ Turn push notifications on/off + - Turn on notifications to receive alerts on your device. + Turn off notifications to stop receiving any alerts on + your device.
@@ -133,6 +129,34 @@ const NotificationSettings = () => {
+ +
+
+ Discussion Activity + + + Get notified when someone mentions you + +
+ +
+ +
+
+ +
+
+ Admin Alerts + + + Notifications for communities you are an admin for + +
+ +
+ +
+
)} diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts index 3556698ad59..74ac60f6abc 100644 --- a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts @@ -32,14 +32,26 @@ export function useSubscriptionPreferenceSettingCallback( const toggle = useCallback( (activate: boolean) => { async function doAsync() { - await pushNotificationToggleCallback(activate); - + // ** first we set the subscription preference await updateSubscriptionPreferences({ id: user.id, ...subscriptionPreferences.data, [pref]: activate, }); - togglePushNotificationActivated(true); + + //** now we have to determine how to set push notifications. + + const pushNotificationsActive = + subscriptionPreferences.data['mobile_push_notifications_enabled'] || + subscriptionPreferences.data[ + 'mobile_push_discussion_activity_enabled' + ] || + subscriptionPreferences.data['mobile_push_admin_alerts_enabled']; + + await pushNotificationToggleCallback(pushNotificationsActive); + + togglePushNotificationActivated(pushNotificationsActive); + await subscriptionPreferences.refetch(); } doAsync().catch(console.error); From c3283b8912e91a8723f219fa2a53e49cee308d00 Mon Sep 17 00:00:00 2001 From: Kevin Burton Date: Wed, 9 Oct 2024 17:23:40 -0700 Subject: [PATCH 10/17] max-len fixes. --- .../useRegisterPushNotificationSubscriptionCallback.ts | 3 +++ .../useSubscriptionPreferenceSettingCallback.ts | 6 +++--- .../useUnregisterPushNotificationSubscriptionCallback.ts | 3 +++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useRegisterPushNotificationSubscriptionCallback.ts b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useRegisterPushNotificationSubscriptionCallback.ts index 3638f064e23..a618c5ce5cc 100644 --- a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useRegisterPushNotificationSubscriptionCallback.ts +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useRegisterPushNotificationSubscriptionCallback.ts @@ -1,7 +1,10 @@ import { getBrowserType } from 'helpers/browser'; import { useCallback } from 'react'; +// eslint-disable-next-line max-len import { useRegisterClientRegistrationTokenMutation } from 'state/api/trpc/subscription/useRegisterClientRegistrationTokenMutation'; +// eslint-disable-next-line max-len import { computeChannelTypeFromBrowserType } from 'views/pages/NotificationSettings/computeChannelTypeFromBrowserType'; +// eslint-disable-next-line max-len import { getFirebaseMessagingToken } from 'views/pages/NotificationSettings/getFirebaseMessagingToken'; export function useRegisterPushNotificationSubscriptionCallback() { diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts index 74ac60f6abc..545e69389db 100644 --- a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts @@ -42,11 +42,11 @@ export function useSubscriptionPreferenceSettingCallback( //** now we have to determine how to set push notifications. const pushNotificationsActive = - subscriptionPreferences.data['mobile_push_notifications_enabled'] || - subscriptionPreferences.data[ + subscriptionPreferences.data?.['mobile_push_notifications_enabled'] || + subscriptionPreferences.data?.[ 'mobile_push_discussion_activity_enabled' ] || - subscriptionPreferences.data['mobile_push_admin_alerts_enabled']; + subscriptionPreferences.data?.['mobile_push_admin_alerts_enabled']; await pushNotificationToggleCallback(pushNotificationsActive); diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useUnregisterPushNotificationSubscriptionCallback.ts b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useUnregisterPushNotificationSubscriptionCallback.ts index d7ac06ad1df..b43b2485f46 100644 --- a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useUnregisterPushNotificationSubscriptionCallback.ts +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useUnregisterPushNotificationSubscriptionCallback.ts @@ -1,7 +1,10 @@ import { getBrowserType } from 'helpers/browser'; import { useCallback } from 'react'; +// eslint-disable-next-line max-len import { useUnregisterClientRegistrationTokenMutation } from 'state/api/trpc/subscription/useUnregisterClientRegistrationTokenMutation'; +// eslint-disable-next-line max-len import { computeChannelTypeFromBrowserType } from 'views/pages/NotificationSettings/computeChannelTypeFromBrowserType'; +// eslint-disable-next-line max-len import { getFirebaseMessagingToken } from 'views/pages/NotificationSettings/getFirebaseMessagingToken'; export function useUnregisterPushNotificationSubscriptionCallback() { From 246d08848050ca9b1218bb28338ff9a01e5583cb Mon Sep 17 00:00:00 2001 From: Kevin Burton Date: Wed, 9 Oct 2024 17:25:38 -0700 Subject: [PATCH 11/17] max-len fixes. --- .../useSubscriptionPreferenceSettingCallback.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts index 545e69389db..236947b59f8 100644 --- a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts @@ -1,8 +1,12 @@ import { useCallback } from 'react'; +// eslint-disable-next-line max-len import { useSubscriptionPreferences } from 'state/api/trpc/subscription/useSubscriptionPreferences'; +// eslint-disable-next-line max-len import { useUpdateSubscriptionPreferencesMutation } from 'state/api/trpc/subscription/useUpdateSubscriptionPreferencesMutation'; -import useUserStore from 'state/ui/user'; +// eslint-disable-next-line max-len import { usePushNotificationActivated } from 'views/pages/NotificationSettings/usePushNotificationActivated'; +// eslint-disable-next-line max-len +import useUserStore from 'state/ui/user'; import { usePushNotificationToggleCallback } from 'views/pages/NotificationSettings/usePushNotificationToggleCallback'; /** From 34519f3431febfe9f02c3594744c5a73a24a491c Mon Sep 17 00:00:00 2001 From: Kevin Burton Date: Wed, 9 Oct 2024 17:27:53 -0700 Subject: [PATCH 12/17] hook fixes. --- .../useSubscriptionPreferenceSettingCallback.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts index 236947b59f8..fa9124f9f70 100644 --- a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useSubscriptionPreferenceSettingCallback.ts @@ -63,7 +63,7 @@ export function useSubscriptionPreferenceSettingCallback( [ pref, pushNotificationToggleCallback, - subscriptionPreferences.data, + subscriptionPreferences, togglePushNotificationActivated, updateSubscriptionPreferences, user.id, From 9737b7091250bb8bef066d12493a42c33e11a989 Mon Sep 17 00:00:00 2001 From: Marcin Date: Thu, 10 Oct 2024 13:35:18 +0200 Subject: [PATCH 13/17] Fixed creating contest without the flag --- .../Contests/ContestsList/ContestCard/ContestCard.tsx | 2 +- .../steps/DetailsFormStep/DetailsFormStep.tsx | 7 ++++++- .../ManageContest/steps/DetailsFormStep/validation.ts | 8 +++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ContestsList/ContestCard/ContestCard.tsx b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ContestsList/ContestCard/ContestCard.tsx index 81ddeac7922..c854e1ddde5 100644 --- a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ContestsList/ContestCard/ContestCard.tsx +++ b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ContestsList/ContestCard/ContestCard.tsx @@ -192,7 +192,7 @@ const ContestCard = ({ )} - Topics: {topics.map(({ name: topicName }) => topicName).join(', ')} + Topic: {topics.map(({ name: topicName }) => topicName).join(', ')} <> diff --git a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/DetailsFormStep.tsx b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/DetailsFormStep.tsx index 40e58df3b96..3c23ca90c17 100644 --- a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/DetailsFormStep.tsx +++ b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/DetailsFormStep.tsx @@ -269,6 +269,7 @@ const DetailsFormStep = ({ validationSchema={detailsFormValidationSchema} onSubmit={handleSubmit} initialValues={getInitialValues()} + onErrors={console.error} > {({ watch, setValue }) => ( <> @@ -533,7 +534,11 @@ const DetailsFormStep = ({ debouncedTokenValue={debouncedTokenValue} tokenMetadataLoading={tokenMetadataLoading} tokenMetadata={tokenMetadata} - tokenValue={tokenValue} + tokenValue={ + editMode + ? contestFormData?.fundingTokenAddress || '' + : tokenValue + } setTokenValue={setTokenValue} tokenError={getTokenError()} containerClassName="funding-token-address-input" diff --git a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/validation.ts b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/validation.ts index 3a9910fe891..a0b494a21cb 100644 --- a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/validation.ts +++ b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/validation.ts @@ -1,7 +1,11 @@ +import { OpenFeature } from '@openfeature/web-sdk'; import { VALIDATION_MESSAGES } from 'helpers/formValidations/messages'; import { ContestFeeType } from 'views/pages/CommunityManagement/Contests/ManageContest/types'; import z from 'zod'; +const client = OpenFeature.getClient(); +const weightedTopicsEnabled = client.getBooleanValue('weightedTopics', false); + export const detailsFormValidationSchema = z.object({ contestName: z .string() @@ -15,7 +19,9 @@ export const detailsFormValidationSchema = z.object({ label: z.string(), }) .optional() - .refine((value) => !!value, { message: 'You must select a topic' }), + .refine((value) => (weightedTopicsEnabled ? !!value : true), { + message: 'You must select a topic', + }), feeType: z.enum([ ContestFeeType.CommunityStake, ContestFeeType.DirectDeposit, From 0fc533469c9da38a67c913e0c597a885610932d0 Mon Sep 17 00:00:00 2001 From: Marcin Date: Thu, 10 Oct 2024 15:58:04 +0200 Subject: [PATCH 14/17] Fixing default values --- .../AdminContestsPage/AdminContestsPage.tsx | 2 - .../Contests/ManageContest/ManageContest.tsx | 2 +- .../steps/DetailsFormStep/DetailsFormStep.tsx | 38 +++++++++++++++---- .../steps/DetailsFormStep/validation.ts | 5 ++- .../SignTransactionsStep.tsx | 20 +++++++--- .../ManageContest/useManageContestForm.ts | 13 +++++++ 6 files changed, 63 insertions(+), 17 deletions(-) diff --git a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/AdminContestsPage/AdminContestsPage.tsx b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/AdminContestsPage/AdminContestsPage.tsx index 63afa96e52a..787ca182d90 100644 --- a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/AdminContestsPage/AdminContestsPage.tsx +++ b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/AdminContestsPage/AdminContestsPage.tsx @@ -170,5 +170,3 @@ const AdminContestsPage = () => { }; export default AdminContestsPage; - -// fix breadcrumbs diff --git a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/ManageContest.tsx b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/ManageContest.tsx index 5c7bbbedcf2..98d2a6e8e16 100644 --- a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/ManageContest.tsx +++ b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/ManageContest.tsx @@ -83,7 +83,7 @@ const ManageContest = ({ contestAddress }: ManageContestProps) => { return ( diff --git a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/DetailsFormStep.tsx b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/DetailsFormStep.tsx index 3c23ca90c17..df11a0a264e 100644 --- a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/DetailsFormStep.tsx +++ b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/DetailsFormStep.tsx @@ -135,7 +135,9 @@ const DetailsFormStep = ({ const getInitialValues = () => { return { contestName: contestFormData?.contestName, - contestTopic: contestFormData?.contestTopic, + contestTopic: weightedTopics.find( + (t) => t.value === contestFormData?.contestTopic?.value, + ), contestDescription: contestFormData?.contestDescription, contestImage: contestFormData?.contestImage, feeType: @@ -192,13 +194,30 @@ const DetailsFormStep = ({ return; } + const selectedTopic = (weightedTopics || []).find( + (t) => t.value === values?.contestTopic?.value, + ); + + const feeType = weightedTopicsEnabled + ? selectedTopic?.weightedVoting === TopicWeightedVoting.ERC20 + ? ContestFeeType.DirectDeposit + : ContestFeeType.CommunityStake + : values.feeType; + + const contestRecurring = weightedTopicsEnabled + ? selectedTopic?.weightedVoting === TopicWeightedVoting.ERC20 + ? ContestRecurringType.No + : ContestRecurringType.Yes + : values.contestRecurring; + const formData: ContestFormData = { contestName: values.contestName, contestDescription: values.contestDescription, contestImage: values.contestImage, - feeType: values.feeType, fundingTokenAddress: values.fundingTokenAddress, - contestRecurring: values.contestRecurring, + contestTopic: selectedTopic, + contestRecurring, + feeType, prizePercentage, payoutStructure, toggledTopicList, @@ -289,6 +308,7 @@ const DetailsFormStep = ({ isClearable={false} isSearchable={false} options={weightedTopics} + isDisabled={editMode} /> )} @@ -385,13 +405,15 @@ const DetailsFormStep = ({ onChange={(newValue) => { setContestDuration(newValue?.value); }} + isDisabled={editMode} /> ) : ( <> - {watch('contestTopic')?.weightedVoting === - TopicWeightedVoting.ERC20 ? ( + {weightedTopics.find( + (t) => t.value === watch('contestTopic')?.value, + )?.weightedVoting === TopicWeightedVoting.ERC20 ? ( <>
Contest Funding @@ -419,8 +441,9 @@ const DetailsFormStep = ({ disabled={editMode} /> - ) : watch('contestTopic')?.weightedVoting === - TopicWeightedVoting.Stake ? ( + ) : weightedTopics.find( + (t) => t.value === watch('contestTopic')?.value, + )?.weightedVoting === TopicWeightedVoting.Stake ? ( <>
Contest Funding @@ -483,6 +506,7 @@ const DetailsFormStep = ({ onChange={(newValue) => { setContestDuration(newValue?.value); }} + isDisabled={editMode} />
diff --git a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/validation.ts b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/validation.ts index a0b494a21cb..3372409a650 100644 --- a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/validation.ts +++ b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/DetailsFormStep/validation.ts @@ -1,3 +1,4 @@ +import { TopicWeightedVoting } from '@hicommonwealth/schemas'; import { OpenFeature } from '@openfeature/web-sdk'; import { VALIDATION_MESSAGES } from 'helpers/formValidations/messages'; import { ContestFeeType } from 'views/pages/CommunityManagement/Contests/ManageContest/types'; @@ -15,8 +16,10 @@ export const detailsFormValidationSchema = z.object({ contestImage: z.string().optional(), contestTopic: z .object({ - value: z.number(), + value: z.number().optional(), label: z.string(), + helpText: z.string().optional(), + weightedVoting: z.nativeEnum(TopicWeightedVoting).optional().nullish(), }) .optional() .refine((value) => (weightedTopicsEnabled ? !!value : true), { diff --git a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/SignTransactionsStep/SignTransactionsStep.tsx b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/SignTransactionsStep/SignTransactionsStep.tsx index 0f3cf0f46d8..5be63af9567 100644 --- a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/SignTransactionsStep/SignTransactionsStep.tsx +++ b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/steps/SignTransactionsStep/SignTransactionsStep.tsx @@ -49,6 +49,8 @@ const SignTransactionsStep = ({ onSetCreatedContestAddress, fundingTokenTicker, }: SignTransactionsStepProps) => { + const weightedTopicsEnabled = useFlag('weightedTopics'); + const [launchContestData, setLaunchContestData] = useState({ state: 'not-started' as ActionStepProps['state'], errorText: '', @@ -81,14 +83,18 @@ const SignTransactionsStep = ({ const namespaceName = app?.chain?.meta?.namespace; const contestLength = devContest ? ONE_HOUR_IN_SECONDS - : SEVEN_DAYS_IN_SECONDS; + : weightedTopicsEnabled + ? contestFormData?.contestDuration + : SEVEN_DAYS_IN_SECONDS; const stakeId = stakeData?.stake_id; const voterShare = commonProtocol.CONTEST_VOTER_SHARE; const feeShare = commonProtocol.CONTEST_FEE_SHARE; const weight = stakeData?.vote_weight; const contestInterval = devContest ? ONE_HOUR_IN_SECONDS - : SEVEN_DAYS_IN_SECONDS; + : weightedTopicsEnabled + ? contestFormData?.contestDuration + : SEVEN_DAYS_IN_SECONDS; const prizeShare = contestFormData?.prizePercentage; const walletAddress = user.activeAccount?.address; const exchangeToken = isDirectDepositSelected @@ -149,10 +155,12 @@ const SignTransactionsStep = ({ ? contestFormData?.prizePercentage : 0, payout_structure: contestFormData?.payoutStructure, - interval: isContestRecurring ? contestInterval : 0, - topic_ids: contestFormData?.toggledTopicList - .filter((t) => t.checked) - .map((t) => t.id!), + interval: isContestRecurring ? contestInterval! : 0, + topic_ids: weightedTopicsEnabled + ? [contestFormData?.contestTopic?.value as number] + : contestFormData?.toggledTopicList + .filter((t) => t.checked) + .map((t) => t.id!), ticker: fundingTokenTicker, }); diff --git a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/useManageContestForm.ts b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/useManageContestForm.ts index 267356b3c92..a49fd15e783 100644 --- a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/useManageContestForm.ts +++ b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Contests/ManageContest/useManageContestForm.ts @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react'; +import moment from 'moment'; import useCommunityContests from 'views/pages/CommunityManagement/Contests/useCommunityContests'; import { ContestFeeType, ContestFormData, ContestRecurringType } from './types'; @@ -26,9 +27,21 @@ const useManageContestForm = ({ return; } + const contestLengthInSeconds = moment( + contestData?.contests?.[0]?.end_time, + ).diff(contestData?.contests?.[0]?.start_time, 'seconds'); + setContestFormData({ contestName: contestData.name, contestImage: contestData.image_url!, + contestTopic: { + value: contestData.topics[0]?.id, + label: contestData.topics[0]?.name, + }, + contestDuration: + contestData.interval === 0 + ? contestLengthInSeconds + : contestData.interval, feeType: contestData.funding_token_address ? ContestFeeType.DirectDeposit : ContestFeeType.CommunityStake, From dc40449527a40c6b9ed6229a071dc41551ad358b Mon Sep 17 00:00:00 2001 From: Kevin Burton Date: Thu, 10 Oct 2024 09:07:15 -0700 Subject: [PATCH 15/17] fixes for malik --- .../computeChannelTypeFromBrowserType.ts | 4 ++++ .../useRegisterPushNotificationSubscriptionCallback.ts | 6 ++++-- .../useUnregisterPushNotificationSubscriptionCallback.ts | 6 ++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/computeChannelTypeFromBrowserType.ts b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/computeChannelTypeFromBrowserType.ts index c87a1b59c3e..e3b43e559ae 100644 --- a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/computeChannelTypeFromBrowserType.ts +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/computeChannelTypeFromBrowserType.ts @@ -1,5 +1,9 @@ import { BrowserType } from 'helpers/browser'; +/** + * Compute the channel for Knock notifications. Firebase cloud messaging or + * Apple. + */ export function computeChannelTypeFromBrowserType( browserType: BrowserType | undefined, ): 'FCM' | 'APNS' | undefined { diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useRegisterPushNotificationSubscriptionCallback.ts b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useRegisterPushNotificationSubscriptionCallback.ts index a618c5ce5cc..2e29b4dbcd5 100644 --- a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useRegisterPushNotificationSubscriptionCallback.ts +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useRegisterPushNotificationSubscriptionCallback.ts @@ -5,11 +5,13 @@ import { useRegisterClientRegistrationTokenMutation } from 'state/api/trpc/subsc // eslint-disable-next-line max-len import { computeChannelTypeFromBrowserType } from 'views/pages/NotificationSettings/computeChannelTypeFromBrowserType'; // eslint-disable-next-line max-len +import useUserStore from 'state/ui/user'; import { getFirebaseMessagingToken } from 'views/pages/NotificationSettings/getFirebaseMessagingToken'; export function useRegisterPushNotificationSubscriptionCallback() { const registerClientRegistrationToken = useRegisterClientRegistrationTokenMutation(); + const user = useUserStore(); return useCallback(async () => { const browserType = getBrowserType(); @@ -21,11 +23,11 @@ export function useRegisterPushNotificationSubscriptionCallback() { console.log('Notification permission granted.'); const token = await getFirebaseMessagingToken(); await registerClientRegistrationToken.mutateAsync({ - id: 0, // this should be the aggregate id (user?) + id: user.id, token, channelType, }); console.log('Push notifications registered.'); } - }, [registerClientRegistrationToken]); + }, [registerClientRegistrationToken, user.id]); } diff --git a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useUnregisterPushNotificationSubscriptionCallback.ts b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useUnregisterPushNotificationSubscriptionCallback.ts index b43b2485f46..3f86a743bde 100644 --- a/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useUnregisterPushNotificationSubscriptionCallback.ts +++ b/packages/commonwealth/client/scripts/views/pages/NotificationSettings/useUnregisterPushNotificationSubscriptionCallback.ts @@ -5,11 +5,13 @@ import { useUnregisterClientRegistrationTokenMutation } from 'state/api/trpc/sub // eslint-disable-next-line max-len import { computeChannelTypeFromBrowserType } from 'views/pages/NotificationSettings/computeChannelTypeFromBrowserType'; // eslint-disable-next-line max-len +import useUserStore from 'state/ui/user'; import { getFirebaseMessagingToken } from 'views/pages/NotificationSettings/getFirebaseMessagingToken'; export function useUnregisterPushNotificationSubscriptionCallback() { const unregisterClientRegistrationToken = useUnregisterClientRegistrationTokenMutation(); + const user = useUserStore(); return useCallback(async () => { const browserType = getBrowserType(); @@ -22,11 +24,11 @@ export function useUnregisterPushNotificationSubscriptionCallback() { console.log('Notification permission granted.'); const token = await getFirebaseMessagingToken(); await unregisterClientRegistrationToken.mutateAsync({ - id: 0, // this should be the aggregate id (user?) + id: user.id, token, channelType, }); console.log('Push notifications unregistered.'); } - }, [unregisterClientRegistrationToken]); + }, [unregisterClientRegistrationToken, user.id]); } From 72c3aeedfea5a5d616116233fbc943234e5cfdf7 Mon Sep 17 00:00:00 2001 From: ianrowan Date: Thu, 10 Oct 2024 14:33:04 -0500 Subject: [PATCH 16/17] add erc20 contest deployment helper --- .../Abi/NamespaceFactoryAbi.ts | 14 +++++++ .../helpers/ContractHelpers/Contest.ts | 37 +++++++++++++++++++ .../ContractHelpers/NamespaceFactory.ts | 36 ++++++++++++++++++ 3 files changed, 87 insertions(+) diff --git a/packages/commonwealth/client/scripts/helpers/ContractHelpers/Abi/NamespaceFactoryAbi.ts b/packages/commonwealth/client/scripts/helpers/ContractHelpers/Abi/NamespaceFactoryAbi.ts index 4314661c8d0..1504b0e381f 100644 --- a/packages/commonwealth/client/scripts/helpers/ContractHelpers/Abi/NamespaceFactoryAbi.ts +++ b/packages/commonwealth/client/scripts/helpers/ContractHelpers/Abi/NamespaceFactoryAbi.ts @@ -1,4 +1,18 @@ export const namespaceFactoryAbi = [ + { + type: 'function', + name: 'newSingleERC20Contest', + inputs: [ + { name: 'name', type: 'string', internalType: 'string' }, + { name: 'length', type: 'uint256', internalType: 'uint256' }, + { name: 'winnerShares', type: 'uint256[]', internalType: 'uint256[]' }, + { name: 'token', type: 'address', internalType: 'address' }, + { name: 'voterShare', type: 'uint256', internalType: 'uint256' }, + { name: 'exhangeToken', type: 'address', internalType: 'address' }, + ], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'nonpayable', + }, { inputs: [], stateMutability: 'view', diff --git a/packages/commonwealth/client/scripts/helpers/ContractHelpers/Contest.ts b/packages/commonwealth/client/scripts/helpers/ContractHelpers/Contest.ts index e8dbde5dd8f..b147405b80e 100644 --- a/packages/commonwealth/client/scripts/helpers/ContractHelpers/Contest.ts +++ b/packages/commonwealth/client/scripts/helpers/ContractHelpers/Contest.ts @@ -130,6 +130,43 @@ class Contest extends ContractBase { } } + async newSingleERC20Contest( + namespaceName: string, + contestInterval: number, + winnerShares: number[], + voteToken: string, + voterShare: number, + walletAddress: string, + exchangeToken: string, + ): Promise { + if (!this.initialized || !this.walletEnabled) { + await this.initialize(true); + } + + try { + const txReceipt = await this.namespaceFactory.newERC20Contest( + namespaceName, + contestInterval, + winnerShares, + voteToken, + voterShare, + walletAddress, + exchangeToken, + ); + // @ts-expect-error StrictNullChecks + const eventLog = txReceipt.logs.find((log) => log.topics[0] == TOPIC_LOG); + const newContestAddress = this.web3.eth.abi.decodeParameters( + ['address', 'address', 'uint256', 'bool'], + // @ts-expect-error StrictNullChecks + eventLog.data.toString(), + )['0'] as string; + this.contractAddress = newContestAddress; + return newContestAddress; + } catch (error) { + throw new Error('Failed to initialize contest ' + error); + } + } + /** * Allows for deposit of contest token(ETH or ERC20) to contest * @param amount amount in ether to send to contest diff --git a/packages/commonwealth/client/scripts/helpers/ContractHelpers/NamespaceFactory.ts b/packages/commonwealth/client/scripts/helpers/ContractHelpers/NamespaceFactory.ts index b42006c9949..8b1ae39aa8f 100644 --- a/packages/commonwealth/client/scripts/helpers/ContractHelpers/NamespaceFactory.ts +++ b/packages/commonwealth/client/scripts/helpers/ContractHelpers/NamespaceFactory.ts @@ -218,6 +218,42 @@ class NamespaceFactory extends ContractBase { return txReceipt; } + async newERC20Contest( + namespaceName: string, + contestInterval: number, + winnerShares: number[], + voteToken: string, + voterShare: number, + walletAddress: string, + exchangeToken: string, + ): Promise { + if (!this.initialized || !this.walletEnabled) { + await this.initialize(true); + } + const maxFeePerGasEst = await this.estimateGas(); + let txReceipt; + try { + txReceipt = await this.contract.methods + .newSingleERC20Contest( + namespaceName, + contestInterval, + winnerShares, + voteToken, + voterShare, + exchangeToken, + ) + .send({ + from: walletAddress, + type: '0x2', + maxFeePerGas: maxFeePerGasEst?.toString(), + maxPriorityFeePerGas: this.web3.utils.toWei('0.001', 'gwei'), + }); + } catch { + throw new Error('Transaction failed'); + } + return txReceipt; + } + async getFeeManagerBalance( namespace: string, token?: string, From a285377a894be8c1467399fc9dddea952e4707ee Mon Sep 17 00:00:00 2001 From: KaleemNeslit Date: Fri, 11 Oct 2024 01:47:04 +0500 Subject: [PATCH 17/17] made the Discussion clickable and navigate to discussionAll --- .../component_kit/cw_breadcrumbs.tsx | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/commonwealth/client/scripts/views/components/component_kit/cw_breadcrumbs.tsx b/packages/commonwealth/client/scripts/views/components/component_kit/cw_breadcrumbs.tsx index 33fc586ad32..dcda32e35cc 100644 --- a/packages/commonwealth/client/scripts/views/components/component_kit/cw_breadcrumbs.tsx +++ b/packages/commonwealth/client/scripts/views/components/component_kit/cw_breadcrumbs.tsx @@ -20,6 +20,21 @@ type BreadcrumbsProps = { tooltipStr?: string; }; +const handleNavigation = (label, navigate, isParent) => { + if (label === 'Discussions' && isParent) { + navigate(`/discussions`); + } +}; +const handleMouseInteraction = ( + label: string, + handleInteraction: (event: React.MouseEvent) => void, + event: React.MouseEvent, +) => { + if (label !== 'Discussions') { + handleInteraction(event); + } +}; + export const CWBreadcrumbs = ({ breadcrumbs, tooltipStr, @@ -36,14 +51,19 @@ export const CWBreadcrumbs = ({ placement="bottom" renderTrigger={(handleInteraction) => ( + handleMouseInteraction(label, handleInteraction, event) + } + onMouseLeave={(event) => + handleMouseInteraction(label, handleInteraction, event) + } type="caption" className={clsx({ 'disable-active-cursor': index === 0, 'current-text': isCurrent, 'parent-text': !isCurrent, })} + onClick={() => handleNavigation(label, navigate, isParent)} > {truncateText(label)}