Platform UI fixes. - #5926
Conversation
There was a problem hiding this comment.
Pull request overview
This PR focuses on UI/UX fixes around platform selection and Go Live settings, primarily improving Patreon audience/tier selection and some shared input components.
Changes:
- Adds a Patreon “Audience” radio control with conditional tier selection and supporting styling/i18n strings.
- Extends shared inputs:
TagsInputnow supportsmaxTagCountplus wrapper layout tweaks;RadioInputsupports per-option children. - Small onboarding/display-selector adjustments (type cleanup, platform list updates, and a header margin tweak).
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| app/i18n/en-US/patreon.json | Adds new Patreon UI strings for audience/tier selection. |
| app/components-react/windows/go-live/platforms/PatreonEditStreamInfo.tsx | Reworks Patreon required fields from a single tags input into an audience radio + conditional tier tags input. |
| app/components-react/windows/go-live/platforms/PatreonEditStreamInfo.m.less | Adds layout overrides for the new Patreon audience/tier UI. |
| app/components-react/windows/go-live/CommonPlatformFields.tsx | Forces controlled behavior for title/description inputs. |
| app/components-react/shared/inputs/TagsInput.tsx | Adds maxTagCount, and introduces nomargin/nolabel wrapper behaviors. |
| app/components-react/shared/inputs/RadioInput.tsx | Adds children rendering per radio option and adjusts layout styling behavior. |
| app/components-react/shared/DisplaySelector.tsx | Removes unused type import and aligns dictionary typing with ICustomRadioOption. |
| app/components-react/pages/onboarding/PrimaryPlatformSelect.tsx | Adds Patreon to the connectable platforms list and reorders some platform options. |
| app/components-react/pages/onboarding/Connect.tsx | Changes title margin styling (currently using an invalid React inline style value). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Memoize tier options and related values to avoid unnecessary calculations on each render | ||
| const { allTiersRule, tierOptions, allRuleValues, allTiersValue } = useMemo(() => { | ||
| const rules = PatreonService.accessRules; | ||
| const paidRule = rules.find(rule => rule.label.toLowerCase() === 'paid'); | ||
| return { | ||
| allTiersRule: paidRule, | ||
| tierOptions: rules.filter( | ||
| rule => rule.label.toLowerCase() !== 'free' && rule.value !== paidRule?.value, | ||
| ), | ||
| allRuleValues: rules.map(rule => rule.value), | ||
| allTiersValue: paidRule?.value, | ||
| }; | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, []); |
| const onTagsChange = useCallback( | ||
| (newValues: string[]) => { | ||
| const addedAllTiers = | ||
| !!allTiersValue && newValues.includes(allTiersValue) && !allTiersSelected; | ||
| const stripped = allTiersValue ? newValues.filter(v => v !== allTiersValue) : newValues; | ||
|
|
||
| if (newValues.length === 0) { | ||
| updateSettings({ accessRules: allTiersValue ? [allTiersValue] : [] }); | ||
| return; | ||
| } | ||
|
|
||
| if (addedAllTiers) { | ||
| updateSettings({ accessRules: [allTiersValue!] }); | ||
| return; | ||
| } | ||
|
|
||
| updateSettings({ accessRules: stripped }); | ||
| }, | ||
| [allTiersValue, allTiersSelected, updateSettings], | ||
| ); |
| <InputWrapper | ||
| {...wrapperAttrs} | ||
| nolabel={p?.nolabel} | ||
| style={{ margin: p.nomargin ? '0px' : 'inherit' }} |
| <Space | ||
| size={p?.gapsize ?? undefined} | ||
| direction={p?.direction ?? 'vertical'} | ||
| style={p?.style} |
| <div className={styles.pageContainer}> | ||
| <div className={styles.container}> | ||
| <h1 className={commonStyles.titleContainer} style={{ marginTop: 0 }}> | ||
| <h1 className={commonStyles.titleContainer} style={{ marginTop: '0px !important' }}> |
BundleMonFiles updated (1)
Unchanged files (3)
Total files change +5.19KB +0.04% Final result: ✅ View report in BundleMon website ➡️ |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (2)
app/components-react/windows/go-live/platforms/PatreonEditStreamInfo.tsx:41
useMemo(..., [])cachesPatreonService.accessRules-derived values forever and suppresses the exhaustive-deps rule. SinceaccessRulesare populated asynchronously (e.g., aftervalidatePlatform()updates service state), this component can permanently keep empty/staletierOptions/allRuleValues, leading to incorrect audience defaults and wrongaccessRulesbeing written. Compute these values from the currentPatreonService.accessRuleseach render, or include a stableaccessRulesdependency so the memo recomputes when rules change (and remove the eslint disable).
// Memoize tier options and related values to avoid unnecessary calculations on each render
const { allTiersRule, tierOptions, allRuleValues, allTiersValue } = useMemo(() => {
const rules = PatreonService.accessRules;
const paidRule = rules.find(rule => rule.label.toLowerCase() === 'paid');
return {
allTiersRule: paidRule,
tierOptions: rules.filter(
rule => rule.label.toLowerCase() !== 'free' && rule.value !== paidRule?.value,
),
allRuleValues: rules.map(rule => rule.value),
allTiersValue: paidRule?.value,
};
// Note: PatreonService.accessRules is assumed to be static or memoized itself
// so it's safe to omit it from dependencies
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
app/components-react/windows/go-live/platforms/PatreonEditStreamInfo.tsx:46
audienceTypeis stored in local state and only initialized frompatreonSettings.accessRules. Ifp.value.accessRuleschanges due to external form re-sync / settings updates, the radio selection can become out of sync with the actualaccessRulesbeing submitted. DeriveaudienceTypedirectly frompatreonSettings.accessRules(and update viaupdateSettings), or add an effect to keep the state synchronized whenpatreonSettings.accessRuleschanges.
const [audienceType, setAudienceType] = useState<TPatreonAudienceType>(() => {
const ruleCount = patreonSettings.accessRules.length;
return ruleCount === 0 || ruleCount === allRuleValues.length ? 'all' : 'paid';
});
| @@ -1,27 +1,115 @@ | |||
| import React from 'react'; | |||
| import React, { useMemo, useState, useCallback } from 'react'; | |||
| return p.tagRender(tagProps, tag); | ||
| } | ||
| return <Tag {...tagProps}>{tag.label}</Tag>; | ||
| return <Tag {...tagProps}>{tag?.label}</Tag>; |
| return ( | ||
| <div className={styles.pageContainer}> | ||
| <div className={styles.container}> | ||
| <h1 className={commonStyles.titleContainer} style={{ marginTop: 0 }}> | ||
| {title} | ||
| </h1> | ||
| <h1 className={commonStyles.titleContainer}>{title}</h1> | ||
| {isSignup ? ( |
Platform onboarding, access-rule tags, and Go Live form fixes
Issues
CommonPlatformFieldswere being cleared whenever the form re-synced after a platform settings update.Changes
Onboarding (
04cb9b5e)Connectstep andPrimaryPlatformSelectlist so it can be linked alongside the other platforms.Access-rule tags input (
d8b705b7)All Members/Paid) to the platform's Go Live edit panel.TagsInput-backed tier picker shown whenPaidis selected, with newpatreon.jsonstrings.RadioInputandTagsInputshared components to support the layout, child-rendering, and label behaviors the new panel needs; smallDisplaySelectoradjustment for consistency.PatreonEditStreamInfo.m.lessmodule for the panel-specific styling.Go Live form — unrelated-field clearing (
34f2853b)useGoLiveSettingsso asetPlatformSettingsupdate merges into the existing platform state instead of replacing the whole settings object, which was wiping fields owned by other platforms.Go Live form — title/description clearing (
a426e355)useGoLiveSettingsthat was overwriting in-progress edits, and updatedCommonPlatformFieldsso title/description stay bound to the active form state instead of being reset on every platform update.Platform rules filtering (
83e91c47)Paidtier picker so the audience state is fully derived fromaccessRules:Paidpreselects the API's "all tiers" rule as a single tag.All Membershides the tier picker entirely instead of disabling it.freerule out of the dropdown, and relabels the API's "paid" rule asAll Tiersfor display only — the stored value remains the real API key so what's sent to the backend is unchanged.