From 3823763f2293e18f3c412b9ef1617822123168d3 Mon Sep 17 00:00:00 2001 From: Amar Trebinjac Date: Thu, 29 Jan 2026 19:43:27 +0100 Subject: [PATCH 1/3] feat: add experience buttons to profile sections Add empty state "click to add" buttons for each experience type on the profile page, following the pattern established by hot takes and stacks sections. Each experience type now shows a dedicated button with appropriate icon and copy when the profile owner has no experiences. - Add centered empty state cards with icons for all 6 experience types - Replace header edit button with "+ Add" button - Navigate directly to add form at /settings/profile/experience/edit?type={type} Co-Authored-By: Claude Haiku 4.5 --- .../experience/UserExperiencesList.tsx | 133 ++++++++++++++++-- 1 file changed, 120 insertions(+), 13 deletions(-) diff --git a/packages/shared/src/features/profile/components/experience/UserExperiencesList.tsx b/packages/shared/src/features/profile/components/experience/UserExperiencesList.tsx index d6314cbcfe..f7bab02386 100644 --- a/packages/shared/src/features/profile/components/experience/UserExperiencesList.tsx +++ b/packages/shared/src/features/profile/components/experience/UserExperiencesList.tsx @@ -1,11 +1,10 @@ import type { ReactElement } from 'react'; import React, { useMemo } from 'react'; -import type { - UserExperience, - UserExperienceType, -} from '../../../../graphql/user/profile'; +import type { UserExperience } from '../../../../graphql/user/profile'; +import { UserExperienceType } from '../../../../graphql/user/profile'; import { Typography, + TypographyColor, TypographyTag, TypographyType, } from '../../../../components/typography/Typography'; @@ -17,13 +16,70 @@ import { ButtonSize, ButtonVariant, } from '../../../../components/buttons/Button'; -import { MoveToIcon, EditIcon } from '../../../../components/icons'; +import { + MoveToIcon, + PlusIcon, + JobIcon, + TerminalIcon, + TourIcon, +} from '../../../../components/icons'; +import { GraduationIcon } from '../../../../components/icons/Graduation'; +import { MedalIcon } from '../../../../components/icons/Medal'; +import { VolunteeringIcon } from '../../../../components/icons/Volunteering'; import { IconSize } from '../../../../components/Icon'; import Link from '../../../../components/utilities/Link'; import { useAuthContext } from '../../../../contexts/AuthContext'; import { webappUrl } from '../../../../lib/constants'; import type { PublicProfile } from '../../../../lib/user'; import { useProfilePreview } from '../../../../hooks/profile/useProfilePreview'; +import type { IconProps } from '../../../../components/Icon'; + +const experienceTypeConfig: Record< + UserExperienceType, + { + icon: React.FC; + label: string; + heading: string; + subheading: string; + } +> = { + [UserExperienceType.Work]: { + icon: JobIcon, + label: 'work experience', + heading: 'Add your work experience', + subheading: "Show where you've worked and what you've accomplished", + }, + [UserExperienceType.Education]: { + icon: GraduationIcon, + label: 'education', + heading: 'Add your education', + subheading: 'Share your academic background and achievements', + }, + [UserExperienceType.Certification]: { + icon: MedalIcon, + label: 'certification', + heading: 'Add your certifications', + subheading: 'Showcase your professional certifications and credentials', + }, + [UserExperienceType.OpenSource]: { + icon: TerminalIcon, + label: 'open source contribution', + heading: 'Add your open source work', + subheading: 'Highlight your contributions to open source projects', + }, + [UserExperienceType.Project]: { + icon: TourIcon, + label: 'project', + heading: 'Add your projects', + subheading: 'Share your side projects and publications', + }, + [UserExperienceType.Volunteering]: { + icon: VolunteeringIcon, + label: 'volunteering experience', + heading: 'Add your volunteering', + subheading: 'Share your community involvement and volunteer work', + }, +}; interface UserExperienceListProps { experiences: T[]; @@ -75,13 +131,63 @@ export function UserExperienceList({ [experiences], ); - if (!user || !experiences?.length) { + const hasExperiences = experiences?.length > 0; + + if (!user) { + return null; + } + + if (!hasExperiences && !isOwner) { return null; } const showMoreUrl = `${webappUrl}${user.username}/${experienceType}`; const editBaseUrl = `${webappUrl}settings/profile/experience/edit`; - const settingsUrl = `${webappUrl}settings/profile/experience/${experienceType}`; + const addUrl = `${editBaseUrl}?type=${experienceType}`; + const config = experienceTypeConfig[experienceType]; + const IconComponent = config.icon; + + if (!hasExperiences && isOwner) { + return ( +
+ {title && ( + + {title} + + )} +
+
+ +
+
+ + {config.heading} + + + {config.subheading} + +
+ + + +
+
+ ); + } return (
@@ -90,15 +196,16 @@ export function UserExperienceList({ {title} - {settingsUrl && isOwner && ( - + {isOwner && ( + )}
From 785f9cb5f84f0808b719963e9b62fe6ec81e11f5 Mon Sep 17 00:00:00 2001 From: Amar Trebinjac Date: Thu, 29 Jan 2026 19:55:50 +0100 Subject: [PATCH 2/3] fix: update tests and add passHref to Link components - Add passHref to Link components to avoid nested anchor warnings - Update tests to reflect new empty state behavior for profile owners - Update test to check for new add button URL pattern Co-Authored-By: Claude Haiku 4.5 --- .../experience/UserExperiencesList.spec.tsx | 35 +++++++++++++------ .../experience/UserExperiencesList.tsx | 4 +-- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/packages/shared/src/features/profile/components/experience/UserExperiencesList.spec.tsx b/packages/shared/src/features/profile/components/experience/UserExperiencesList.spec.tsx index 0b54f830b5..c622761856 100644 --- a/packages/shared/src/features/profile/components/experience/UserExperiencesList.spec.tsx +++ b/packages/shared/src/features/profile/components/experience/UserExperiencesList.spec.tsx @@ -100,7 +100,7 @@ describe('UserExperiencesList', () => { }); describe('Basic rendering', () => { - it('should render empty fragment when no experiences provided', () => { + it('should render empty state with add button for owner when no experiences provided', () => { const user = createUser(); renderComponent({ experiences: [], @@ -109,6 +109,23 @@ describe('UserExperiencesList', () => { user, }); + // Owner should see empty state with title and add button + expect(screen.getByText('Work Experience')).toBeInTheDocument(); + expect(screen.getByText('Add your work experience')).toBeInTheDocument(); + expect( + screen.getByRole('link', { name: /add your first work experience/i }), + ).toBeInTheDocument(); + }); + + it('should render empty fragment for non-owner when no experiences provided', () => { + const user = createUser({ id: 'otheruser', username: 'otheruser' }); + renderComponent({ + experiences: [], + title: 'Work Experience', + experienceType: UserExperienceType.Work, + user, + }); + expect(screen.queryByText('Work Experience')).not.toBeInTheDocument(); }); @@ -451,7 +468,7 @@ describe('UserExperiencesList', () => { }); describe('UI interactions and navigation', () => { - it('should render edit button when user owns the profile', () => { + it('should render add button when user owns the profile', () => { const user = createUser(); const experience = createExperience(); renderComponent({ @@ -461,14 +478,12 @@ describe('UserExperiencesList', () => { user, }); - // The edit button is rendered as a link/button with href - const editLinks = screen.getAllByRole('link'); - const editButton = editLinks.find((link) => - link.getAttribute('href')?.includes('settings/profile/experience/work'), - ); - expect(editButton).toBeTruthy(); - expect(editButton?.getAttribute('href')).toBe( - 'https://app.daily.dev/settings/profile/experience/work', + // The add button is rendered as a link/button with href to edit page + const addButton = screen.getByRole('link', { name: 'Add' }); + expect(addButton).toBeInTheDocument(); + expect(addButton).toHaveAttribute( + 'href', + 'https://app.daily.dev/settings/profile/experience/edit?type=work', ); }); diff --git a/packages/shared/src/features/profile/components/experience/UserExperiencesList.tsx b/packages/shared/src/features/profile/components/experience/UserExperiencesList.tsx index f7bab02386..45f9ae9fce 100644 --- a/packages/shared/src/features/profile/components/experience/UserExperiencesList.tsx +++ b/packages/shared/src/features/profile/components/experience/UserExperiencesList.tsx @@ -174,7 +174,7 @@ export function UserExperienceList({ {config.subheading} - + + size={ButtonSize.XSmall} + icon={} + aria-label={`Edit ${title}`} + /> )}