Skip to content
Merged
15 changes: 15 additions & 0 deletions src/components/ui/icons/UIIcons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -541,3 +541,18 @@ export const CollapseIcon = ({ className = 'w-5 h-5' }: IconProps) => {
</svg>
);
};

export const PencilIcon = ({ className = 'w-5 h-5' }: IconProps) => {
return (
<svg
className={className}
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
>
<g>
<path d="M23 3c-6.62-.1-10.38 2.421-13.05 6.03C7.29 12.61 6 17.331 6 22h2c0-1.007.07-2.012.19-3H12c4.1 0 7.48-3.082 7.94-7.054C22.79 10.147 23.17 6.359 23 3zm-7 8h-1.5v2H16c.63-.016 1.2-.08 1.72-.188C16.95 15.24 14.68 17 12 17H8.55c.57-2.512 1.57-4.851 3-6.78 2.16-2.912 5.29-4.911 9.45-5.187C20.95 8.079 19.9 11 16 11zM4 9V6H1V4h3V1h2v3h3v2H6v3H4z"></path>
</g>
</svg>
);
};
1 change: 1 addition & 0 deletions src/components/ui/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export {
JoinDateIcon,
CameraIcon,
SettingsIcon,
PencilIcon,
} from './UIIcons';

// Brand Icons
Expand Down
2 changes: 1 addition & 1 deletion src/features/authentication/configs/authFormConfigs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export const authFormConfigs = {
},

