2027 new design payment drawer#2094
Conversation
Refactor code structure for improved readability and maintainability
New redesigned payment drawer with mobile-first approach: - Mobile: Fixed bottom bar with pie chart, expands via swipe-up gesture - Desktop: Sticky sidebar with 360px max-width and drop shadow Components added: - PaymentDrawer: Main component with mobile/desktop responsive layouts - CampaignProgressPie: SVG circular progress with state-based colors - DonationsTab: Sortable donation list with API-based sorting - WishesTab: Supporter wishes display with truncated messages - ShareButtons: Facebook, email, WhatsApp, copy link functionality - constants.ts: Centralized design tokens and styled components - types.ts: TypeScript interfaces for all drawer components Features: - Swipe-to-open gesture with scroll prevention on mobile - API-based sorting by date/amount for donations - Tab navigation between Donations and Wishes - Share buttons with clipboard integration - Campaign progress pie chart with percentage display - "See All" buttons scroll to wishes section Replaces InlineDonation component with new PaymentDrawer Relates to: podkrepi-bg#2027
New redesigned payment drawer with mobile-first approach: PaymentDrawer: - Mobile: Fixed bottom bar with pie chart, expands via swipe-up gesture - Desktop: Sticky sidebar with 360px width and drop shadow - Donations/Wishes tabs with sortable donation list - API-based sorting by date/amount SharePage (/campaigns/[slug]/share): - Dedicated page for social sharing matching Figma design - Campaign image with circular avatar display - Facebook, LinkedIn, Instagram share buttons - Yellow email button, grey copy link button - Back navigation to campaign Components added: - PaymentDrawer, CampaignProgressPie, DonationsTab, WishesTab - SharePage with social share handlers - Design tokens in constants.ts, types in types.ts Features: - Swipe-to-open gesture with scroll prevention (mobile) - Campaign progress pie chart with state-based colors - "See All" buttons scroll to wishes section Replaces InlineDonation component with PaymentDrawer Relates to: podkrepi-bg#2027
- Wrap SharePage in Layout component with meta tags for SEO - Remove back button and use site-wide navigation instead - Change vertical alignment from center to top (flex-start) - Adjust padding for desktop/mobile responsive design - Remove unused imports (useRouter, ArrowBack) - Set minHeight to 60vh instead of 100vh Ref: podkrepi-bg#2027
- Add aria-hidden="true" to decorative swipe handle elements
- Replace hardcoded Bulgarian "от" with t('campaign.from') in WishesTab
This reverts commit e41ec8c.
…ns component is being used. Do we need it?" No code changes needed. This is a comment regarding the usage of the ShareButtons component.
… bit redundant since all components here are used only by PaymentDrawer." We only export PaymentDrawer from this file now.
…sure whether this would work. Can you remove it for now, as it is not in the requirements anyway." Removed whatsapp share option from social media sharing helper.
…e/DesktopWrappers fragments into their own files? Should make the file a bit more readable." PaymentDrawer Refactor - Modullized Components and Logic Hook; Separated concerns for better maintainability and readability.
…ographys etc..) should be within the themeOptions(common/theme.ts). Can you investigate whether it will be hard to do so, and adapt those settings(if not existing there)." All theme related settings have been moved to the theme. Also, Added theme.d.ts for type safety.
- Add data-testid attributes to DesktopPaymentDrawer and MobilePaymentDrawer:
- payment-drawer (wrapper)
- summary-donors, summary-wishes (tabs)
- summary-donors-wrapper, summary-wishes-wrapper (content)
- payment-drawer-donate-button (donate button)
- Update campaigns.page.ts selectors:
- Replace old InlineDonation selectors with PaymentDrawer testIds
- Fix pre-existing TS errors in isDonorsSectionVisible/isWishesSectionVisible
(methods referenced non-existent parent class methods since PR podkrepi-bg#1646)
- Fix campaign-view.spec.ts:
- Add missing await before expect().toBeVisible() assertions
Refs: podkrepi-bg#2027
- Add 1px border using theme.palette.primary.light (#4AC3FF) to desktop drawer - Implement separate page sizes: 7 for donations, 4 for wishes - Remove unused isMobile parameter from usePaymentDrawerLogic hook
|
❌ Not all tests have run for this PR. Please add the |
There was a problem hiding this comment.
Pull request overview
This PR implements a redesigned payment drawer for campaign pages per issue #2027. The redesign provides a modern, mobile-first interface with two states: collapsed (minimal info with progress indicator) and expanded (full details with donations/wishes tabs and sharing).
Changes:
- Replaced
InlineDonationcomponent with newPaymentDrawercomponent featuring collapsed/expanded states for mobile and always-visible sidebar for desktop - Added new
/campaigns/[slug]/sharepage with social media sharing functionality (Facebook, LinkedIn, Instagram, Email) - Extended API endpoints to support sorting donations by date or amount
- Updated theme with new color variants, shadows, and gradients for the drawer design
Reviewed changes
Copilot reviewed 28 out of 29 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/components/client/campaigns/PaymentDrawer/* |
Complete redesign of donation interface with mobile/desktop variants, tabs, progress visualization, and integrated sharing |
src/pages/campaigns/[slug]/share.tsx |
New dedicated share page with social media integration |
src/components/client/campaigns/ViewCampaignPage.tsx |
Replaced InlineDonation with PaymentDrawer component |
src/service/apiEndpoints.ts |
Added sortBy and sortOrder parameters to donations API endpoint |
src/common/theme.ts & src/types/theme.d.ts |
Extended theme with accent colors, yellow variants, custom shadows, and gradients |
src/common/routes.ts |
Added share route for campaigns |
public/locales/{en,bg}/*.json |
Added translations for new drawer, tabs, sharing, and error messages |
e2e/tests/regression/campaign-flow/campaign-view.spec.ts |
Updated tests with proper await syntax for new drawer selectors |
e2e/pages/web-pages/campaigns/campaigns.page.ts |
Updated page object selectors to use PaymentDrawer instead of InlineDonation |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const copyLink = useCallback(async () => { | ||
| try { | ||
| await copyToClipboard(campaignUrl) | ||
| AlertStore.show( | ||
| t('alerts.link-copied', { ns: 'common', defaultValue: 'Link copied successfully!' }), | ||
| 'success', | ||
| ) | ||
| } catch { | ||
| AlertStore.show( | ||
| t('alerts.copy-failed', { ns: 'common', defaultValue: 'Copy failed. Please try again.' }), | ||
| 'error', | ||
| ) | ||
| } |
There was a problem hiding this comment.
The copyToClipboard function from useCopyToClipboard returns void, not a Promise. Using await and try-catch here won't work as expected. The function already handles success/failure internally through state management.
Replace this implementation with:
const copyLink = useCallback(() => {
copyToClipboard(campaignUrl)
AlertStore.show(
t('alerts.link-copied', { ns: 'common', defaultValue: 'Link copied successfully!' }),
'success',
)
}, [campaignUrl, copyToClipboard, t])Or if you need error handling, use the status from the hook:
const [copyStatus, copyToClipboard] = useCopyToClipboard(1000)
useEffect(() => {
if (copyStatus === 'copied') {
AlertStore.show(t('alerts.link-copied', { ns: 'common' }), 'success')
} else if (copyStatus === 'failed') {
AlertStore.show(t('alerts.copy-failed', { ns: 'common' }), 'error')
}
}, [copyStatus, t])| const copyLink = useCallback(async () => { | |
| try { | |
| await copyToClipboard(campaignUrl) | |
| AlertStore.show( | |
| t('alerts.link-copied', { ns: 'common', defaultValue: 'Link copied successfully!' }), | |
| 'success', | |
| ) | |
| } catch { | |
| AlertStore.show( | |
| t('alerts.copy-failed', { ns: 'common', defaultValue: 'Copy failed. Please try again.' }), | |
| 'error', | |
| ) | |
| } | |
| const copyLink = useCallback(() => { | |
| copyToClipboard(campaignUrl) | |
| AlertStore.show( | |
| t('alerts.link-copied', { ns: 'common', defaultValue: 'Link copied successfully!' }), | |
| 'success', | |
| ) |
| onTouchStart={handleTouchStart} | ||
| onTouchEnd={handleTouchEnd} | ||
| data-testid="payment-drawer"> | ||
| <Box className={classes.swipeHandle} aria-hidden="true" /> |
There was a problem hiding this comment.
The collapsed bottom bar relies solely on touch gestures (swipe up) to expand, which is not keyboard accessible. While the "Дарения" button inside can open the drawer, the swipe handle itself should be keyboard accessible for users who cannot use touch.
Consider wrapping the swipe handle in a button or adding keyboard event handlers:
<Box
className={classes.swipeHandle}
role="button"
tabIndex={0}
onClick={openDrawer}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
openDrawer()
}
}}
aria-label={t('cta.expand-drawer', { defaultValue: 'Expand payment drawer' })}
/>Alternatively, ensure the "Дарения" button is properly positioned as the first focusable element in the drawer for keyboard users.
| <Box className={classes.swipeHandle} aria-hidden="true" /> | |
| <Box | |
| className={classes.swipeHandle} | |
| role="button" | |
| tabIndex={0} | |
| onClick={openDrawer} | |
| onKeyDown={(e) => { | |
| if (e.key === 'Enter' || e.key === ' ') { | |
| e.preventDefault() | |
| openDrawer() | |
| } | |
| }} | |
| aria-label={t('cta.expand-drawer', { defaultValue: 'Expand payment drawer' })} | |
| /> |
| <Box className={classes.desktopTabsWrapper}> | ||
| <Typography | ||
| className={`${classes.desktopTab} ${ | ||
| activeTab === 'donors' ? classes.desktopTabSelected : '' | ||
| }`} | ||
| onClick={() => handleTabChange('donors')} | ||
| data-testid="summary-donors"> | ||
| {t('cta.donations')} | ||
| </Typography> | ||
| <Typography | ||
| className={`${classes.desktopTab} ${ | ||
| activeTab === 'wishes' ? classes.desktopTabSelected : '' | ||
| }`} | ||
| onClick={() => handleTabChange('wishes')} | ||
| data-testid="summary-wishes"> | ||
| {t('campaign.wishes')} | ||
| </Typography> | ||
| </Box> | ||
|
|
||
| {/* Content */} | ||
| <Box | ||
| className={classes.contentSection} |
There was a problem hiding this comment.
The tab elements use Typography components with onClick, which are not keyboard accessible. Users navigating with keyboards cannot activate these tabs using Enter or Space keys, and screen readers won't announce them as interactive elements.
Replace Typography with Button or add proper accessibility attributes:
<Button
className={`${classes.desktopTab} ${
activeTab === 'donors' ? classes.desktopTabSelected : ''
}`}
onClick={() => handleTabChange('donors')}
role="tab"
aria-selected={activeTab === 'donors'}
aria-controls="summary-donors-wrapper"
data-testid="summary-donors">
{t('cta.donations')}
</Button>Also wrap the tabs in a proper role="tablist" container. The same issue exists in MobilePaymentDrawer.tsx lines 195-208.
| <Box className={classes.desktopTabsWrapper}> | |
| <Typography | |
| className={`${classes.desktopTab} ${ | |
| activeTab === 'donors' ? classes.desktopTabSelected : '' | |
| }`} | |
| onClick={() => handleTabChange('donors')} | |
| data-testid="summary-donors"> | |
| {t('cta.donations')} | |
| </Typography> | |
| <Typography | |
| className={`${classes.desktopTab} ${ | |
| activeTab === 'wishes' ? classes.desktopTabSelected : '' | |
| }`} | |
| onClick={() => handleTabChange('wishes')} | |
| data-testid="summary-wishes"> | |
| {t('campaign.wishes')} | |
| </Typography> | |
| </Box> | |
| {/* Content */} | |
| <Box | |
| className={classes.contentSection} | |
| <Box className={classes.desktopTabsWrapper} role="tablist"> | |
| <Button | |
| className={`${classes.desktopTab} ${ | |
| activeTab === 'donors' ? classes.desktopTabSelected : '' | |
| }`} | |
| onClick={() => handleTabChange('donors')} | |
| role="tab" | |
| aria-selected={activeTab === 'donors'} | |
| aria-controls="summary-donors-wrapper" | |
| data-testid="summary-donors"> | |
| {t('cta.donations')} | |
| </Button> | |
| <Button | |
| className={`${classes.desktopTab} ${ | |
| activeTab === 'wishes' ? classes.desktopTabSelected : '' | |
| }`} | |
| onClick={() => handleTabChange('wishes')} | |
| role="tab" | |
| aria-selected={activeTab === 'wishes'} | |
| aria-controls="summary-wishes-wrapper" | |
| data-testid="summary-wishes"> | |
| {t('campaign.wishes')} | |
| </Button> | |
| </Box> | |
| {/* Content */} | |
| <Box | |
| className={classes.contentSection} | |
| id={activeTab === 'donors' ? 'summary-donors-wrapper' : 'summary-wishes-wrapper'} |
| <FormControl className={classes.tabsContainer}> | ||
| <Typography | ||
| className={`${classes.tab} ${activeTab === 'donors' ? classes.tabSelected : ''}`} | ||
| onClick={() => handleTabChange('donors')} | ||
| data-testid="summary-donors"> | ||
| {t('cta.donations')} | ||
| </Typography> | ||
| <Typography | ||
| className={`${classes.tab} ${activeTab === 'wishes' ? classes.tabSelected : ''}`} | ||
| onClick={() => handleTabChange('wishes')} | ||
| data-testid="summary-wishes"> | ||
| {t('campaign.wishes')} | ||
| </Typography> | ||
| </FormControl> | ||
|
|
||
| {/* Content */} | ||
| <Box | ||
| className={classes.contentSection} |
There was a problem hiding this comment.
The tab elements use Typography components with onClick, which are not keyboard accessible. Users navigating with keyboards cannot activate these tabs using Enter or Space keys, and screen readers won't announce them as interactive elements.
Replace Typography with Button or add proper accessibility attributes:
<Button
className={`${classes.tab} ${activeTab === 'donors' ? classes.tabSelected : ''}`}
onClick={() => handleTabChange('donors')}
role="tab"
aria-selected={activeTab === 'donors'}
aria-controls="summary-donors-wrapper"
data-testid="summary-donors">
{t('cta.donations')}
</Button>Also change the FormControl container to use role="tablist" for proper tab navigation semantics.
| <FormControl className={classes.tabsContainer}> | |
| <Typography | |
| className={`${classes.tab} ${activeTab === 'donors' ? classes.tabSelected : ''}`} | |
| onClick={() => handleTabChange('donors')} | |
| data-testid="summary-donors"> | |
| {t('cta.donations')} | |
| </Typography> | |
| <Typography | |
| className={`${classes.tab} ${activeTab === 'wishes' ? classes.tabSelected : ''}`} | |
| onClick={() => handleTabChange('wishes')} | |
| data-testid="summary-wishes"> | |
| {t('campaign.wishes')} | |
| </Typography> | |
| </FormControl> | |
| {/* Content */} | |
| <Box | |
| className={classes.contentSection} | |
| <FormControl className={classes.tabsContainer} role="tablist"> | |
| <Button | |
| type="button" | |
| className={`${classes.tab} ${activeTab === 'donors' ? classes.tabSelected : ''}`} | |
| onClick={() => handleTabChange('donors')} | |
| role="tab" | |
| aria-selected={activeTab === 'donors'} | |
| aria-controls="summary-donors-wrapper" | |
| data-testid="summary-donors"> | |
| {t('cta.donations')} | |
| </Button> | |
| <Button | |
| type="button" | |
| className={`${classes.tab} ${activeTab === 'wishes' ? classes.tabSelected : ''}`} | |
| onClick={() => handleTabChange('wishes')} | |
| role="tab" | |
| aria-selected={activeTab === 'wishes'} | |
| aria-controls="summary-wishes-wrapper" | |
| data-testid="summary-wishes"> | |
| {t('campaign.wishes')} | |
| </Button> | |
| </FormControl> | |
| {/* Content */} | |
| <Box | |
| className={classes.contentSection} | |
| id={ | |
| activeTab === 'donors' ? 'summary-donors-wrapper' : 'summary-wishes-wrapper' | |
| } |
| const colors = { | ||
| white: '#FFFFFF', | ||
| black: '#000000', | ||
| darkBlue: '#294E85', | ||
| yellow: '#FFCB57', | ||
| yellowHover: '#FFCA28', | ||
| greyBackground: '#F5F5F5', | ||
| greyBorder: '#E0E0E0', | ||
| } |
There was a problem hiding this comment.
Hardcoded colors are used instead of theme values, which reduces maintainability and consistency across the application. Several of these colors are already defined in the theme.
Replace hardcoded color values with theme references:
colors.yellow→theme.palette.yellowVariants.lightortheme.palette.secondary.maincolors.yellowHover→theme.palette.yellowVariants.hovercolors.white→theme.palette.common.whitecolors.black→theme.palette.common.blackcolors.greyBackground→theme.palette.grey[100]colors.greyBorder→theme.palette.grey[300]
The darkBlue (#294E85) appears to match the primary dark blue used elsewhere. Consider adding it to the theme if it's a brand color, or use an existing theme color.
|
Apologizes for the delay. |
Closes #2027
Motivation and context
Because requested here: #2027 (#2027)
I believe described here: #2027 (#2027)
By implementing requested changes from here: #2027 (#2027)
https://www.figma.com/design/MmvFKzUv6yE5U2wrOpWtwS/Podkrepi.bg?node-id=26830-46795&t=s1fFOpTTQPyWlov9-0
Screenshots:
campaigns/[slug]
campaigns/[slug]/share
Testing
Steps to test
Please check requested changes from here #2027 (#2027) and validate all is implemented
Affected urls
campaigns/[slug]
campaigns/[slug]/share
I did manual E2E tests
campaigns/[slug]
campaigns/[slug]/share
campaigns/[slug] - No longer shows Inline Donation but Payment Drawer
Environment
New environment variables:
NEW_ENV_VAR: env var detailsNew or updated dependencies:
dependency/namev1.0.0v2.0.0