diff --git a/packages/webapp/pages/settings/customization/devcard-mock.tsx b/packages/webapp/pages/settings/customization/devcard-mock.tsx new file mode 100644 index 0000000000..d2fcd44786 --- /dev/null +++ b/packages/webapp/pages/settings/customization/devcard-mock.tsx @@ -0,0 +1,556 @@ +import type { ReactElement, ReactNode } from 'react'; +import React, { useMemo, useState } from 'react'; +import classNames from 'classnames'; +import type { NextSeoProps } from 'next-seo'; +import { Button } from '@dailydotdev/shared/src/components/buttons/Button'; +import { + ButtonSize, + ButtonVariant, +} from '@dailydotdev/shared/src/components/buttons/common'; +import { IconSize } from '@dailydotdev/shared/src/components/Icon'; +import { DevCardIcon } from '@dailydotdev/shared/src/components/icons/DevCard'; +import { MedalBadgeIcon } from '@dailydotdev/shared/src/components/icons/MedalBadge'; +import { ReadingStreakIcon } from '@dailydotdev/shared/src/components/icons/ReadingStreak'; +import { ReputationIcon } from '@dailydotdev/shared/src/components/icons/Reputation'; +import { ShareIcon } from '@dailydotdev/shared/src/components/icons/Share'; +import { SparkleIcon } from '@dailydotdev/shared/src/components/icons/Sparkle'; +import { BriefIcon } from '@dailydotdev/shared/src/components/icons/Brief'; +import { HotIcon } from '@dailydotdev/shared/src/components/icons/Hot'; +import { SettingsIcon } from '@dailydotdev/shared/src/components/icons/Settings'; +import { fallbackImages } from '@dailydotdev/shared/src/lib/config'; +import { cloudinaryDevcardDefaultCoverImage } from '@dailydotdev/shared/src/lib/image'; +import { getPageSeoTitles } from '../../../components/layouts/utils'; + +type MockLayout = 'profile' | 'proof' | 'stack'; +type MockTheme = 'signal' | 'graphite' | 'ranked'; +type MockOption = 'achievements' | 'stack' | 'gear' | 'hotTake'; + +const mockOptions: { id: MockOption; label: string }[] = [ + { id: 'achievements', label: 'Achievements' }, + { id: 'stack', label: 'Stack' }, + { id: 'gear', label: 'Gear' }, + { id: 'hotTake', label: 'Hot take' }, +]; + +const layouts: { id: MockLayout; label: string }[] = [ + { id: 'profile', label: 'Profile' }, + { id: 'proof', label: 'Proof' }, + { id: 'stack', label: 'Stack' }, +]; + +const themes: Record< + MockTheme, + { + label: string; + background: string; + accent: string; + accentSoft: string; + ring: string; + } +> = { + signal: { + label: 'Signal', + background: + 'linear-gradient(145deg, #10141f 0%, #17251d 44%, #2b2113 100%)', + accent: 'bg-accent-avocado-default', + accentSoft: 'bg-accent-avocado-default/16 text-accent-avocado-default', + ring: 'ring-accent-avocado-default/30', + }, + graphite: { + label: 'Graphite', + background: + 'linear-gradient(145deg, #f6f7fb 0%, #e9eef5 48%, #d6efe9 100%)', + accent: 'bg-accent-water-default', + accentSoft: 'bg-accent-water-default/16 text-accent-water-default', + ring: 'ring-accent-water-default/30', + }, + ranked: { + label: 'Ranked', + background: + 'linear-gradient(145deg, #1d1217 0%, #242033 44%, #3a2f14 100%)', + accent: 'bg-accent-cheese-default', + accentSoft: 'bg-accent-cheese-default/16 text-accent-cheese-default', + ring: 'ring-accent-cheese-default/30', + }, +}; + +const stats = [ + { label: 'Reputation', value: '18.4k', icon: ReputationIcon }, + { label: 'Read', value: '1,286', icon: BriefIcon }, + { label: 'Streak', value: '164d', icon: ReadingStreakIcon }, +]; + +const achievements = [ + { title: 'AI Pathfinder', meta: 'Top 2%', color: 'bg-accent-onion-default' }, + { title: 'Open Source Sage', meta: 'Rare', color: 'bg-accent-water-default' }, + { title: 'Brief Boss', meta: 'Level 8', color: 'bg-accent-cheese-default' }, +]; + +const stack = ['Next.js', 'React', 'GraphQL', 'TanStack Query', 'Postgres']; +const gear = ['MacBook Pro', 'Raycast', 'Zed', 'Arc']; + +const profile = { + name: 'Ada Lovelace', + username: 'ada', + title: 'Full-stack engineer building calm AI tools', + location: 'London, UK', + avatar: fallbackImages.avatar, + cover: cloudinaryDevcardDefaultCoverImage, + hotTake: 'The best developer tools feel invisible until they save your day.', +}; + +const seoTitles = getPageSeoTitles('DevCard mock'); +const seo: NextSeoProps = { + title: seoTitles.title, + noindex: true, + nofollow: true, +}; + +const SectionTitle = ({ children }: { children: ReactNode }): ReactElement => ( +

{children}

+); + +const OptionButton = ({ + active, + children, + onClick, +}: { + active: boolean; + children: ReactNode; + onClick: () => void; +}): ReactElement => ( + +); + +const StatTile = ({ + label, + value, + icon: Icon, +}: (typeof stats)[number]): ReactElement => ( +
+ +
+

{value}

+

{label}

+
+
+); + +const ProfileHeader = ({ theme }: { theme: MockTheme }): ReactElement => ( +
+ +
+ {`${profile.name} +
+ Legendary +
+
+); + +const AchievementStrip = (): ReactElement => ( +
+ {achievements.map((item) => ( +
+ + + +

+ {item.title} +

+

{item.meta}

+
+ ))} +
+); + +const ChipList = ({ + items, + icon, +}: { + items: string[]; + icon?: ReactElement; +}): ReactElement => ( +
+ {items.map((item) => ( + + {icon} + {item} + + ))} +
+); + +const DynamicModules = ({ + selectedOptions, +}: { + selectedOptions: MockOption[]; +}): ReactElement => ( +
+ {selectedOptions.includes('achievements') && } + {selectedOptions.includes('stack') && ( + + } + /> + )} + {selectedOptions.includes('gear') && ( + + } + /> + )} + {selectedOptions.includes('hotTake') && ( +
+
+ + Hot take +
+

{profile.hotTake}

+
+ )} +
+); + +const DevCardMockPreview = ({ + layout, + theme, + selectedOptions, +}: { + layout: MockLayout; + theme: MockTheme; + selectedOptions: MockOption[]; +}): ReactElement => { + const isGraphite = theme === 'graphite'; + const cardTextClass = isGraphite ? 'text-raw-pepper-90' : 'text-white'; + + if (layout === 'proof') { + return ( +
+
+
+
+ {`${profile.name} +
+

+ {profile.name} +

+

+ @{profile.username} · {profile.location} +

+
+
+

{profile.title}

+
+
+ {stats.map((stat) => ( + + ))} +
+
+ +
+ ); + } + + if (layout === 'stack') { + return ( +
+
+ {`${profile.name} +
+

{profile.name}

+

+ @{profile.username} +

+
+ +
+
+ {stack.map((item, index) => ( +
+ {item} + + {index + 1} + +
+ ))} +
+
+ +
+
+ ); + } + + return ( +
+ +
+
+

{profile.name}

+

+ @{profile.username} · {profile.location} +

+

{profile.title}

+
+
+ {stats.map((stat) => ( + + ))} +
+ +
+
+ ); +}; + +const Page = (): ReactElement => { + const [layout, setLayout] = useState('profile'); + const [theme, setTheme] = useState('signal'); + const [selectedOptions, setSelectedOptions] = useState([ + 'achievements', + 'stack', + 'hotTake', + ]); + + const selectedOptionLabels = useMemo( + () => + mockOptions + .filter((option) => selectedOptions.includes(option.id)) + .map((option) => option.label) + .join(', '), + [selectedOptions], + ); + + const toggleOption = (option: MockOption) => { + setSelectedOptions((current) => { + if (current.includes(option)) { + return current.filter((item) => item !== option); + } + + return [...current, option]; + }); + }; + + return ( +
+
+
+
+
+ + DevCard 2026 mock +
+

+ Dynamic profile cards +

+

+ A live concept for a richer DevCard built around reputation, + achievements, stack, gear, and opinionated profile moments. +

+
+
+ + +
+
+ +
+ + +
+
+
+ {themes[theme].label} preview +

+ {layouts.find((item) => item.id === layout)?.label} ·{' '} + {selectedOptionLabels || 'Core identity'} +

+
+ + Live mock + +
+ +
+ +
+
+
+
+
+ ); +}; + +Page.layoutProps = { seo }; + +export default Page;