signup: {
title: 'Join X today',
title: 'Join Hankers today',
fields: [],
socialProviders: [
{
Expand Down
2 changes: 1 addition & 1 deletion src/features/authentication/constants/index.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ describe('useAuth Hooks', () => {

await act(async () => {
await result.current.changePassword({
currentPassword: 'old',
oldPassword: 'old',
newPassword: 'new',
});
});
Expand Down
2 changes: 1 addition & 1 deletion src/features/layout/components/GrokSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ function SummarySubTweet({
function SummaryButton({ setOpened }: { setOpened: (val: boolean) => void }) {
return (
<button
className="fixed bottom-20 right-8 z-50 w-14 h-14 bg-black rounded-2xl flex items-center justify-center border border-gray-600 hover:bg-[#101a2b]"
className="hidden sm:block fixed bottom-20 right-8 z-50 w-14 h-14 bg-black rounded-2xl flex items-center justify-center border border-gray-600 hover:bg-[#101a2b]"
onClick={() => setOpened(true)}
>
<div className="flex items-center justify-center">
Expand Down
2 changes: 2 additions & 0 deletions src/features/layout/components/LayoutWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import RightSidebar from './RightSidebar';
import MobileBottomBar from './MobileBottomBar';
import { Toaster } from 'react-hot-toast';
import GrokSummary from './GrokSummary';
import MobilePostButton from './MobilePostButton';
interface LayoutWrapperProps {
children: React.ReactNode;
showRightSidebar?: boolean;
Expand All @@ -29,6 +30,7 @@ export default function LayoutWrapper({
{showMobileBottomBar && (
<div className="sm:hidden">
<MobileBottomBar />
{showRightSidebar && <MobilePostButton />}
</div>
)}

Expand Down
26 changes: 26 additions & 0 deletions src/features/layout/components/MobilePostButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use client';
import { useState } from 'react';
import ComposeModal from '@/features/timeline/components/ComposeModal';
import { PencilIcon } from '@/components/ui/icons';

export default function MobilePostButton() {
const [isComposeOpen, setIsComposeOpen] = useState(false);

return (
<>
<button
data-testid="mobile-post-button"
onClick={() => setIsComposeOpen(true)}
className="fixed bottom-20 right-4 bg-primary hover:bg-primary-hover text-white font-bold rounded-full transition-colors w-14 h-14 flex items-center justify-center shadow-lg z-40"
aria-label="Create post"
>
<PencilIcon className="w-6 h-6 text-white" />
</button>

<ComposeModal
isOpen={isComposeOpen}
onClose={() => setIsComposeOpen(false)}
/>
</>
);
}
16 changes: 10 additions & 6 deletions src/features/layout/components/MobileSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,24 +74,28 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
</p>
</div>
<div className="flex items-center gap-4 text-sm">
<div
className="flex items-center gap-1"
<Link
href={`/${user?.username}/following`}
className="flex items-center gap-1 hover:underline"
data-testid="mobile-sidebar-following"
onClick={onClose}
>
<span className="font-bold text-white">
{profile?.following_count ?? 0}
</span>
<span className="text-gray-400">Following</span>
</div>
<div
className="flex items-center gap-1"
</Link>
<Link
href={`/${user?.username}/followers`}
className="flex items-center gap-1 hover:underline"
data-testid="mobile-sidebar-followers"
onClick={onClose}
>
<span className="font-bold text-white">
{profile?.followers_count ?? 0}
</span>
<span className="text-gray-400">Followers</span>
</div>
</Link>
</div>
</div>

Expand Down
4 changes: 2 additions & 2 deletions src/features/layout/components/PostButton.tsx
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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+ */}
<Plus className="w-6 h-6 min-[1400px]:hidden" strokeWidth={3} />
<PencilIcon className="w-6 h-6 min-[1400px]:hidden" />
<span className="hidden min-[1400px]:inline text-lg">Post</span>
</button>

Expand Down
2 changes: 1 addition & 1 deletion src/features/onboarding/components/InterestsModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});

Expand Down
6 changes: 3 additions & 3 deletions src/features/onboarding/components/InterestsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ export default function InterestsModal({
{/* Header */}
<div className="px-8 pt-5 pb-4">
<h2 className="text-[31px] font-bold text-foreground mb-2 leading-9">
What do you want to see on X?
What do you want to see on Hankers?
</h2>
<p className="text-text-inactive text-[15px] leading-5">
Choose what you like, and we&apos;ll customise your X experience
with more of what you&apos;re interested in.
Choose what you like, and we&apos;ll customise your Hankers
experience with more of what you&apos;re interested in.
</p>
</div>

Expand Down
5 changes: 3 additions & 2 deletions src/features/settings/components/SettingsDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -46,7 +47,7 @@ export default function SettingsDetail({
const handleBack = () => {
router.push('/settings');
};

const { user } = useAuth();
if (!selectedOption) {
return (
<div
Expand All @@ -66,7 +67,7 @@ export default function SettingsDetail({
<div className="">
<Breadcrumb
title={selectedOption.label}
subtitle="@ahmedfathy0-0"
subtitle={user?.username}
description={selectedOption.description}
onBack={handleBack}
data-testid="settings-detail-breadcrumb"
Expand Down
14 changes: 12 additions & 2 deletions src/features/settings/components/__tests__/SettingsDetail.test.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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) => (
Expand Down Expand Up @@ -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'
Expand Down
66 changes: 6 additions & 60 deletions src/features/timeline/components/ComposeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,78 +10,24 @@ 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<HTMLDivElement>(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();
};

return (
<XModal
isOpen={isOpen}
onClose={handleClose}
size="xl"
overlayColor="bg-[rgba(91,112,131,0.4)]"
size="4xl"
title=""
padding={false}
showCloseButton={true}
showLogo={false}
padding={false}
>
<AddTweet type={ADD_TWEET.POST} />
<div className="pt-14 px-5">
<AddTweet type={ADD_TWEET.POST} />
</div>
</XModal>
);
}
2 changes: 1 addition & 1 deletion src/features/timeline/components/ShowTweets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
</div>
);
}
2 changes: 1 addition & 1 deletion src/features/timeline/components/Timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export default function Timeline() {
className="flex flex-col justify-items-center full-width relative"
data-testid="timeline-content"
>
<div ref={topRef}>
<div ref={topRef} className="sm:block hidden">
<AddTweet type={ADD_TWEET.POST} persistent={true} />
</div>
{/* <ShowTweets /> */}
Expand Down
2 changes: 1 addition & 1 deletion src/features/tweets/components/Tweet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ export default function Tweet({
}
: undefined;

const deleteTweetMutation = useDeleteTweet(
const deleteTweetMutation: ReturnType<typeof useDeleteTweet> = useDeleteTweet(
dataViewd.postId,
actionsStats.isRepost,
actionsStats.userId,
Expand Down
7 changes: 6 additions & 1 deletion src/features/tweets/components/UserInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,12 @@ export default function UserInfo({
}, leaveDelay);
}}
>
{data.username}
<span className="hidden xs:inline">{data.username}</span>
<span className="inline xs:hidden">
{data.username.length > 10
? `${data.username.slice(0, 10)}...`
: data.username}
</span>
</span>
</Link>
{usernameCardShow && (
Expand Down
2 changes: 1 addition & 1 deletion src/features/tweets/tests/Tweet.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});

Expand Down
2 changes: 1 addition & 1 deletion src/features/tweets/tests/UserInfo.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe('UserInfo Component', () => {
render(<UserInfo data={mockUser} />);

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', () => {
Expand Down
Loading