diff --git a/.ai/progress.txt b/.ai/progress.txt index ba201f9da40..a76cf37b0f7 100644 --- a/.ai/progress.txt +++ b/.ai/progress.txt @@ -74,3 +74,35 @@ Each AI run should append an entry with: * Commit: 3e54e37d98 --- + +[2026-01-08] Refactor: Refactored betting markets admin components to align with community market design patterns +- Completed GitHub issue #13274 +- Key implementation decisions: + * Aligned admin components with MarketsAppPage styling and component patterns (light theme) + * Used CW component kit components (CWText, CWButton, CWTag, CWTextInput, CWSelectList, CWCircleMultiplySpinner) + * Applied light theme styling matching MarketsAppPage: white backgrounds (#ffffff), dark text (#1a1a1a, #212529), light borders (#e9ecef) + * Maintained consistent card styling with hover effects, rounded corners, and gradient provider badges + * Implemented proper empty state handling with descriptive messages + * Used responsive grid layout (2 columns on desktop, 1 on mobile) + * Applied proper status tag color coding (active, new, info) + * Added proper date formatting matching MarketsAppPage patterns + * Replaced inline styles and native HTML elements with CW component kit +- Files changed: + * packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketSelector.tsx (refactored to use CW components and proper layout) + * packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketSelector.scss (created new styles matching MarketsAppPage light theme) + * packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketFilters.tsx (refactored to use CWTextInput and CWSelectList with react-select API) + * packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketFilters.scss (created responsive filter layout) + * packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketCard.tsx (refactored to match MarketsAppPage card design with tags, badges, and CWButton) + * packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketCard.scss (completely rewritten to match MarketsAppPage light theme styling) + * packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketList.tsx (added proper empty state with CWText) + * packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketList.scss (updated grid layout and added empty state styles with light theme) + * packages/commonwealth/client/scripts/views/pages/CommunityManagement/Markets/MarketsPage.scss (simplified to match overall structure) +- Tests: All unit tests pass (70 tests in commonwealth package) +- Type checking: No type errors in modified files (pre-existing unrelated errors in model package about Soneium property) +- Linting: All checks pass +- Concerns for reviewers: + * Verify visual consistency between admin and community market views + * Test responsive behavior on mobile devices + * Confirm light theme colors match design system and are consistent + * Validate that status tag colors align with UX expectations + * Check that CWSelectList dropdowns work correctly with react-select API diff --git a/.github/pr-screenshots/markets-admin-full-page.png b/.github/pr-screenshots/markets-admin-full-page.png new file mode 100644 index 00000000000..ebb0fae8c25 Binary files /dev/null and b/.github/pr-screenshots/markets-admin-full-page.png differ diff --git a/.github/pr-screenshots/markets-admin-scrolled.png b/.github/pr-screenshots/markets-admin-scrolled.png new file mode 100644 index 00000000000..fa52405a082 Binary files /dev/null and b/.github/pr-screenshots/markets-admin-scrolled.png differ diff --git a/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketCard.scss b/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketCard.scss index be543186e75..e3d3c739b9b 100644 --- a/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketCard.scss +++ b/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketCard.scss @@ -1,81 +1,94 @@ -@use '../../../styles/shared.scss'; -@use '../../../styles/mixins/border_radius'; -@use '../../../styles/mixins/colors.module'; -@use '../../../styles/mixins/elevation'; - .market-card { + background: #ffffff; + border-radius: 16px; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + border: 1px solid #e9ecef; + overflow: hidden; + height: 100%; display: flex; flex-direction: column; - justify-content: space-between; - // Override default CWCard styles for a darker theme - border: 1px solid colors.$neutral-700; // Subtle dark border - color: colors.$white; // Light text for contrast - padding: 1rem; // Add back padding that was commented out - height: 100%; - &__image-container { - width: calc(100% + 2rem); // Span full width including padding - margin: -1rem -1rem 1rem -1rem; // Adjust margins to align with card edges - height: 120px; // Fixed height for images - overflow: hidden; // Hide overflow for images - border-top-left-radius: border_radius.$border-radius-md; - border-top-right-radius: border_radius.$border-radius-md; - - .market-card__image { - width: 100%; - height: 100%; - object-fit: cover; // Cover the container while maintaining aspect ratio - } + &:hover { + transform: translateY(-4px); + box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1); + border-color: #dee2e6; } - &__header { - margin-bottom: 0.75rem; // Increased margin for spacing - padding-bottom: 0.75rem; - border-bottom: 1px solid colors.$neutral-700; // Darker subtle separator - - .CWText { - color: colors.$white; // Ensure title is white - font-size: 1.1rem; // Slightly larger for prominence - } + .market-card-content { + padding: 24px; + display: flex; + flex-direction: column; + height: 100%; + gap: 20px; } - &__body { - flex-grow: 1; - margin-bottom: 1rem; // Increased margin - font-size: 0.85rem; // Slightly smaller details - color: colors.$neutral-300; // Lighter grey for details + .market-card-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 12px; + margin-bottom: 4px; - .CWText { - margin-bottom: 0.4rem; // Spacing between details - color: colors.$neutral-300; // Ensure detail text is light grey - } - } + .market-tags { + display: flex; + gap: 8px; + flex-wrap: wrap; + flex: 1; - &__footer { - margin-top: auto; - padding-top: 1rem; // Increased padding - border-top: 1px solid colors.$neutral-700; // Darker subtle separator + .category-tag { + font-size: 12px; + font-weight: 600; + padding: 4px 12px; + border-radius: 6px; + } - button { - width: 100%; - padding: 0.75rem; // More padding for larger button - border-radius: border_radius.$border-radius-md; - border: none; // Remove border, use background color - background-color: colors.$primary-500; - color: colors.$white; - cursor: pointer; - font-weight: 600; - font-size: 0.95rem; // Slightly larger text + .status-tag { + font-size: 11px; + font-weight: 700; + padding: 4px 10px; + border-radius: 6px; + text-transform: uppercase; + letter-spacing: 0.5px; + } - &:hover { - background-color: colors.$primary-600; + .date-tag { + font-size: 11px; + font-weight: 500; + padding: 4px 10px; + border-radius: 6px; } + } - &:disabled { - background-color: colors.$neutral-700; // Darker disabled background - color: colors.$neutral-500; // Muted disabled text - cursor: not-allowed; + .market-provider { + .provider-badge { + display: inline-block; + padding: 6px 12px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: #ffffff; + border-radius: 8px; + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + box-shadow: 0 2px 4px rgba(102, 126, 234, 0.3); } } } + + .market-question { + h5 { + color: #212529; + line-height: 1.5; + font-size: 18px; + margin: 0; + } + } + + .market-card-footer { + margin-top: auto; + padding-top: 16px; + border-top: 1px solid #e9ecef; + display: flex; + justify-content: flex-end; + } } diff --git a/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketCard.tsx b/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketCard.tsx index 0920f667777..e5fa25d52b3 100644 --- a/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketCard.tsx +++ b/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketCard.tsx @@ -1,8 +1,7 @@ import React from 'react'; - -import { CWCard } from 'views/components/component_kit/cw_card'; import { CWText } from 'views/components/component_kit/cw_text'; - +import { CWButton } from 'views/components/component_kit/new_designs/CWButton'; +import { CWTag } from 'views/components/component_kit/new_designs/CWTag'; import './MarketCard.scss'; import { Market } from './types'; @@ -27,38 +26,74 @@ export const MarketCard = ({ } }; + const getStatusTagType = (status: string): 'active' | 'new' | 'info' => { + switch (status) { + case 'open': + return 'active'; + case 'closed': + return 'new'; + case 'settled': + return 'info'; + default: + return 'info'; + } + }; + + const formatDate = (date: Date | null) => { + if (!date) return 'N/A'; + const dateObj = typeof date === 'string' ? new Date(date) : date; + return new Intl.DateTimeFormat('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + }).format(dateObj); + }; + return ( - - {market.imageUrl && ( -
- {market.question} +
+
+
+
+ + +
+
+ {market.provider} +
- )} -
- - {market.question} - -
-
- Provider: {market.provider} - Category: {market.category} -
-
- {isSubscribed ? ( - - ) : ( - + +
+ + {market.question} + +
+ + {market.startTime && market.endTime && ( +
+ +
)} + +
+ +
- +
); }; diff --git a/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketFilters.scss b/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketFilters.scss new file mode 100644 index 00000000000..424efb49efc --- /dev/null +++ b/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketFilters.scss @@ -0,0 +1,26 @@ +@use '../../../styles/mixins/media_queries'; + +.MarketFilters { + display: flex; + flex-direction: column; + gap: 16px; + margin-bottom: 24px; + + .filter-search { + width: 100%; + } + + .filter-selects { + display: flex; + gap: 16px; + + @include media_queries.smallInclusive { + flex-direction: column; + } + + > * { + flex: 1; + min-width: 0; + } + } +} diff --git a/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketFilters.tsx b/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketFilters.tsx index 17c2e0c1dac..38520932279 100644 --- a/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketFilters.tsx +++ b/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketFilters.tsx @@ -1,4 +1,8 @@ import React from 'react'; +import type { SingleValue } from 'react-select'; +import { CWSelectList } from 'views/components/component_kit/new_designs/CWSelectList'; +import { CWTextInput } from 'views/components/component_kit/new_designs/CWTextInput'; +import './MarketFilters.scss'; import { MarketFilters as IMarketFilters, MARKET_PROVIDERS } from './types'; interface MarketFiltersProps { @@ -7,6 +11,11 @@ interface MarketFiltersProps { categories: (string | 'all')[]; } +type OptionType = { + value: string; + label: string; +}; + export function MarketFilters({ filters, onFiltersChange, @@ -16,46 +25,60 @@ export function MarketFilters({ onFiltersChange({ ...filters, search: e.target.value }); }; - const handleProviderChange = (e: React.ChangeEvent) => { + const handleProviderChange = (newValue: SingleValue) => { onFiltersChange({ ...filters, - provider: e.target.value as IMarketFilters['provider'], + provider: (newValue?.value || 'all') as IMarketFilters['provider'], }); }; - const handleCategoryChange = (e: React.ChangeEvent) => { + const handleCategoryChange = (newValue: SingleValue) => { onFiltersChange({ ...filters, - category: e.target.value, + category: newValue?.value || 'all', }); }; + const providerOptions: OptionType[] = [ + { value: 'all', label: 'All Providers' }, + ...MARKET_PROVIDERS.map((provider) => ({ + value: provider, + label: provider.charAt(0).toUpperCase() + provider.slice(1), + })), + ]; + + const categoryOptions: OptionType[] = categories.map((category) => ({ + value: category, + label: + category === 'all' + ? 'All Categories' + : category.charAt(0).toUpperCase() + category.slice(1), + })); + return ( -
- - - +
+
+ +
+
+ opt.value === filters.provider)} + onChange={handleProviderChange} + label="Provider" + /> + opt.value === filters.category)} + onChange={handleCategoryChange} + label="Category" + /> +
); } diff --git a/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketList.scss b/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketList.scss index 34c7eec974c..7f28ce118e7 100644 --- a/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketList.scss +++ b/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketList.scss @@ -1,5 +1,35 @@ +@use '../../../styles/mixins/media_queries'; + .market-list { display: grid; - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); - gap: 1rem; + grid-template-columns: 1fr 1fr; + gap: 24px; + + @include media_queries.smallInclusive { + grid-template-columns: 1fr; + } +} + +.markets-empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 80px 32px; + text-align: center; + background: #ffffff; + border-radius: 12px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + + .empty-state-title { + margin-bottom: 12px; + color: #495057; + font-size: 24px; + } + + .empty-state-description { + color: #6c757d; + max-width: 400px; + line-height: 1.6; + } } diff --git a/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketList.tsx b/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketList.tsx index 9f6c488491a..cca9ae9d7d9 100644 --- a/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketList.tsx +++ b/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketList.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { CWText } from 'views/components/component_kit/cw_text'; import { MarketCard } from './MarketCard'; import './MarketList.scss'; import { Market } from './types'; @@ -17,7 +18,16 @@ export function MarketList({ onUnsubscribe, }: MarketListProps) { if (markets.length === 0) { - return

