From e3a2308d0eea0c81edc5b370bc253c16641df5e4 Mon Sep 17 00:00:00 2001 From: ahmedfathy0-0 Date: Mon, 15 Dec 2025 11:58:31 +0200 Subject: [PATCH 1/6] fix: update branding references from 'X' to 'Hankers' across multiple components --- src/features/authentication/configs/authFormConfigs.tsx | 2 +- src/features/authentication/constants/index.ts | 2 +- src/features/onboarding/components/InterestsModal.test.tsx | 2 +- src/features/onboarding/components/InterestsModal.tsx | 6 +++--- src/features/settings/components/SettingsDetail.tsx | 5 +++-- src/features/timeline/components/ShowTweets.tsx | 2 +- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/features/authentication/configs/authFormConfigs.tsx b/src/features/authentication/configs/authFormConfigs.tsx index 8a94aab7..dc1cb468 100644 --- a/src/features/authentication/configs/authFormConfigs.tsx +++ b/src/features/authentication/configs/authFormConfigs.tsx @@ -75,7 +75,7 @@ export const authFormConfigs = { }, signup: { - title: 'Join X today', + title: 'Join Hankers today', fields: [], socialProviders: [ { diff --git a/src/features/authentication/constants/index.ts b/src/features/authentication/constants/index.ts index 63aa1914..fcd96901 100644 --- a/src/features/authentication/constants/index.ts +++ b/src/features/authentication/constants/index.ts @@ -1,7 +1,7 @@ // Authentication feature constants export const FOOTER_LINKS = [ 'About', - 'Download the X app', + 'Download the Hankers app', 'Grok', 'Help Center', 'Terms of Service', diff --git a/src/features/onboarding/components/InterestsModal.test.tsx b/src/features/onboarding/components/InterestsModal.test.tsx index 342cfddf..6748d816 100644 --- a/src/features/onboarding/components/InterestsModal.test.tsx +++ b/src/features/onboarding/components/InterestsModal.test.tsx @@ -108,7 +108,7 @@ describe('InterestsModal', () => { expect(screen.getByTestId('x-modal')).toBeInTheDocument(); expect( - screen.getByText('What do you want to see on X?') + screen.getByText('What do you want to see on Hankers?') ).toBeInTheDocument(); }); diff --git a/src/features/onboarding/components/InterestsModal.tsx b/src/features/onboarding/components/InterestsModal.tsx index 7ec11755..5083736e 100644 --- a/src/features/onboarding/components/InterestsModal.tsx +++ b/src/features/onboarding/components/InterestsModal.tsx @@ -59,11 +59,11 @@ export default function InterestsModal({ {/* Header */}

- What do you want to see on X? + What do you want to see on Hankers?

- Choose what you like, and we'll customise your X experience - with more of what you're interested in. + Choose what you like, and we'll customise your Hankers + experience with more of what you're interested in.

diff --git a/src/features/settings/components/SettingsDetail.tsx b/src/features/settings/components/SettingsDetail.tsx index 263728ce..5e12ce10 100644 --- a/src/features/settings/components/SettingsDetail.tsx +++ b/src/features/settings/components/SettingsDetail.tsx @@ -12,6 +12,7 @@ import { MuteIcon, } from '@/components/ui/icons'; import type { SettingsOption } from '@/features/settings/constants/SETTINGs_ITEMS'; +import { useAuth } from '@/features/authentication/hooks/useAuth'; interface SettingsDetailProps { selectedOption: SettingsOption | null; @@ -46,7 +47,7 @@ export default function SettingsDetail({ const handleBack = () => { router.push('/settings'); }; - + const { user } = useAuth(); if (!selectedOption) { return (
- Show X posts + Show Hankers posts
); } From 29f562d3676b8c1b28ed933d4412048516694332 Mon Sep 17 00:00:00 2001 From: ahmedfathy0-0 Date: Mon, 15 Dec 2025 18:24:27 +0200 Subject: [PATCH 2/6] fix: improve modal styling and adjust password change parameter naming --- .../hooks/__tests__/useAuth.test.tsx | 2 +- .../timeline/components/ComposeModal.tsx | 66 ++----------------- src/features/tweets/components/Tweet.tsx | 6 +- src/utils/profileValidation.ts | 11 ++++ 4 files changed, 21 insertions(+), 64 deletions(-) diff --git a/src/features/authentication/hooks/__tests__/useAuth.test.tsx b/src/features/authentication/hooks/__tests__/useAuth.test.tsx index f2705f6b..b512d74e 100644 --- a/src/features/authentication/hooks/__tests__/useAuth.test.tsx +++ b/src/features/authentication/hooks/__tests__/useAuth.test.tsx @@ -462,7 +462,7 @@ describe('useAuth Hooks', () => { await act(async () => { await result.current.changePassword({ - currentPassword: 'old', + oldPassword: 'old', newPassword: 'new', }); }); diff --git a/src/features/timeline/components/ComposeModal.tsx b/src/features/timeline/components/ComposeModal.tsx index 0ab312ad..e34befc3 100644 --- a/src/features/timeline/components/ComposeModal.tsx +++ b/src/features/timeline/components/ComposeModal.tsx @@ -10,64 +10,7 @@ interface ComposeModalProps { } export default function ComposeModal({ isOpen, onClose }: ComposeModalProps) { - // const useStore = useMemo(() => createAddTweetStore(), []); - // const selectors = useMemo( - // () => createAddTweetSelectors(useStore), - // [useStore] - // ); - // const isSending = selectors.useIsSending(); - - // const { clearMedia, setTweetText } = selectors.useActions(); - // const ref = useRef(null); - // const wasSendingRef = useRef(false); - - // const hasText = selectors.useTweetText().length > 0 || false; - // const hasmMedia = selectors.useMedia().length > 0; - - // useEffect(() => { - // const unloadCallback = (event: BeforeUnloadEvent) => { - // if (hasText || hasmMedia) { - // console.log(event); - // event.preventDefault(); - // return ''; - // } - // }; - - // window.addEventListener('beforeunload', unloadCallback); - // return () => window.removeEventListener('beforeunload', unloadCallback); - // }, [hasText, hasmMedia]); - - // Track when sending starts - // useEffect(() => { - // if (isSending) { - // wasSendingRef.current = true; - // } - // }, [isSending]); - - // // Close modal after successful tweet send - // useEffect(() => { - // // Only close if we were sending and now we're done (tweet sent successfully) - // if ( - // wasSendingRef.current && - // !isSending && - // !hasText && - // !hasmMedia && - // isOpen - // ) { - // wasSendingRef.current = false; - // const timer = setTimeout(() => { - // onClose(); - // }, 300); - // return () => clearTimeout(timer); - // } - // }, [isSending, hasText, hasmMedia, isOpen, onClose]); - - // Clear draft when modal closes (if user cancels) const handleClose = () => { - // Clear the draft - // setTweetText(''); - // clearMedia(); - // wasSendingRef.current = false; onClose(); }; @@ -75,13 +18,16 @@ export default function ComposeModal({ isOpen, onClose }: ComposeModalProps) { - +
+ +
); } diff --git a/src/features/tweets/components/Tweet.tsx b/src/features/tweets/components/Tweet.tsx index bc4a7cc8..96fc089b 100644 --- a/src/features/tweets/components/Tweet.tsx +++ b/src/features/tweets/components/Tweet.tsx @@ -177,7 +177,7 @@ export default function Tweet({ } : undefined; - const deleteTweetMutation = useDeleteTweet( + const deleteTweetMutation: ReturnType = useDeleteTweet( dataViewd.postId, actionsStats.isRepost, actionsStats.userId, @@ -187,14 +187,14 @@ export default function Tweet({ const handleDropdownAction = async (key: string) => { switch (key) { case 'follow': - if (data.isFollowedByMe) { + if (dataViewd.isFollowedByMe) { await unfollowUser(dataViewd.userId); } else { await followUser(dataViewd.userId); } break; case 'mute': - if (data.isMutedByMe) { + if (dataViewd.isMutedByMe) { await unmuteUser(dataViewd.userId); } else { await muteUser(dataViewd.userId); diff --git a/src/utils/profileValidation.ts b/src/utils/profileValidation.ts index b4b2f8a8..f568ead2 100644 --- a/src/utils/profileValidation.ts +++ b/src/utils/profileValidation.ts @@ -42,6 +42,17 @@ export const validateDisplayName = (name: string): ValidationResult => { }; } + // Validate name format: only letters (any language), accent marks, spaces, hyphens, or apostrophes + // Reject emojis, numbers, or punctuation + const namePattern = /^[\p{L}\p{M}' -]+$/u; + if (!namePattern.test(trimmed)) { + return { + isValid: false, + error: + 'Name should only contain letters (from any language), accent marks, spaces, hyphens, or apostrophes', + }; + } + // Check if only emojis (no alphanumeric characters) // This regex matches if there are NO letters, numbers, or common punctuation const hasAlphanumeric = /[a-zA-Z0-9]/.test(trimmed); From 87de97f48480e51b3e42c776eb4ff9bc3e378786 Mon Sep 17 00:00:00 2001 From: ahmedfathy0-0 Date: Mon, 15 Dec 2025 18:47:00 +0200 Subject: [PATCH 3/6] refactor: add PencilIcon and integrate it into MobilePostButton and PostButton components --- src/components/ui/icons/UIIcons.tsx | 15 +++++++++++ src/components/ui/icons/index.ts | 1 + .../layout/components/GrokSummary.tsx | 2 +- .../layout/components/LayoutWrapper.tsx | 2 ++ .../layout/components/MobilePostButton.tsx | 26 +++++++++++++++++++ src/features/layout/components/PostButton.tsx | 4 +-- 6 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 src/features/layout/components/MobilePostButton.tsx diff --git a/src/components/ui/icons/UIIcons.tsx b/src/components/ui/icons/UIIcons.tsx index 7af95d76..7bc4cfd3 100644 --- a/src/components/ui/icons/UIIcons.tsx +++ b/src/components/ui/icons/UIIcons.tsx @@ -541,3 +541,18 @@ export const CollapseIcon = ({ className = 'w-5 h-5' }: IconProps) => { ); }; + +export const PencilIcon = ({ className = 'w-5 h-5' }: IconProps) => { + return ( + + ); +}; diff --git a/src/components/ui/icons/index.ts b/src/components/ui/icons/index.ts index 1dcf20f5..4126e935 100644 --- a/src/components/ui/icons/index.ts +++ b/src/components/ui/icons/index.ts @@ -45,6 +45,7 @@ export { JoinDateIcon, CameraIcon, SettingsIcon, + PencilIcon, } from './UIIcons'; // Brand Icons diff --git a/src/features/layout/components/GrokSummary.tsx b/src/features/layout/components/GrokSummary.tsx index 19557829..2d2d8a30 100644 --- a/src/features/layout/components/GrokSummary.tsx +++ b/src/features/layout/components/GrokSummary.tsx @@ -93,7 +93,7 @@ function SummarySubTweet({ function SummaryButton({ setOpened }: { setOpened: (val: boolean) => void }) { return ( + + setIsComposeOpen(false)} + /> + + ); +} diff --git a/src/features/layout/components/PostButton.tsx b/src/features/layout/components/PostButton.tsx index d8714c8d..ccd52e97 100644 --- a/src/features/layout/components/PostButton.tsx +++ b/src/features/layout/components/PostButton.tsx @@ -1,7 +1,7 @@ 'use client'; -import { Plus } from 'lucide-react'; import { useState } from 'react'; import ComposeModal from '@/features/timeline/components/ComposeModal'; +import { PencilIcon } from '@/components/ui/icons'; export default function PostButton() { const [isComposeOpen, setIsComposeOpen] = useState(false); @@ -14,7 +14,7 @@ export default function PostButton() { className="bg-white hover:bg-gray-200 text-black font-bold rounded-full transition-colors mt-4 w-14 h-14 min-[1400px]:w-full min-[1400px]:h-auto min-[1400px]:py-3 flex items-center justify-center" > {/* Show + icon on small screens, "Post" text at 1400px+ */} - + Post From c434e4757474d2541dc0bfc623dbdad577511842 Mon Sep 17 00:00:00 2001 From: ahmedfathy0-0 Date: Mon, 15 Dec 2025 18:49:19 +0200 Subject: [PATCH 4/6] fix: conditionally render MobilePostButton based on showRightSidebar prop --- src/features/layout/components/LayoutWrapper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/layout/components/LayoutWrapper.tsx b/src/features/layout/components/LayoutWrapper.tsx index 15e6cfc4..b4586c0a 100644 --- a/src/features/layout/components/LayoutWrapper.tsx +++ b/src/features/layout/components/LayoutWrapper.tsx @@ -30,7 +30,7 @@ export default function LayoutWrapper({ {showMobileBottomBar && (
- + {showRightSidebar && }
)} From 068fb77709a133e04a5ad9f19ef4ff3af6ab4788 Mon Sep 17 00:00:00 2001 From: ahmedfathy0-0 Date: Mon, 15 Dec 2025 19:08:25 +0200 Subject: [PATCH 5/6] fix: replace divs with Link components for following and followers in MobileSidebar --- src/features/layout/components/MobileSidebar.tsx | 16 ++++++++++------ .../components/__tests__/SettingsDetail.test.tsx | 14 ++++++++++++-- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/features/layout/components/MobileSidebar.tsx b/src/features/layout/components/MobileSidebar.tsx index b84062c7..4a59ddba 100644 --- a/src/features/layout/components/MobileSidebar.tsx +++ b/src/features/layout/components/MobileSidebar.tsx @@ -74,24 +74,28 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {

-
{profile?.following_count ?? 0} Following -
-
+ {profile?.followers_count ?? 0} Followers -
+
diff --git a/src/features/settings/components/__tests__/SettingsDetail.test.tsx b/src/features/settings/components/__tests__/SettingsDetail.test.tsx index 144abd6d..fa98c244 100644 --- a/src/features/settings/components/__tests__/SettingsDetail.test.tsx +++ b/src/features/settings/components/__tests__/SettingsDetail.test.tsx @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent } from '@/test/test-utils'; import SettingsDetail from '../SettingsDetail'; import { usePathname, useRouter } from 'next/navigation'; import type { SettingsOption } from '@/features/settings/constants/SETTINGs_ITEMS'; @@ -10,6 +10,16 @@ vi.mock('next/navigation', () => ({ useRouter: vi.fn(), })); +// Mock useAuth +vi.mock('@/features/authentication/hooks/useAuth', () => ({ + useAuth: vi.fn(() => ({ + user: { + username: 'ahmedfathy0-0', + email: 'ahmed@example.com', + }, + })), +})); + // Mock components vi.mock('@/components/ui/Breadcrumb', () => ({ default: ({ title, subtitle, description, onBack, ...props }: any) => ( @@ -142,7 +152,7 @@ describe('SettingsDetail', () => { 'Your Account' ); expect(screen.getByTestId('breadcrumb-subtitle')).toHaveTextContent( - '@ahmedfathy0-0' + 'ahmedfathy0-0' ); expect(screen.getByTestId('breadcrumb-description')).toHaveTextContent( 'Manage your account settings' From e494efa55b60af246ee63b4eecbae8df2f359128 Mon Sep 17 00:00:00 2001 From: ahmedfathy0-0 Date: Mon, 15 Dec 2025 19:57:37 +0200 Subject: [PATCH 6/6] fix: show AddTweet component for > sm view and enhance UserInfo username display --- src/features/timeline/components/Timeline.tsx | 2 +- src/features/tweets/components/UserInfo.tsx | 7 ++++++- src/features/tweets/tests/Tweet.test.tsx | 2 +- src/features/tweets/tests/UserInfo.test.tsx | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/features/timeline/components/Timeline.tsx b/src/features/timeline/components/Timeline.tsx index 7e3ca51f..dd1689a4 100644 --- a/src/features/timeline/components/Timeline.tsx +++ b/src/features/timeline/components/Timeline.tsx @@ -109,7 +109,7 @@ export default function Timeline() { className="flex flex-col justify-items-center full-width relative" data-testid="timeline-content" > -
+
{/* */} diff --git a/src/features/tweets/components/UserInfo.tsx b/src/features/tweets/components/UserInfo.tsx index 9c874d5b..1192fde2 100644 --- a/src/features/tweets/components/UserInfo.tsx +++ b/src/features/tweets/components/UserInfo.tsx @@ -141,7 +141,12 @@ export default function UserInfo({ }, leaveDelay); }} > - {data.username} + {data.username} + + {data.username.length > 10 + ? `${data.username.slice(0, 10)}...` + : data.username} + {usernameCardShow && ( diff --git a/src/features/tweets/tests/Tweet.test.tsx b/src/features/tweets/tests/Tweet.test.tsx index d4d8b2a3..4e7b3c3c 100644 --- a/src/features/tweets/tests/Tweet.test.tsx +++ b/src/features/tweets/tests/Tweet.test.tsx @@ -113,7 +113,7 @@ describe('Tweet Component', () => { ); expect(screen.getByText('Test User')).toBeInTheDocument(); - expect(screen.getByText('testuser')).toBeInTheDocument(); + expect(screen.getAllByText('testuser')[0]).toBeInTheDocument(); expect(screen.getByText('Test tweet text')).toBeInTheDocument(); }); diff --git a/src/features/tweets/tests/UserInfo.test.tsx b/src/features/tweets/tests/UserInfo.test.tsx index f47fda35..40384b05 100644 --- a/src/features/tweets/tests/UserInfo.test.tsx +++ b/src/features/tweets/tests/UserInfo.test.tsx @@ -22,7 +22,7 @@ describe('UserInfo Component', () => { render(); expect(screen.getByText('Test User')).toBeInTheDocument(); - expect(screen.getByText('testuser')).toBeInTheDocument(); + expect(screen.getAllByText('testuser')[0]).toBeInTheDocument(); }); it('should show verified icon for verified users', () => {