Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
e502b95
feat(#15): Add WalletHistoryPage route to the application for user wa…
yeomin4242 Oct 31, 2025
c120a3d
feat(#16): Integrate useFollowers hook for dynamic follower data fetc…
yeomin4242 Oct 31, 2025
507b66c
feat(#16): Add GraphQL query for fetching game rankings with user det…
yeomin4242 Oct 31, 2025
3bf5d81
feat(#16): Add GraphQL query for fetching wallet transactions with pa…
yeomin4242 Oct 31, 2025
a15deee
feat(#16): Refactor Ranking component to use GraphQL for fetching ran…
yeomin4242 Oct 31, 2025
305f8ea
feat(#16): Implement follower data fetching with useFollowers hook an…
yeomin4242 Oct 31, 2025
a9bac90
feat(#16): Add follow/unfollow functionality to UserItem component us…
yeomin4242 Oct 31, 2025
7454a46
feat(#16): Refactor FollowingPage to use useFollowing hook for data f…
yeomin4242 Oct 31, 2025
213eac5
feat(#16): Enhance UserItem component to handle unfollow action with …
yeomin4242 Oct 31, 2025
f47891f
feat(#15): Update StorePage component to use functional component typ…
yeomin4242 Oct 31, 2025
79de1b1
feat(#15): Implement WalletHistoryPage component for displaying user …
yeomin4242 Oct 31, 2025
3c0e056
fix(#15): Correct typo in Payment Management requirements section of …
yeomin4242 Oct 31, 2025
6017e36
feat(#8): Add EditProfileModal component for updating user nickname a…
yeomin4242 Oct 31, 2025
3486ff8
test(#8): Add unit tests for MyPage component to verify Edit Profile …
yeomin4242 Oct 31, 2025
f6471fc
test(#9): Enhance FollowerPage tests with search functionality and im…
yeomin4242 Oct 31, 2025
aa2b0af
test(#9): Add search query tests to FollowingPage and improve renderi…
yeomin4242 Oct 31, 2025
49affe4
test(#8): Refactor MyPage tests by removing redundant imports and sim…
yeomin4242 Oct 31, 2025
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
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,17 +120,17 @@

#### 2.2 Payment Management (PF)

| No. | Requirement Name | Requirement Details | Priority |
| ---- | ------------------------------- | ---------------------------------------------- | -------- |
| PF01 | Prepare Payment | Prepare payment using PortOne API | Required |
| PF02 | Verify Payment & Grant Currency | Verify payment and grant in-game currency | Required |
| PF03 | Receive PortOne Webhook | Receive re-verification webhook from PortOne | Required |
| PF04 | View Purchase History | View user's own purchase history | Required |
| PF05 | View Purchase Details | View detailed purchase information | Required |
| PF06 | Request Refund | Request refund for purchased currency | Required |
| PF07 | View Refund History | View user's refund history | Required |
| PF08 | Check Refund Eligibility | Check if purchase is eligible for refund | Required |
| PF09 | Payment Failure Notification | Alert users of payment failures (errors, etc.) | Required |
| No. | Requirement Name | Requirement Details | Priority |
| ---- | ------------------------------ | ---------------------------------------------- | -------- |
| PF01 | Prepare Payment | Prepare payment using PortOne API | Required |
| PF02 | Verify Payment & lang Currency | Verify payment and lang in-game currency | Required |
| PF03 | Receive PortOne Webhook | Receive re-verification webhook from PortOne | Required |
| PF04 | View Purchase History | View user's own purchase history | Required |
| PF05 | View Purchase Details | View detailed purchase information | Required |
| PF06 | Request Refund | Request refund for purchased currency | Required |
| PF07 | View Refund History | View user's refund history | Required |
| PF08 | Check Refund Eligibility | Check if purchase is eligible for refund | Required |
| PF09 | Payment Failure Notification | Alert users of payment failures (errors, etc.) | Required |

**Key Features**:

Expand Down
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import StorePurchaseSuccessPage from "./pages/store/StorePurchaseSuccessPage";
import StorePurchaseFailurePage from "./pages/store/StorePurchaseFailurePage";
import FollowerPage from "./pages/follower/FollowerPage";
import FollowingPage from "./pages/following/FollowingPage";
import WalletHistoryPage from "./pages/wallet/WalletHistoryPage";

function App() {
const userType = useAuthStore((state: any) => state.user?.userType);
Expand Down Expand Up @@ -71,6 +72,7 @@ function App() {
<Route path="/store/success" element={<StorePurchaseSuccessPage />} />
<Route path="/store/failure" element={<StorePurchaseFailurePage />} />
<Route path="/language" element={<LanguageStorePage />} />
<Route path="/wallet/history" element={<WalletHistoryPage />} />

{/* 공개 라우트로 전환: 메인 페이지는 인증 없이 접근 가능 */}
<Route path="/main/*" element={<MainPage />} />
Expand Down
37 changes: 37 additions & 0 deletions src/features/products/graphql/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { gql } from "@apollo/client";

export const GET_PRODUCTS = gql`
query GetProducts($page: Int, $limit: Int) {
products(page: $page, limit: $limit) {
items {
id
name
description
price
bonus
amount
currency
}
pageInfo {
page
limit
total
totalPages
}
}
}
`;

export const GET_PRODUCT = gql`
query GetProduct($id: ID!) {
product(id: $id) {
id
name
description
price
bonus
amount
currency
}
}
`;
20 changes: 20 additions & 0 deletions src/features/ranking/graphql/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { gql } from "@apollo/client";

export const GET_RANKINGS = gql`
query GetRankings($game: String!, $period: String!, $limit: Int) {
rankings(game: $game, period: $period, limit: $limit) {
items {
userId
nickname
score
typingSpeed
rank
}
myRank {
rank
score
typingSpeed
}
}
}
`;
35 changes: 35 additions & 0 deletions src/features/wallet/graphql/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { gql } from "@apollo/client";

export const GET_WALLET_TRANSACTIONS = gql`
query GetWalletTransactions(
$page: Int
$limit: Int
$type: String
$from: String
$to: String
) {
walletTransactions(
page: $page
limit: $limit
type: $type
from: $from
to: $to
) {
items {
id
type
amount
balanceAfter
createdAt
description
}
pageInfo {
page
limit
total
totalPages
hasNextPage
}
}
}
`;
39 changes: 35 additions & 4 deletions src/pages/follower/FollowerPage.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React from "react";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom";
import { MockedProvider } from "@apollo/client/testing/react";
import { BrowserRouter } from "react-router-dom";
import { describe, it, expect, vi } from "vitest";
import { describe, it, expect, vi, beforeEach } from "vitest";
import FollowerPage from "./FollowerPage";
import { GET_FOLLOWERS } from "@/features/user/graphql/follow-operations";
import { GET_FOLLOWERS } from "../../features/user/graphql/follow-operations";

// Mock the Header component
vi.mock("@/components/common/Header", () => ({
Expand Down Expand Up @@ -78,6 +79,32 @@ const mocks = [
data: mockFollowersData,
},
},
// Search query: "Esthera"
{
request: {
query: GET_FOLLOWERS,
variables: {
page: 1,
limit: 20,
search: "Esthera",
},
},
result: {
data: {
followers: {
items: [mockFollowersData.followers.items[0]],
pageInfo: {
page: 1,
limit: 20,
total: 1,
totalPages: 1,
hasNextPage: false,
hasPreviousPage: false,
},
},
},
},
},
];

describe("FollowerPage", () => {
Expand All @@ -87,6 +114,7 @@ describe("FollowerPage", () => {

it("renders follower page with modal", async () => {
render(
// @ts-expect-error addTypename is supported at runtime in our Apollo version
<MockedProvider mocks={mocks} addTypename={false}>
<BrowserRouter>
<FollowerPage />
Expand All @@ -106,12 +134,13 @@ describe("FollowerPage", () => {
expect(screen.getByPlaceholderText("Search")).toBeInTheDocument();

// Check if follower names are displayed
expect(screen.getByText("Esthera Jackson")).toBeInTheDocument();
expect(screen.getByText("Alexa Liras")).toBeInTheDocument();
await screen.findByText("Esthera Jackson");
await screen.findByText("Alexa Liras");
});

it("handles search functionality", async () => {
render(
// @ts-expect-error addTypename is supported at runtime in our Apollo version
<MockedProvider mocks={mocks} addTypename={false}>
<BrowserRouter>
<FollowerPage />
Expand All @@ -134,6 +163,7 @@ describe("FollowerPage", () => {

it("handles close button click", async () => {
render(
// @ts-expect-error addTypename is supported at runtime in our Apollo version
<MockedProvider mocks={mocks} addTypename={false}>
<BrowserRouter>
<FollowerPage />
Expand All @@ -156,6 +186,7 @@ describe("FollowerPage", () => {

it("navigates back to mypage when close button is clicked", async () => {
render(
// @ts-expect-error addTypename is supported at runtime in our Apollo version
<MockedProvider mocks={mocks} addTypename={false}>
<BrowserRouter>
<FollowerPage />
Expand Down
26 changes: 13 additions & 13 deletions src/pages/follower/FollowerPage.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import React, { useState, useCallback } from "react";
import { useNavigate } from "react-router-dom";
import { Box } from "../../../styled-system/jsx";
import board1bg from "@/assets/images/board1.jpg";
import Header from "@/components/common/Header";
import FollowerModal from "./components/FollowerModal";
import { mockFollowers } from "@/features/user/mock-data";
import multibg from "@/assets/images/multi_background.png";
import { useFollowers } from "./hooks/useFollowers";

const FollowerPage: React.FC = () => {
const navigate = useNavigate();
const [searchQuery, setSearchQuery] = useState<string>("");

// Filter followers based on search query
const filteredFollowers = mockFollowers.filter(
(follower) =>
follower.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
follower.email.toLowerCase().includes(searchQuery.toLowerCase())
const { data, loading, error, hasNextPage, loadMore, refetch } = useFollowers(
{
page: 1,
search: searchQuery,
limit: 20,
}
);

const handleSearch = useCallback((query: string) => {
Expand Down Expand Up @@ -80,15 +80,15 @@ const FollowerPage: React.FC = () => {
>
{/* Modal Container */}
<FollowerModal
followers={filteredFollowers}
loading={false}
error={null}
followers={data?.items || []}
loading={loading}
error={error}
searchQuery={searchQuery}
onSearch={handleSearch}
onLoadMore={() => {}}
hasNextPage={false}
onLoadMore={loadMore}
hasNextPage={hasNextPage}
onClose={handleClose}
onRefetch={() => {}}
onRefetch={refetch}
/>
</div>
</Box>
Expand Down
27 changes: 20 additions & 7 deletions src/pages/follower/components/UserItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import React, { useState, useCallback } from "react";
import { css } from "../../../../styled-system/css";
import { FollowerUser } from "@/features/user/types/follow-types";
import { useMutation } from "@apollo/client/react";
import {
FOLLOW_USER,
UNFOLLOW_USER,
} from "@/features/user/graphql/follow-operations";

interface UserItemProps {
user: FollowerUser;
Expand All @@ -9,18 +14,26 @@ interface UserItemProps {
const UserItem: React.FC<UserItemProps> = ({ user }) => {
const [isFollowing, setIsFollowing] = useState(user.isFollowing);
const [isLoading, setIsLoading] = useState(false);
const [followUser] = useMutation(FOLLOW_USER);
const [unfollowUser] = useMutation(UNFOLLOW_USER);

const handleFollowToggle = useCallback(async () => {
if (isLoading) return;

setIsLoading(true);

// Simulate API call delay
setTimeout(() => {
setIsFollowing(!isFollowing);
try {
if (isFollowing) {
await unfollowUser({ variables: { userId: user.id } });
setIsFollowing(false);
} else {
await followUser({ variables: { userId: user.id } });
setIsFollowing(true);
}
} catch {
// keep previous state on error
} finally {
setIsLoading(false);
}, 500);
}, [isFollowing, isLoading]);
}
}, [isFollowing, isLoading, followUser, unfollowUser, user.id]);

return (
<div
Expand Down
Loading
Loading