No markets found.

; + return ( +
+ + No markets found + + + Try adjusting your filters or search terms to find markets. + +
+ ); } return ( diff --git a/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketSelector.scss b/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketSelector.scss new file mode 100644 index 00000000000..18d91cadbc5 --- /dev/null +++ b/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketSelector.scss @@ -0,0 +1,31 @@ +.MarketSelector { + width: 100%; + display: flex; + flex-direction: column; + gap: 24px; + + .markets-header { + padding-bottom: 16px; + border-bottom: 1px solid #e9ecef; + + .markets-title { + margin-bottom: 8px; + color: #1a1a1a; + font-size: 32px; + letter-spacing: -0.5px; + } + + .markets-subtitle { + color: #6c757d; + font-size: 16px; + line-height: 1.5; + } + } + + .markets-loading { + display: flex; + justify-content: center; + align-items: center; + min-height: 400px; + } +} diff --git a/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketSelector.tsx b/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketSelector.tsx index e7f7662cd73..9397d5b6c50 100644 --- a/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketSelector.tsx +++ b/packages/commonwealth/client/scripts/views/components/MarketIntegrations/MarketSelector.tsx @@ -1,6 +1,9 @@ import React from 'react'; +import { CWText } from 'views/components/component_kit/cw_text'; +import CWCircleMultiplySpinner from 'views/components/component_kit/new_designs/CWCircleMultiplySpinner'; import { MarketFilters } from './MarketFilters'; import { MarketList } from './MarketList'; +import './MarketSelector.scss'; import { useMarketData } from './useMarketData'; interface MarketSelectorProps { @@ -20,15 +23,26 @@ export function MarketSelector({ communityId }: MarketSelectorProps) { } = useMarketData(communityId); return ( -
-

Find Markets

+
+
+ + Find Markets + + + Discover and subscribe to prediction markets from multiple providers + +
+ + {isLoading ? ( -

Loading markets...

+
+ +
) : ( )} -
+ ); } diff --git a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Markets/MarketsPage.scss b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Markets/MarketsPage.scss index 4d71160921a..a17eb87006e 100644 --- a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Markets/MarketsPage.scss +++ b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/Markets/MarketsPage.scss @@ -1,15 +1,4 @@ -@use '../../../../styles/mixins/colors.module'; - .markets-page-container { - color: colors.$white; - min-height: 100vh; // Ensure it covers the full viewport height - padding: 1rem; // Add some padding around the content - - h1 { - color: colors.$white; // Ensure heading is white - } - - p { - color: colors.$neutral-300; // Muted text for descriptions - } + width: 100%; + padding: 24px; }