Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: connect paints, user api in web #237

Merged
merged 23 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
3 changes: 2 additions & 1 deletion src/web/src/@types/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface User {
nickname: string;
introduce: string;
websitePath: string;
joinedAt: Date;
joinedAt: string;
followerCount: number;
followingCount: number;
}
Expand All @@ -26,6 +26,7 @@ export type UserSearchResult = Pick<User, 'id' | 'username' | 'nickname'> & {
};
export type UserProfile = Pick<
User,
| 'id'
| 'backgroundImagePath'
| 'profileImagePath'
| 'nickname'
Expand Down
4 changes: 2 additions & 2 deletions src/web/src/@types/api/paint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface TimelineItem {
authorNickname: User['nickname'];
authorImagePath: User['profileImagePath'];
authorStatus: User['status'];
createdAt: Date;
createdAt: string;
text: string;
replyCount: number;
repaintCount: number;
Expand Down Expand Up @@ -39,7 +39,7 @@ export interface TimelineItem {
export interface EditPaint {
text: string;
medias: {
id: string;
path: string;
type: 'image' | 'video';
}[];
taggedUserIds: string[];
Expand Down
18 changes: 6 additions & 12 deletions src/web/src/api/AuthTokenStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,12 @@ import { generateLocalStorage } from '@/utils';
import { AuthenticationRequiredError } from './AuthenticationRequiredError';

export class AuthTokenStorage {
private readonly authTokenKey = '@@@authToken';

private readonly storage = generateLocalStorage<string | null>(
this.authTokenKey,
);

private authToken: string | null;

constructor() {
this.spawn();
this.authToken = null;
}
private storage: ReturnType<typeof generateLocalStorage<string | null>>;

spawn(): void {
constructor(key: string) {
this.storage = generateLocalStorage<string | null>(key);
this.authToken = this.storage.get();
}

Expand All @@ -41,4 +33,6 @@ export class AuthTokenStorage {
}
}

export const authTokenStorage = new AuthTokenStorage();
export const userIdStorage = generateLocalStorage<string>('@@@userId');
export const accessTokenStorage = new AuthTokenStorage('@@@accessToken');
export const refreshTokenStorage = new AuthTokenStorage('@@@refreshToken');
4 changes: 2 additions & 2 deletions src/web/src/api/apiFactory.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import axios from 'axios';

import { env } from '@/constants';
import { authTokenStorage } from './AuthTokenStorage';
import { accessTokenStorage } from './AuthTokenStorage';

export const createApiClient = ({ auth }: { auth: boolean }) => {
const client = axios.create({
Expand All @@ -10,7 +10,7 @@ export const createApiClient = ({ auth }: { auth: boolean }) => {

if (auth) {
client.interceptors.request.use((config) => {
const token = authTokenStorage.getTokenOrThrow();
const token = accessTokenStorage.getTokenOrThrow();
config.headers.Authorization = `Bearer ${token}`;
return config;
});
Expand Down
96 changes: 95 additions & 1 deletion src/web/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { env } from '@/constants';
import { createApiWrappers } from './handler';
import type {
EditPaint,
JoinInfo,
LoginInfo,
TimelineItem,
User,
UserProfile,
UserSearchResult,
Expand All @@ -18,7 +20,13 @@ const client = {
const auth = createApiWrappers({
verifyEmailCode: (request: { email: User['email']; payload: string }) =>
client.public.post('/auth', request),
login: (request: LoginInfo) => client.public.post('/auth/web', request),
reSendEmailCode: (request: { email: User['email'] }) =>
client.public.post('/auth/resend', request),
login: (request: LoginInfo) =>
client.public.post<{ accessToken: string; refreshToken: string }>(
'/auth/mobile',
request,
),
logout: () => client.private.post('/auth/web-logout'),
});

Expand Down Expand Up @@ -69,6 +77,71 @@ const users = createApiWrappers({
| 'websitePath'
> & { userId: User['id'] }
>('/users/profile', request),
getUserPaints: (userId: User['id']) =>
client.private.get<TimelineItem[]>(`/users/${userId}/paint`),
getUserReplyPaints: (userId: User['id']) =>
client.private.get<TimelineItem[]>(`/users/${userId}/reply`),
getUserMediaPaints: (userId: User['id']) =>
client.private.get<TimelineItem[]>(`/users/${userId}/media`),
getUserLikePaints: (userId: User['id']) =>
client.private.get<TimelineItem[]>(`/users/${userId}/heart`),
followUser: (userId: User['id']) =>
client.private.post(`/users/${userId}/follow`),
unFollowUser: (userId: User['id']) =>
client.private.delete(`/users/${userId}/follow`),
likePaint: ({
userId,
paintId,
}: {
userId: User['id'];
paintId: TimelineItem['id'];
}) =>
client.private.post<{ paintId: TimelineItem['id'] }>(
`/users/${userId}/like`,
{ paintId },
),
disLikePaint: ({
userId,
paintId,
}: {
userId: User['id'];
paintId: TimelineItem['id'];
}) =>
client.private.delete<{ paintId: TimelineItem['id'] }>(
`/users/${userId}/like/${paintId}`,
),
rePaint: ({
userId,
paintId,
}: {
userId: User['id'];
paintId: TimelineItem['id'];
}) =>
client.private.post<{ paintId: TimelineItem['id'] }>(
`/users/${userId}/repaint`,
{ paintId },
),
markPaint: ({
userId,
paintId,
}: {
userId: User['id'];
paintId: TimelineItem['id'];
}) =>
client.private.post<{ paintId: TimelineItem['id'] }>(
`/users/${userId}/mark`,
{ paintId },
),
unMarkPaint: ({
userId,
paintId,
}: {
userId: User['id'];
paintId: TimelineItem['id'];
}) =>
client.private.delete<{ paintId: TimelineItem['id'] }>(
`/users/${userId}/mark/${paintId}`,
),
});

const images = createApiWrappers({
Expand Down Expand Up @@ -115,8 +188,29 @@ const images = createApiWrappers({
},
});

const paints = createApiWrappers({
getPaintById: (paintId: TimelineItem['id']) =>
client.private.get<TimelineItem>(`/paints/${paintId}`),
getBeforePaintsById: (paintId: TimelineItem['id']) =>
client.private.get<TimelineItem[]>(`/paints/${paintId}/before`),
getAfterPaintsById: (paintId: TimelineItem['id']) =>
client.private.get<TimelineItem[]>(`/paints/${paintId}/after`),
getPaints: (paintId: TimelineItem['id']) =>
client.private.get<TimelineItem[]>(`/paints/${paintId}`),
createPaint: (request: EditPaint) => client.private.post('/paints', request),
getQuotePaintList: (paintId: TimelineItem['id']) =>
client.private.get<
(Pick<TimelineItem, 'authorId' | 'createdAt' | 'id' | 'text'> & {
includes: {
paints: Pick<TimelineItem, 'authorId' | 'createdAt' | 'id' | 'text'>;
};
})[]
>(`/paints/${paintId}/quote-paints`),
});

export const apis = {
auth,
users,
images,
paints,
} as const;
7 changes: 4 additions & 3 deletions src/web/src/components/AfterTimelineList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import type { ForwardedRef } from 'react';
import { useNavigate } from '@tanstack/react-router';
import { useSuspenseQuery } from '@tanstack/react-query';

import { apis } from '@/api';
import { cn } from '@/utils';
import type { PaintAction } from '@/hooks';
import { postDetailRoute } from '@/routes';
import { cn, fetchAfterPost } from '@/utils';
import TimelineItemBox from './TimelineItemBox';

interface AfterTimelineListProps {
Expand All @@ -19,7 +20,7 @@ const AfterTimelineList = forwardRef<HTMLDivElement, AfterTimelineListProps>(
const params = postDetailRoute.useParams();
const { data: posts } = useSuspenseQuery({
queryKey: ['post', params.postId, 'before'],
queryFn: fetchAfterPost,
queryFn: () => apis.paints.getBeforePaintsById(params.postId),
});

if (Array.isArray(posts) && posts.length === 0) {
Expand Down Expand Up @@ -50,7 +51,7 @@ const AfterTimelineList = forwardRef<HTMLDivElement, AfterTimelineListProps>(
})
}
onClickRetweet={() => paintAction.onClickRetweet(post.id)}
onClickHeart={() => paintAction.onClickHeart(post.id)}
onClickHeart={() => paintAction.onClickHeart(post.id, post.like)}
onClickViews={() => paintAction.onClickViews(post.id)}
onClickShare={() => paintAction.onClickShare(post.id)}
onClickMore={() => paintAction.onClickMore(post.id)}
Expand Down
7 changes: 4 additions & 3 deletions src/web/src/components/BeforeTimelineList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import type { ForwardedRef, RefObject } from 'react';
import { useNavigate } from '@tanstack/react-router';
import { useSuspenseQuery } from '@tanstack/react-query';

import { cn } from '@/utils';
import { apis } from '@/api';
import type { PaintAction } from '@/hooks';
import { postDetailRoute } from '@/routes';
import { cn, fetchBeforePost } from '@/utils';
import TimelineItemBox from './TimelineItemBox';

interface BeforeTimelineListProps {
Expand All @@ -25,7 +26,7 @@ const BeforeTimelineList = forwardRef<HTMLDivElement, BeforeTimelineListProps>(
const params = postDetailRoute.useParams();
const { data: posts, isSuccess } = useSuspenseQuery({
queryKey: ['post', params.postId, 'before'],
queryFn: fetchBeforePost,
queryFn: () => apis.paints.getBeforePaintsById(params.postId),
});

if (Array.isArray(posts) && posts.length === 0) {
Expand Down Expand Up @@ -64,7 +65,7 @@ const BeforeTimelineList = forwardRef<HTMLDivElement, BeforeTimelineListProps>(
})
}
onClickRetweet={() => paintAction.onClickRetweet(post.id)}
onClickHeart={() => paintAction.onClickHeart(post.id)}
onClickHeart={() => paintAction.onClickHeart(post.id, post.like)}
onClickViews={() => paintAction.onClickViews(post.id)}
onClickShare={() => paintAction.onClickShare(post.id)}
onClickMore={() => paintAction.onClickMore(post.id)}
Expand Down
7 changes: 2 additions & 5 deletions src/web/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { memo, useEffect, useState } from 'react';
import { cva } from 'class-variance-authority';
import type { MouseEvent, MouseEventHandler, ReactNode } from 'react';

import { DUMMY_USER, cn } from '@/utils';
import { cn } from '@/utils';
import MenuModal from './MenuModal';
import Typography from './common/Typography';
import type { IconKeyType } from './common/Icon';
Expand Down Expand Up @@ -148,10 +148,7 @@ function Header({ left, center, right, position, className }: HeaderProps) {
<HeaderButton header={right} align="end" />
</header>
{isProfile && isProfileModalOpen && (
<MenuModal
user={DUMMY_USER}
onClose={() => setIsProfileModalOpen(false)}
/>
<MenuModal onClose={() => setIsProfileModalOpen(false)} />
)}
</>
);
Expand Down
9 changes: 5 additions & 4 deletions src/web/src/components/MainPostBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import type { ForwardedRef } from 'react';
import { useNavigate } from '@tanstack/react-router';
import { useSuspenseQuery } from '@tanstack/react-query';

import { apis } from '@/api';
import { Button } from './common';
import type { PaintAction } from '@/hooks';
import { postDetailRoute } from '@/routes';
import QuotePostBox from './QuotePostBox';
import Typography from './common/Typography';
import { cn, forCloudinaryImage } from '@/utils';
import TimelineItemMenu from './TimelineItemMenu';
import AccessibleIconButton from './AccessibleIconButton';
import { cn, fetchMainPost, forCloudinaryImage } from '@/utils';

interface MainPostBoxProps {
className?: string;
Expand All @@ -24,7 +25,7 @@ const MainPostBox = forwardRef<HTMLDivElement, MainPostBoxProps>(
const params = postDetailRoute.useParams();
const { data: post } = useSuspenseQuery({
queryKey: ['post', params.postId],
queryFn: fetchMainPost,
queryFn: () => apis.paints.getPaintById(params.postId),
});
const hasMedia = post?.includes.medias.length > 0;

Expand Down Expand Up @@ -115,7 +116,7 @@ const MainPostBox = forwardRef<HTMLDivElement, MainPostBoxProps>(

<div className="flex flex-col gap-[16px] divide-y divide-y-blueGrey400">
<Typography as="span" size="body-2" color="blueGrey-800">
{post.createdAt.toDateString()} ·
{new Date(post.createdAt).toDateString()} ·
<Typography
as="span"
size="headline-8"
Expand Down Expand Up @@ -237,7 +238,7 @@ const MainPostBox = forwardRef<HTMLDivElement, MainPostBoxProps>(
iconType={post.like ? 'solidHeart' : 'heart'}
label="마음에 들어요 누르기"
className="transition-colors hover:bg-grey-200 rounded-full p-1"
onClick={() => paintAction.onClickHeart(post.id)}
onClick={() => paintAction.onClickHeart(post.id, post.like)}
/>
</div>
<div className="flex gap-[4px] items-center">
Expand Down
Loading
Loading