Skip to content

Commit cc09e99

Browse files
authored
fix(app): state management interaction tools (#407)
1 parent 279f54e commit cc09e99

File tree

16 files changed

+127
-149
lines changed

16 files changed

+127
-149
lines changed

frontend/app/components/banner/BannerAnimationSelector.tsx

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect } from 'react'
1+
import { useState } from 'react'
22
import { type SlideAnimationType, SLIDE_ANIMATION } from '@shared/types'
33
import { Checkbox, ToolsDropdown } from '@/components'
44

@@ -24,21 +24,13 @@ export function BannerAnimationSelector({
2424
? SLIDE_ANIMATION.Slide
2525
: validated
2626
})
27-
28-
const [isAnimated, setIsAnimated] = useState(
29-
() => value !== SLIDE_ANIMATION.None
30-
)
31-
32-
useEffect(() => {
33-
setIsAnimated(value !== SLIDE_ANIMATION.None)
34-
}, [value])
27+
const isAnimated = value !== SLIDE_ANIMATION.None
3528

3629
return (
3730
<div className="flex gap-md xl:flex-row flex-col xl:items-center items-start">
3831
<Checkbox
3932
checked={isAnimated}
4033
onChange={(visible) => {
41-
setIsAnimated(visible)
4234
const animation = visible
4335
? lastSelectedAnimation
4436
: SLIDE_ANIMATION.None

frontend/app/components/banner/BannerBuilder.tsx

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
1-
import {
2-
BANNER_FONT_SIZES,
3-
FONT_FAMILY_OPTIONS,
4-
type BannerConfig
5-
} from '@shared/types'
1+
import { BANNER_FONT_SIZES, FONT_FAMILY_OPTIONS } from '@shared/types'
62
import {
73
BannerColorsSelector,
84
Divider,
95
ToolsDropdown,
106
CornerRadiusSelector
117
} from '@/components'
12-
import { useUI } from '~/stores/uiStore'
138
import BuilderAccordion from '@/components/BuilderAccordion'
149
import { InputFieldset } from '@/components/builder/InputFieldset'
1510
import { TitleInput } from '@/components/builder/TitleInput'
@@ -26,7 +21,8 @@ import {
2621
SVGText,
2722
SVGThumbnail
2823
} from '~/assets/svg'
29-
import { toolState } from '~/stores/toolStore'
24+
import { useUIActions, useUIState } from '~/stores/uiStore'
25+
import { useCurrentConfig } from '~/stores/toolStore'
3026

3127
interface Props {
3228
onRefresh: (section: 'content' | 'appearance') => void
@@ -61,8 +57,9 @@ export function BannerBuilder({ onRefresh }: Props) {
6157
}
6258

6359
function ContentBuilder({ onRefresh }: Props) {
64-
const { actions: uiActions, state: uiState } = useUI()
65-
const profile = toolState.currentConfig as BannerConfig
60+
const uiState = useUIState()
61+
const uiActions = useUIActions()
62+
const [snap, profile] = useCurrentConfig({ sync: true })
6663

6764
return (
6865
<BuilderAccordion
@@ -81,7 +78,7 @@ function ContentBuilder({ onRefresh }: Props) {
8178
initialIsOpen={uiState.activeSection === 'content'}
8279
>
8380
<TitleInput
84-
value={profile.bannerTitleText}
81+
value={snap.bannerTitleText}
8582
onChange={(value) => (profile.bannerTitleText = value)}
8683
suggestions={config.suggestedTitles}
8784
maxLength={config.titleMaxLength}
@@ -92,9 +89,9 @@ function ContentBuilder({ onRefresh }: Props) {
9289

9390
<DescriptionInput
9491
label={config.messageLabel}
95-
value={profile.bannerDescriptionText}
92+
value={snap.bannerDescriptionText}
9693
onChange={(text) => (profile.bannerDescriptionText = text)}
97-
isVisible={profile.bannerDescriptionVisible}
94+
isVisible={snap.bannerDescriptionVisible}
9895
onVisibilityChange={(visible) =>
9996
(profile.bannerDescriptionVisible = visible)
10097
}
@@ -107,11 +104,12 @@ function ContentBuilder({ onRefresh }: Props) {
107104
}
108105

109106
function AppearanceBuilder({ onRefresh }: Props) {
110-
const { actions: uiActions, state: uiState } = useUI()
111-
const profile = toolState.currentConfig as BannerConfig
107+
const uiState = useUIState()
108+
const uiActions = useUIActions()
109+
const [snap, profile] = useCurrentConfig()
112110

113111
const defaultFontIndex = FONT_FAMILY_OPTIONS.findIndex(
114-
(option) => option === profile.bannerFontName
112+
(option) => option === snap.bannerFontName
115113
)
116114

117115
return (
@@ -145,7 +143,7 @@ function AppearanceBuilder({ onRefresh }: Props) {
145143
/>
146144

147145
<FontSizeInput
148-
value={profile.bannerFontSize}
146+
value={snap.bannerFontSize}
149147
onChange={(value) => (profile.bannerFontSize = value)}
150148
min={config.fontSizeRange.min}
151149
max={config.fontSizeRange.max}
@@ -159,8 +157,8 @@ function AppearanceBuilder({ onRefresh }: Props) {
159157
icon={<SVGColorPicker className="w-5 h-5" />}
160158
>
161159
<BannerColorsSelector
162-
backgroundColor={profile.bannerBackgroundColor}
163-
textColor={profile.bannerTextColor}
160+
backgroundColor={snap.bannerBackgroundColor}
161+
textColor={snap.bannerTextColor}
164162
onBackgroundColorChange={(color: string) =>
165163
(profile.bannerBackgroundColor = color)
166164
}
@@ -177,7 +175,7 @@ function AppearanceBuilder({ onRefresh }: Props) {
177175
icon={<SVGRoundedCorner className="w-5 h-5" />}
178176
>
179177
<CornerRadiusSelector
180-
value={profile.bannerBorder}
178+
value={snap.bannerBorder}
181179
onChange={(value) => (profile.bannerBorder = value)}
182180
/>
183181
</InputFieldset>
@@ -189,7 +187,7 @@ function AppearanceBuilder({ onRefresh }: Props) {
189187
icon={<SVGHeaderPosition className="w-5 h-5" />}
190188
>
191189
<BannerPositionSelector
192-
value={profile.bannerPosition}
190+
value={snap.bannerPosition}
193191
onChange={(value) => (profile.bannerPosition = value)}
194192
/>
195193
</InputFieldset>
@@ -201,7 +199,7 @@ function AppearanceBuilder({ onRefresh }: Props) {
201199
icon={<SVGAnimation className="w-5 h-5" />}
202200
>
203201
<BannerAnimationSelector
204-
value={profile.bannerSlideAnimation}
202+
value={snap.bannerSlideAnimation}
205203
onChange={(value) => (profile.bannerSlideAnimation = value)}
206204
/>
207205
</InputFieldset>
@@ -213,13 +211,8 @@ function AppearanceBuilder({ onRefresh }: Props) {
213211
icon={<SVGThumbnail className="w-5 h-5" />}
214212
>
215213
<BannerThumbnailSelector
216-
isVisible={
217-
typeof profile.bannerThumbnail === 'undefined' ||
218-
!!profile.bannerThumbnail
219-
}
220-
onVisibilityChange={(visible) => {
221-
profile.bannerThumbnail = visible ? 'default' : ''
222-
}}
214+
value={snap.bannerThumbnail}
215+
onChange={(value) => (profile.bannerThumbnail = value)}
223216
/>
224217
</InputFieldset>
225218
</BuilderAccordion>

frontend/app/components/banner/BannerPreview.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,23 @@ import React, {
66
useState
77
} from 'react'
88
import type { BannerConfig, Banner as BannerElement } from '@tools/components'
9-
import type { BannerConfig as BannerStoredConfig } from '@shared/types'
9+
import { useCurrentConfig } from '~/stores/toolStore'
1010

1111
export interface BannerHandle {
1212
triggerPreview: () => void
1313
}
1414

1515
interface Props {
16-
profile: BannerStoredConfig
1716
cdnUrl: string
1817
ref?: React.Ref<BannerHandle>
1918
}
2019

2120
export const BannerPreview = ({
22-
profile,
2321
cdnUrl,
2422
ref
2523
}: React.PropsWithChildren<Props>) => {
2624
const [isLoaded, setIsLoaded] = useState(false)
25+
const [profile] = useCurrentConfig()
2726
const bannerContainerRef = useRef<HTMLDivElement>(null)
2827
const bannerElementRef = useRef<BannerElement | null>(null)
2928

frontend/app/components/banner/BannerThumbnailSelector.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,23 @@ import { Checkbox, Thumbnail } from '@/components'
33
import wmLogo from '~/assets/images/wm_logo_animated.svg?url'
44

55
interface BannerThumbnailSelectorProps {
6-
isVisible: boolean
7-
onVisibilityChange: (visible: boolean) => void
6+
value: string
7+
onChange: (newValue: string) => void
88
}
99

1010
export function BannerThumbnailSelector({
11-
isVisible,
12-
onVisibilityChange
11+
value,
12+
onChange
1313
}: BannerThumbnailSelectorProps) {
1414
const thumbnails = [wmLogo]
1515
const [selectedThumbnail, setSelectedThumbnail] = useState(0)
16+
const isVisible = Boolean(value)
1617

1718
return (
1819
<div className="flex gap-md xl:flex-row flex-col xl:items-center items-start">
1920
<Checkbox
2021
checked={isVisible}
21-
onChange={onVisibilityChange}
22+
onChange={(visible) => onChange(visible ? 'default' : '')}
2223
label="Visible"
2324
/>
2425
<div className="flex gap-md">

frontend/app/components/builder/BuilderTabs.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ import { useSnapshot } from 'valtio'
66
export function BuilderTabs({ children }: React.PropsWithChildren) {
77
const snap = useSnapshot(toolState)
88

9-
const handleTabSelect = (stableKey: StableKey) => {
10-
toolActions.selectVersion(stableKey)
9+
const handleTabSelect = (profileId: StableKey) => {
10+
toolActions.handleTabSelect(profileId)
1111
}
1212

1313
const handleTabLabelChange = (newLabel: string) => {
14-
toolState.currentConfig.versionName = newLabel
14+
toolActions.handleVersionNameChange(newLabel)
1515
}
1616

1717
// derive tab options consistently from snapshot

frontend/app/components/redesign/components/BuilderPresetTabs.tsx

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { cx } from 'class-variance-authority'
2-
import { useCallback, useEffect, useRef, useState } from 'react'
2+
import { useCallback, useRef, useState } from 'react'
33
import { SVGEdit, SVGExclamationCircle } from '~/assets/svg'
44

55
type TabOption<T extends string> = { id: T; label: string; isDirty: boolean }
@@ -22,17 +22,11 @@ export const BuilderPresetTabs = <T extends string>({
2222
}: Props<T>) => {
2323
const tabListRef = useRef<HTMLDivElement>(null)
2424

25-
const [activeTabId, setActiveTabId] = useState(selectedId)
26-
const [activeTabIdx, setActiveTabIdx] = useState(
27-
options.findIndex((option) => option.id === selectedId)
28-
)
25+
const activeTabId = selectedId
26+
const activeTabIdx = options.findIndex((option) => option.id === selectedId)
2927
const [editingId, setEditingId] = useState<T | null>()
3028
const [hasEditingError, setHasEditingError] = useState(false)
3129

32-
useEffect(() => {
33-
setActiveTab(options.findIndex((option) => option.id === selectedId))
34-
}, [selectedId])
35-
3630
const getTabElement = (id: T) => {
3731
return tabListRef.current!.querySelector<HTMLElement>(
3832
`#${idPrefix}-tab-${id}`
@@ -43,8 +37,6 @@ export const BuilderPresetTabs = <T extends string>({
4337
(tabIndex: number) => {
4438
if (tabIndex < 0) tabIndex += options.length
4539
const tabId = options[tabIndex].id
46-
setActiveTabIdx(tabIndex)
47-
setActiveTabId(tabId)
4840
onChange(tabId)
4941
getTabElement(tabId)?.focus()
5042
},

frontend/app/components/redesign/components/ToolsWalletAddress.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import React, { useState, useId, useRef, useEffect } from 'react'
2-
import { useSnapshot } from 'valtio'
32
import { Tooltip } from './Tooltip'
43
import { InputField } from './InputField'
54
import { ToolsSecondaryButton } from './ToolsSecondaryButton'
@@ -8,7 +7,8 @@ import { SVGRefresh, SVGSpinner } from '~/assets/svg'
87
import { toolState, toolActions } from '~/stores/toolStore'
98
import type { ElementErrors } from '~/lib/types'
109
import { Heading5 } from '../Typography'
11-
import { useUI } from '~/stores/uiStore'
10+
import { useUIActions } from '~/stores/uiStore'
11+
import { useSnapshot } from 'valtio'
1212
import {
1313
checkHrefFormat,
1414
getWalletAddress,
@@ -17,7 +17,7 @@ import {
1717

1818
export const ToolsWalletAddress = () => {
1919
const snap = useSnapshot(toolState)
20-
const { actions: uiActions } = useUI()
20+
const uiActions = useUIActions()
2121
const [error, setError] = useState<ElementErrors>()
2222
const [isLoading, setIsLoading] = useState(false)
2323
const generatedId = useId()

frontend/app/components/redesign/components/builder/DescriptionInput.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { useEffect, useRef } from 'react'
21
import Checkbox from '../Checkbox'
32
import { TextareaField } from '../TextareaField'
43

@@ -23,13 +22,6 @@ export function DescriptionInput({
2322
helpText,
2423
placeholder
2524
}: Props) {
26-
const ref = useRef<HTMLTextAreaElement>(null)
27-
useEffect(() => {
28-
if (ref.current) {
29-
ref.current.value = value
30-
}
31-
}, [value])
32-
3325
return (
3426
<fieldset className="space-y-xs">
3527
<legend className="text-base leading-md font-bold text-text-primary">
@@ -46,9 +38,8 @@ export function DescriptionInput({
4638

4739
<div className="flex-grow w-full">
4840
<TextareaField
49-
defaultValue={value}
41+
value={value}
5042
onChange={(e) => onChange(e.target.value)}
51-
ref={ref}
5243
currentLength={value.length || 0}
5344
maxLength={maxLength}
5445
showCounter={true}

frontend/app/components/redesign/components/builder/TitleInput.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useRef } from 'react'
1+
import { useRef } from 'react'
22
import Divider from '../Divider'
33
import { InputField } from '../InputField'
44
import PillRadioListItem from '../PillRadioListItem'
@@ -80,19 +80,13 @@ function CustomTitle({
8080
helpText
8181
}: Omit<Props, 'suggestions'> & { placeholder: string }) {
8282
const ref = useRef<HTMLInputElement>(null)
83-
useEffect(() => {
84-
if (ref.current) {
85-
ref.current.value = value
86-
}
87-
}, [value])
88-
8983
return (
9084
<div className="flex flex-col gap-xs">
9185
<h4 className="text-base leading-md font-bold text-text-primary">
9286
Custom title
9387
</h4>
9488
<InputField
95-
defaultValue={value}
89+
value={value}
9690
onChange={(e) => onChange(e.target.value.trim())}
9791
ref={ref}
9892
placeholder={placeholder}

frontend/app/components/redesign/components/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export { Checkbox } from './Checkbox'
44
export { StepsIndicator } from './StepsIndicator'
55
export { MobileStepsIndicator } from './StepsIndicator'
66
export { BuilderBackground } from './BuilderBackground'
7+
export { BuilderAccordion } from './BuilderAccordion'
78
export { HeadingCore } from './HeadingCore'
89
export { ToolsWalletAddress } from './ToolsWalletAddress'
910
export { ToolsDropdown } from './ToolsDropdown'
@@ -39,6 +40,7 @@ export { PillTag } from './landing/PillTag'
3940
export { LinkTagGenerator } from './LinkTagGenerator'
4041
export { CodeBlockLink } from './CodeBlockLink'
4142
export { Card } from './Card'
43+
export { TitleInput } from './builder/TitleInput'
4244
export { ImportTagModal } from './revshare/ImportTagModal'
4345
export { RevShareChart } from './revshare/RevShareChart'
4446
export { ShareInput } from './revshare/ShareInput'

0 commit comments

Comments
 (0)