feat(gui): Add a Introduction for new users (#287)

* feat(gui): add IntroductionModal component

* feat(gui): add interactivity to IntroductionModal

* feat(gui): create SlideTemplate component for IntroductionModal Slides

* feat(gui): add generic slides to IntroductionModal with images and content

* feat(gui): add Slide with SwapStatusAlert to IntroductionModal

* feat(gui): show the introduction only on the first app start

* feat(gui): make external links functional

* fix(gui): update github link to link to active repo

* feat(gui): replace old images with new mockups and update Slide05 content

* feat(gui): add CardSelectionGroup and CardSelectionOption components for improved card selection UI

* feat(gui): add FiatPricePreference slide to IntroductionModal

* feat(gui): save user preference regarding fiat prices

I set the initial store configuration for fetching fiat prices to false to avoid any calls to coingecko without user consent

* refactor(gui): remove old Slide05 component for improved codebase maintenance

* fix(gui): add UnstoppableSwap logo to FiatPricePreference slide

* refactor(gui): update image imports and improve slide content for introduction modal

* fix(gui): introduce ExternalLink component and update Slide05 to use it for external navigation

* fix(gui): replace webp images for introduction with svg mockups for improved quality

* fix(gui): change order of introduction slides, to asking for fiat price preference at the end

* refactor(gui): implement CardSelectionContext for managing card selection state

* refactor: texts in intro modakl

* fix(gui): update currency fetching SVG for improved design and clarity

* feat(gui): added changelog entry for introduction

---------

Co-authored-by: Binarybaron <binarybaron@protonmail.com>
This commit is contained in:
b-enedict 2025-05-07 12:44:29 +02:00 committed by GitHub
parent 66313ad91f
commit 31e68b2671
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 869 additions and 3 deletions

View file

@ -0,0 +1,39 @@
import { createContext, useContext, useState, ReactNode } from 'react'
interface CardSelectionContextType {
selectedValue: string
setSelectedValue: (value: string) => void
}
const CardSelectionContext = createContext<CardSelectionContextType | undefined>(undefined)
export function CardSelectionProvider({
children,
initialValue,
onChange
}: {
children: ReactNode
initialValue: string
onChange?: (value: string) => void
}) {
const [selectedValue, setSelectedValue] = useState(initialValue)
const handleValueChange = (value: string) => {
setSelectedValue(value)
onChange?.(value)
}
return (
<CardSelectionContext.Provider value={{ selectedValue, setSelectedValue: handleValueChange }}>
{children}
</CardSelectionContext.Provider>
)
}
export function useCardSelection() {
const context = useContext(CardSelectionContext)
if (context === undefined) {
throw new Error('useCardSelection must be used within a CardSelectionProvider')
}
return context
}

View file

@ -0,0 +1,23 @@
import { Box } from '@material-ui/core'
import CheckIcon from '@material-ui/icons/Check'
import { CardSelectionProvider } from './CardSelectionContext'
interface CardSelectionGroupProps {
children: React.ReactElement<{ value: string }>[]
value: string
onChange: (value: string) => void
}
export default function CardSelectionGroup({
children,
value,
onChange,
}: CardSelectionGroupProps) {
return (
<CardSelectionProvider initialValue={value} onChange={onChange}>
<Box style={{ display: 'flex', flexDirection: 'column', gap: 12, marginTop: 12 }}>
{children}
</Box>
</CardSelectionProvider>
)
}

View file

@ -0,0 +1,53 @@
import { Box } from "@material-ui/core";
import CheckIcon from '@material-ui/icons/Check'
import { useCardSelection } from './CardSelectionContext'
// The value prop is used by the parent CardSelectionGroup to determine which option is selected
export default function CardSelectionOption({children, value}: {children: React.ReactNode, value: string}) {
const { selectedValue, setSelectedValue } = useCardSelection()
const selected = value === selectedValue
return (
<Box
onClick={() => setSelectedValue(value)}
style={{
display: 'flex',
alignItems: 'flex-start',
gap: 16,
border: selected ? '2px solid #FF5C1B' : '2px solid #555',
borderRadius: 16,
padding: '1em',
cursor: 'pointer',
transition: 'all 0.2s ease-in-out',
}}
>
<Box
style={{
border: selected ? '2px solid #FF5C1B' : '2px solid #555',
borderRadius: 99999,
width: 28,
height: 28,
background: selected ? '#FF5C1B' : 'transparent',
overflow: 'hidden',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transition: 'all 0.2s ease-in-out',
transform: selected ? 'scale(1.1)' : 'scale(1)',
flexShrink: 0,
}}
>
{selected ? (
<CheckIcon
style={{
transition: 'all 0.2s ease-in-out',
transform: 'scale(1)',
animation: 'checkIn 0.2s ease-in-out'
}}
/>
) : null}
</Box>
<Box pt={0.5}>{children}</Box>
</Box>
)
}