From ea2bef7770282bcfdf17b5f33273b8b41e5a26bd Mon Sep 17 00:00:00 2001 From: Amar Trebinjac Date: Wed, 8 Oct 2025 21:10:09 +0200 Subject: [PATCH 01/32] feat: user info --- .../src/components/profile/ProfileHeader.tsx | 65 +++++++++++++++ .../src/components/profile/UserStats.tsx | 8 +- .../layouts/ProfileLayout/NavBar.module.css | 16 ---- .../layouts/ProfileLayout/NavBar.tsx | 79 ------------------- .../layouts/ProfileLayout/index.tsx | 4 - packages/webapp/pages/[userId]/index.tsx | 5 +- 6 files changed, 70 insertions(+), 107 deletions(-) create mode 100644 packages/shared/src/components/profile/ProfileHeader.tsx delete mode 100644 packages/webapp/components/layouts/ProfileLayout/NavBar.module.css delete mode 100644 packages/webapp/components/layouts/ProfileLayout/NavBar.tsx diff --git a/packages/shared/src/components/profile/ProfileHeader.tsx b/packages/shared/src/components/profile/ProfileHeader.tsx new file mode 100644 index 0000000000..f9894ce8ef --- /dev/null +++ b/packages/shared/src/components/profile/ProfileHeader.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { Image } from '../image/Image'; +import { Typography, TypographyType } from '../typography/Typography'; +import { PlusIcon } from '../icons'; +import type { PublicProfile } from '../../lib/user'; +import { VerifiedCompanyUserBadge } from '../VerifiedCompanyUserBadge'; +import { ProfileImageSize } from '../ProfilePicture'; +import type { UserStatsProps } from './UserStats'; +import { UserStats } from './UserStats'; +import JoinedDate from './JoinedDate'; +import { Separator } from '../cards/common/common'; + +type ProfileHeaderProps = { + user: PublicProfile; + userStats?: UserStatsProps['stats']; +}; + +const ProfileHeader = ({ user, userStats }: ProfileHeaderProps) => { + const { name, username, bio, image, cover, isPlus } = user; + return ( +
+
+ Cover +
+ Avatar +
+
+ + {name} + + {isPlus && } +
+
+ {bio && {bio}} + {user?.companies?.length > 0 && ( +
+ +
+ )} +
+ @{username} + + +
+ +
+
+
+ ); +}; + +export default ProfileHeader; diff --git a/packages/shared/src/components/profile/UserStats.tsx b/packages/shared/src/components/profile/UserStats.tsx index 390639a156..cb56c5fdc1 100644 --- a/packages/shared/src/components/profile/UserStats.tsx +++ b/packages/shared/src/components/profile/UserStats.tsx @@ -15,8 +15,6 @@ import { ButtonSize } from '../buttons/Button'; export interface UserStatsProps { stats: { reputation: number; - views: number; - upvotes: number; numFollowers: number; numFollowing: number; }; @@ -68,6 +66,7 @@ export function UserStats({ stats, userId }: UserStatsProps): ReactElement { /> )}
+
-
- - - -
); diff --git a/packages/webapp/components/layouts/ProfileLayout/NavBar.module.css b/packages/webapp/components/layouts/ProfileLayout/NavBar.module.css deleted file mode 100644 index ee24750adb..0000000000 --- a/packages/webapp/components/layouts/ProfileLayout/NavBar.module.css +++ /dev/null @@ -1,16 +0,0 @@ -.nav { - &:before { - content: ''; - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 0.063rem; - margin: 0 auto; - background: var(--theme-border-subtlest-quaternary); - } - - & > div { - position: relative; - } -} diff --git a/packages/webapp/components/layouts/ProfileLayout/NavBar.tsx b/packages/webapp/components/layouts/ProfileLayout/NavBar.tsx deleted file mode 100644 index 68128db5ca..0000000000 --- a/packages/webapp/components/layouts/ProfileLayout/NavBar.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import type { ReactElement } from 'react'; -import React from 'react'; -import type { PublicProfile } from '@dailydotdev/shared/src/lib/user'; -import { ActiveTabIndicator } from '@dailydotdev/shared/src/components/utilities'; -import { - Button, - ButtonSize, - ButtonVariant, -} from '@dailydotdev/shared/src/components/buttons/Button'; -import classNames from 'classnames'; -import { useFeatureTheme } from '@dailydotdev/shared/src/hooks/utils/useFeatureTheme'; -import { useScrollTopClassName } from '@dailydotdev/shared/src/hooks/useScrollTopClassName'; -import Link from '@dailydotdev/shared/src/components/utilities/Link'; -import styles from './NavBar.module.css'; - -export type Tab = { path: string; title: string }; - -const basePath = `/[userId]`; -export const tabs: Tab[] = [ - { - path: basePath, - title: 'Readme', - }, - { - path: `${basePath}/posts`, - title: 'Posts', - }, - { - path: `${basePath}/replies`, - title: 'Replies', - }, - { - path: `${basePath}/upvoted`, - title: 'Upvoted', - }, -]; - -export type NavBarProps = { - selectedTab: number; - profile: PublicProfile; -}; - -export default function NavBar({ - selectedTab, - profile, -}: NavBarProps): ReactElement { - const getTabHref = (tab: Tab) => - tab.path.replace('[userId]', profile.username || profile.id); - const featureTheme = useFeatureTheme(); - const scrollClassname = useScrollTopClassName({ enabled: !!featureTheme }); - - return ( -
- {tabs.map((tab, index) => ( -
- - - - {selectedTab === index && ( - - )} -
- ))} -
- ); -} diff --git a/packages/webapp/components/layouts/ProfileLayout/index.tsx b/packages/webapp/components/layouts/ProfileLayout/index.tsx index 77fed66372..00f7ef0c2d 100644 --- a/packages/webapp/components/layouts/ProfileLayout/index.tsx +++ b/packages/webapp/components/layouts/ProfileLayout/index.tsx @@ -30,7 +30,6 @@ import { useActions } from '@dailydotdev/shared/src/hooks'; import { usePostReferrerContext } from '@dailydotdev/shared/src/contexts/PostReferrerContext'; import { getLayout as getFooterNavBarLayout } from '../FooterNavBarLayout'; import { getLayout as getMainLayout } from '../MainLayout'; -import NavBar, { tabs } from './NavBar'; import { getTemplatedTitle } from '../utils'; import { ProfileWidgets } from '../../../../shared/src/components/profile/ProfileWidgets'; @@ -119,8 +118,6 @@ export default function ProfileLayout({ return <>; } - const selectedTab = tabs.findIndex((tab) => tab.path === router?.pathname); - return ( - {children} diff --git a/packages/webapp/pages/[userId]/index.tsx b/packages/webapp/pages/[userId]/index.tsx index f37c878a77..870de2a9a0 100644 --- a/packages/webapp/pages/[userId]/index.tsx +++ b/packages/webapp/pages/[userId]/index.tsx @@ -20,6 +20,7 @@ import { NextSeo } from 'next-seo'; import type { NextSeoProps } from 'next-seo/lib/types'; import dynamic from 'next/dynamic'; import { useHasAccessToCores } from '@dailydotdev/shared/src/hooks/useCoresFeature'; +import ProfileHeader from '@dailydotdev/shared/src/components/profile/ProfileHeader'; import type { ProfileLayoutProps } from '../../components/layouts/ProfileLayout'; import { getLayout as getProfileLayout, @@ -44,6 +45,7 @@ const Awards = dynamic( const ProfilePage = ({ user: initialUser, noindex, + userStats, }: ProfileLayoutProps): ReactElement => { useJoinReferral(); const { tokenRefreshed } = useAuthContext(); @@ -82,7 +84,8 @@ const ProfilePage = ({ return ( <> -
+
+ {hasCoresAccess && } From b8c5ad84c7f0e75ceeb6fea80f7d25209698365e Mon Sep 17 00:00:00 2001 From: Amar Trebinjac Date: Thu, 9 Oct 2025 09:39:11 +0200 Subject: [PATCH 02/32] feat: profile header --- .../src/components/profile/ProfileHeader.tsx | 2 +- .../src/components/profile/UserStats.tsx | 27 +++++++------------ .../layouts/ProfileLayout/index.tsx | 7 ----- packages/webapp/pages/[userId]/index.tsx | 2 +- 4 files changed, 12 insertions(+), 26 deletions(-) diff --git a/packages/shared/src/components/profile/ProfileHeader.tsx b/packages/shared/src/components/profile/ProfileHeader.tsx index f9894ce8ef..c649aed47a 100644 --- a/packages/shared/src/components/profile/ProfileHeader.tsx +++ b/packages/shared/src/components/profile/ProfileHeader.tsx @@ -27,7 +27,7 @@ const ProfileHeader = ({ user, userStats }: ProfileHeaderProps) => { alt="Avatar" className="absolute left-6 top-6 h-[7.5rem] w-[7.5rem] rounded-16 object-cover" /> -
+
{name} diff --git a/packages/shared/src/components/profile/UserStats.tsx b/packages/shared/src/components/profile/UserStats.tsx index cb56c5fdc1..8447de41a1 100644 --- a/packages/shared/src/components/profile/UserStats.tsx +++ b/packages/shared/src/components/profile/UserStats.tsx @@ -6,11 +6,8 @@ import classed from '../../lib/classed'; import { LazyModal } from '../modals/common/types'; import { useLazyModal } from '../../hooks/useLazyModal'; import { ContentPreferenceType } from '../../graphql/contentPreference'; -import { useAuthContext } from '../../contexts/AuthContext'; -import { UpgradeToPlus } from '../UpgradeToPlus'; -import { TargetId } from '../../lib/log'; -import { usePlusSubscription } from '../../hooks/usePlusSubscription'; -import { ButtonSize } from '../buttons/Button'; +import { ReputationIcon } from '../icons'; +import { IconSize } from '../Icon'; export interface UserStatsProps { stats: { @@ -39,12 +36,9 @@ const Item = ({ ); export function UserStats({ stats, userId }: UserStatsProps): ReactElement { - const { user: loggedUser } = useAuthContext(); const { openModal } = useLazyModal< LazyModal.UserFollowersModal | LazyModal.UserFollowingModal >(); - const { isPlus } = usePlusSubscription(); - const isSameUser = !!loggedUser && loggedUser?.id === userId; const defaultModalProps = { props: { @@ -56,17 +50,16 @@ export function UserStats({ stats, userId }: UserStatsProps): ReactElement { }; return ( -
+
- {isSameUser && !isPlus && ( - - )}
- +
+ + +
- {children}
diff --git a/packages/webapp/pages/[userId]/index.tsx b/packages/webapp/pages/[userId]/index.tsx index 870de2a9a0..0dba04d6b8 100644 --- a/packages/webapp/pages/[userId]/index.tsx +++ b/packages/webapp/pages/[userId]/index.tsx @@ -84,7 +84,7 @@ const ProfilePage = ({ return ( <> -
+
{hasCoresAccess && } From b335b2ba0784c718f063886123afc64e8e5bc111 Mon Sep 17 00:00:00 2001 From: Amar Trebinjac Date: Thu, 9 Oct 2025 21:58:30 +0200 Subject: [PATCH 03/32] feat: basic form --- .../components/fields/ControlledTextField.tsx | 32 ++ .../components/fields/ControlledTextarea.tsx | 28 + .../MarkdownInput/ControlledMarkdownInput.tsx | 30 + .../shared/src/components/fields/Select.tsx | 75 +++ .../components/profile/ExperienceSelect.tsx | 31 ++ packages/shared/src/lib/user.ts | 10 + .../layouts/SettingsLayout/Profile/index.tsx | 513 +++++++----------- 7 files changed, 409 insertions(+), 310 deletions(-) create mode 100644 packages/shared/src/components/fields/ControlledTextField.tsx create mode 100644 packages/shared/src/components/fields/ControlledTextarea.tsx create mode 100644 packages/shared/src/components/fields/MarkdownInput/ControlledMarkdownInput.tsx create mode 100644 packages/shared/src/components/fields/Select.tsx create mode 100644 packages/shared/src/components/profile/ExperienceSelect.tsx diff --git a/packages/shared/src/components/fields/ControlledTextField.tsx b/packages/shared/src/components/fields/ControlledTextField.tsx new file mode 100644 index 0000000000..7c08cab783 --- /dev/null +++ b/packages/shared/src/components/fields/ControlledTextField.tsx @@ -0,0 +1,32 @@ +import { Controller, useFormContext } from 'react-hook-form'; +import React from 'react'; +import type { TextFieldProps } from './TextField'; +import { TextField } from './TextField'; + +type ControlledTextFieldProps = Pick< + TextFieldProps, + 'name' | 'label' | 'leftIcon' | 'placeholder' +>; + +const ControlledTextField = ({ + name, + ...restProps +}: ControlledTextFieldProps) => { + const { control } = useFormContext(); + + return ( + ( + + )} + /> + ); +}; +export default ControlledTextField; diff --git a/packages/shared/src/components/fields/ControlledTextarea.tsx b/packages/shared/src/components/fields/ControlledTextarea.tsx new file mode 100644 index 0000000000..b9120036b2 --- /dev/null +++ b/packages/shared/src/components/fields/ControlledTextarea.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Controller, useFormContext } from 'react-hook-form'; +import Textarea from './Textarea'; +import type { BaseFieldProps } from './BaseFieldContainer'; + +const ControlledTextarea = ({ + name, + ...restProps +}: Pick, 'name' | 'label'>) => { + const { control } = useFormContext(); + + return ( + ( +