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/authentication/configs/authFormConfigs.tsx b/src/features/authentication/configs/authFormConfigs.tsx index db07ac69..39a58fb8 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/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/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/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/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 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 (
({ 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' 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/timeline/components/ShowTweets.tsx b/src/features/timeline/components/ShowTweets.tsx index c17b5928..94c73fb1 100644 --- a/src/features/timeline/components/ShowTweets.tsx +++ b/src/features/timeline/components/ShowTweets.tsx @@ -4,7 +4,7 @@ export default function ShowTweets() { data-testid="show-tweets-button" className=" flex h-12 w-full p-3 justify-center text-l text-primary border-b-1 border-border hover:cursor-pointer hover:bg-border" > - Show X posts + Show Hankers posts
); } 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/Tweet.tsx b/src/features/tweets/components/Tweet.tsx index 852d1e63..d436040a 100644 --- a/src/features/tweets/components/Tweet.tsx +++ b/src/features/tweets/components/Tweet.tsx @@ -188,7 +188,7 @@ export default function Tweet({ } : undefined; - const deleteTweetMutation = useDeleteTweet( + const deleteTweetMutation: ReturnType = useDeleteTweet( dataViewd.postId, actionsStats.isRepost, actionsStats.userId, 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', () => { 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);