Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions src/components/common/Post/PostBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ const PostBody = ({
feedId,
contentUrls = [],
}: PostBodyProps) => {
const navigate = useNavigate();
const hasImage = contentUrls.length > 0;
const previewImages = contentUrls.slice(0, 3);
const hasImage = previewImages.length > 0;
const contentRef = useRef<HTMLDivElement>(null);
const [isTruncated, setIsTruncated] = useState(false);

Expand Down Expand Up @@ -44,8 +44,17 @@ const PostBody = ({
<ImageContainer>
{hasImage && (
<>
{contentUrls.map((src: string, i: number) => (
<img key={i} src={src} />
{previewImages.map((src: string, i: number) => (
<img
key={`${feedId}-${i}`}
src={src}
alt={`피드 이미지 ${i + 1}`}
loading="lazy"
decoding="async"
fetchPriority="low"
width={100}
height={100}
/>
))}
</>
)}
Expand Down
52 changes: 52 additions & 0 deletions src/components/feed/FollowList.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,30 @@ export const Container = styled.div`
.title {
display: flex;
flex-direction: row;
align-items: center;
gap: 4px;
color: var(--color-white);
font-size: ${typography.fontSize['2xs']};
font-weight: var(--font-weight-medium);
line-height: 20px;

img {
width: 14px;
height: 14px;
flex-shrink: 0;
}
}

.titleSkeletonIcon {
width: 14px;
height: 14px;
flex-shrink: 0;
}

.titleSkeletonText {
display: flex;
align-items: center;
height: 20px;
}
`;

Expand All @@ -26,6 +46,7 @@ export const FollowContainer = styled.div`
flex-direction: row;
justify-content: space-between;
align-items: center;
min-height: 58px;

img {
cursor: pointer;
Expand Down Expand Up @@ -73,6 +94,37 @@ export const FollowContainer = styled.div`
border: 0.5px solid #888;
}
}

.skeletonItem {
cursor: default;
}
}

.arrowSkeleton {
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
`;

export const FollowListLoading = styled.div`
display: flex;
align-items: center;
min-height: 58px;

.placeholderList {
display: flex;
gap: 12px;
}

.placeholderItem {
width: 36px;
height: 36px;
border-radius: 50%;
background-color: var(--color-darkgrey-dark);
}
`;

Expand Down
49 changes: 42 additions & 7 deletions src/components/feed/FollowList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,23 @@ import rightArrow from '../../assets/feed/rightArrow.svg';
import people from '../../assets/feed/people.svg';
import character from '../../assets/feed/character.svg';
import { getRecentFollowing, type RecentWriterData } from '@/api/users/getRecentFollowing';
import Skeleton from '@/shared/ui/Skeleton';
import { Container, FollowContainer, EmptyFollowerContainer } from './FollowList.styled';

const FollowList = () => {
interface FollowListProps {
onLoadingChange?: (loading: boolean) => void;
}

const FollowList = ({ onLoadingChange }: FollowListProps) => {
const navigate = useNavigate();
const [myFollowings, setMyFollowings] = useState<RecentWriterData[]>([]);
const [loading, setLoading] = useState(true);

const fetchRecentFollowing = async () => {
try {
setLoading(true);
const response = await getRecentFollowing();
const minLoadingTime = new Promise(resolve => setTimeout(resolve, 500));
const [response] = await Promise.all([getRecentFollowing(), minLoadingTime]);

if (response.isSuccess) {
setMyFollowings(response.data.myFollowingUsers);
Expand All @@ -33,6 +39,10 @@ const FollowList = () => {
fetchRecentFollowing();
}, []);

useEffect(() => {
onLoadingChange?.(loading);
}, [loading, onLoadingChange]);

const hasFollowers = myFollowings.length > 0;
const visible = hasFollowers ? myFollowings.slice(0, 10) : [];

Expand All @@ -50,12 +60,37 @@ const FollowList = () => {

return (
<Container>
<div className="title">
<img src={people} />
<div>내 띱</div>
</div>
{loading ? (
<></>
<div className="title">
<div className="titleSkeletonIcon">
<Skeleton.Box width={14} height={14} />
</div>
<div className="titleSkeletonText">
<Skeleton.Text width={24} height={12} />
</div>
</div>
) : (
<div className="title">
<img src={people} alt="내 띱" />
<div>내 띱</div>
</div>
)}
{loading ? (
<FollowContainer>
<div className="followerList">
{Array.from({ length: 6 }).map((_, i) => (
<div className="followers skeletonItem" key={i}>
<Skeleton.Circle width={36} />
<div className="username skeletonUsername">
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

skeletonUsername 클래스가 styled 파일에 정의되어 있지 않습니다.

skeletonUsername 클래스가 사용되었지만 FollowList.styled.ts에는 .skeletonItem만 정의되어 있습니다. 현재는 스타일링에 영향이 없지만, 의도된 스타일이 있다면 추가하거나 불필요하다면 클래스를 제거하세요.

🔧 수정 제안
               <div className="followers skeletonItem" key={i}>
                 <Skeleton.Circle width={36} />
-                <div className="username skeletonUsername">
+                <div className="username">
                   <Skeleton.Text width={30} height={10} />
                 </div>
               </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="username skeletonUsername">
<div className="followers skeletonItem" key={i}>
<Skeleton.Circle width={36} />
<div className="username">
<Skeleton.Text width={30} height={10} />
</div>
</div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/feed/FollowList.tsx` at line 84, FollowList.tsx uses a CSS
class "skeletonUsername" that isn't defined in FollowList.styled.ts (which only
defines .skeletonItem); either remove the unused class from the JSX or add a
matching style rule in FollowList.styled.ts (e.g., define .skeletonUsername with
the intended sizing/animation and ensure it's exported/available to the
component), or change the JSX to use the existing .skeletonItem class if that
was the intended target (update the div className accordingly in
FollowList.tsx).

<Skeleton.Text width={30} height={10} />
</div>
</div>
))}
</div>
<div className="arrowSkeleton">
<Skeleton.Box width={16} height={16} />
</div>
</FollowContainer>
) : hasFollowers ? (
<FollowContainer>
<div className="followerList">
Expand Down
1 change: 0 additions & 1 deletion src/components/feed/MyFeed.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { colors, typography } from '@/styles/global/global';

export const Container = styled.div`
min-height: 100vh;
padding-top: 136px;
padding-bottom: 125px; //이전 76px
background-color: var(--color-black-main);
`;
Expand Down
2 changes: 1 addition & 1 deletion src/components/feed/TotalFeed.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { colors, typography } from '@/styles/global/global';

export const Container = styled.div`
min-height: 100vh;
padding-top: 136px;
/* padding-top: 136px; */
padding-bottom: 125px; //이전 76px
background-color: var(--color-black-main);
`;
Expand Down
2 changes: 0 additions & 2 deletions src/components/feed/TotalFeed.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import FollowList from './FollowList';
import FeedPost from './FeedPost';
import type { FeedListProps } from '../../types/post';
import { Container, EmptyState } from './TotalFeed.styled';
Expand All @@ -8,7 +7,6 @@ const TotalFeed = ({ showHeader, posts = [], isTotalFeed, isLast = false }: Feed

return (
<Container>
<FollowList />
{hasPosts ? (
posts.map((post, index) => (
<FeedPost
Expand Down
2 changes: 1 addition & 1 deletion src/pages/feed/Feed.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import styled from '@emotion/styled';
export const Container = styled.div`
min-width: 320px;
max-width: 767px;
padding-top: 136px;
height: 100vh;
background-color: var(--color-black-main);
margin: 0 auto;
`;

export const SkeletonWrapper = styled.div`
min-height: 100vh;
padding-top: 136px;
padding-bottom: 125px;
background-color: var(--color-black-main);
`;
17 changes: 16 additions & 1 deletion src/pages/feed/Feed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import NavBar from '../../components/common/NavBar';
import TabBar from '../../components/feed/TabBar';
import MyFeed from '../../components/feed/MyFeed';
import TotalFeed from '../../components/feed/TotalFeed';
import FollowList from '../../components/feed/FollowList';
import MainHeader from '@/components/common/MainHeader';
import { FeedPostSkeleton, OtherFeedSkeleton } from '@/shared/ui/Skeleton';
import writefab from '../../assets/common/writefab.svg';
Expand All @@ -21,6 +22,10 @@ const tabs = ['피드', '내 피드'];
const Feed = () => {
const navigate = useNavigate();
const location = useLocation();
const initialTabFromState = (location.state as { initialTab?: string } | null)?.initialTab;
const [activeTab, setActiveTab] = useState<string>(initialTabFromState ?? tabs[0]);
const [isFollowListLoading, setIsFollowListLoading] = useState(activeTab === '피드');

const { waitForToken } = useSocialLoginToken();

const initialTabFromState = (location.state as { initialTab?: string } | null)?.initialTab;
Expand Down Expand Up @@ -85,8 +90,17 @@ const Feed = () => {
window.scrollTo(0, 0);
}, [activeTab]);

useEffect(() => {
if (activeTab === '피드') {
setIsFollowListLoading(true);
}
}, [activeTab]);

const currentFeed = activeTab === '피드' ? totalFeed : myFeed;
const showInitialLoading = currentFeed.isLoading && currentFeed.items.length === 0;
const showFeedInitialLoading = totalFeed.isLoading && totalFeed.items.length === 0;
const showMyFeedInitialLoading = myFeed.isLoading && myFeed.items.length === 0;
const showInitialLoading =
activeTab === '피드' ? showFeedInitialLoading || isFollowListLoading : showMyFeedInitialLoading;

return (
<Container>
Expand All @@ -96,6 +110,7 @@ const Feed = () => {
rightButtonClick={() => navigate('/notice')}
/>
<TabBar tabs={tabs} activeTab={activeTab} onTabClick={setActiveTab} />
{activeTab === '피드' && <FollowList onLoadingChange={setIsFollowListLoading} />}
{showInitialLoading ? (
activeTab === '내 피드' ? (
<OtherFeedSkeleton showFollowButton={false} paddingTop={136} />
Expand Down
2 changes: 1 addition & 1 deletion src/shared/ui/Skeleton/feed/OtherFeedSkeleton.styled.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import styled from '@emotion/styled';

export const SkeletonContainer = styled.div<{ paddingTop?: number }>`
padding-top: ${({ paddingTop }) => (paddingTop !== undefined ? `${paddingTop}px` : '56px')};
/* padding-top: ${({ paddingTop }) => (paddingTop !== undefined ? `${paddingTop}px` : '56px')}; */
background-color: var(--color-black-main);
min-height: 100vh;
`;
Expand Down