-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #155 from hyeon9782/main
feat: Next / TypeScript / React Query / Zustand / Vanilla Extract를 사용하여 Real World 1차 기능 구현을 완료합니다.
- Loading branch information
Showing
89 changed files
with
1,964 additions
and
997 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
name: Review Assign | ||
|
||
on: | ||
pull_request: | ||
types: [opened, ready_for_review] | ||
|
||
jobs: | ||
assign: | ||
permissions: | ||
actions: write | ||
checks: write | ||
contents: write | ||
deployments: write | ||
discussions: write | ||
issues: write | ||
id-token: read | ||
packages: write | ||
pages: write | ||
pull-requests: write | ||
repository-projects: write | ||
security-events: write | ||
statuses: write | ||
runs-on: ubuntu-latest | ||
steps: | ||
- if: github.base_ref == 'main' # base branch name is 'master' | ||
run: echo REVIEWERS=inseong-so >> $GITHUB_ENV | ||
- if: startsWith(github.base_ref, 'team1') | ||
run: echo REVIEWERS=headring, KimHunJin, hyjoong her0707 >> $GITHUB_ENV | ||
- if: startsWith(github.base_ref, 'team2') | ||
run: echo REVIEWERS=Bsfla, SeolJaeHyeok, choisy9619, kyung-jun >> $GITHUB_ENV | ||
- if: startsWith(github.base_ref, 'team3') | ||
run: echo REVIEWERS=sgsg9447, kingyong9169, 2dowon, jqkk >> $GITHUB_ENV | ||
- if: startsWith(github.base_ref, 'team4') | ||
run: echo REVIEWERS=kimseongchan-kr, cham0287, hyeon9782 >> $GITHUB_ENV | ||
- if: startsWith(github.base_ref, 'team5') | ||
run: echo REVIEWERS=2-NOW, hyew-kim, geeonie >> $GITHUB_ENV | ||
- if: startsWith(github.base_ref, 'team6') | ||
run: echo REVIEWERS=areumsheep, ludacirs, innocarpe >> $GITHUB_ENV | ||
- if: startsWith(github.base_ref, 'team7') | ||
run: echo REVIEWERS=endmoseung, steven-yn, ding-co, mandarin-sep >> $GITHUB_ENV | ||
- if: startsWith(github.base_ref, 'team8') | ||
run: echo REVIEWERS=HOJOON07, jiji-hoon96, 71summernight, seung-wan >> $GITHUB_ENV | ||
- if: startsWith(github.base_ref, 'team9') | ||
run: echo REVIEWERS=Siihyun, hhhminme, 0uizi0, brgndyy >> $GITHUB_ENV | ||
- if: startsWith(github.base_ref, 'team10') | ||
run: echo REVIEWERS=Leejha, steadily-worked >> $GITHUB_ENV | ||
- uses: hkusu/review-assign-action@v1 | ||
with: | ||
assignees: ${{ github.actor }} | ||
reviewers: ${{ env.REVIEWERS }} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,77 @@ | ||
# ![RealWorld Example App](./assets/logo.png) | ||
# Next World | ||
|
||
> ### [YOUR_FRAMEWORK] codebase containing real world examples (CRUD, auth, advanced patterns, etc) that adheres to the [RealWorld](https://github.com/gothinkster/realworld) spec and API. | ||
### Real World에서 제공해주는 API를 활용하여 블로그를 개발한 프로젝트입니다. | ||
|
||
## 프로젝트 목표 | ||
|
||
### [Demo](https://demo.realworld.io/) [RealWorld](https://github.com/gothinkster/realworld) | ||
### Next.js 13 App Router의 사용법을 익히고 SSR 이해하기 | ||
|
||
### Vanilla Extract의 사용법을 익히고 제로 런타임 이해하기 | ||
|
||
This codebase was created to demonstrate a fully fledged fullstack application built with **[YOUR_FRAMEWORK]** including CRUD operations, authentication, routing, pagination, and more. | ||
### React Query의 사용법을 익히고 효율적인 데이터 패칭을 구현하기 | ||
|
||
We've gone to great lengths to adhere to the **[YOUR_FRAMEWORK]** community styleguides & best practices. | ||
### Zustand의 사용법을 익히고 Flux 패턴 이해하기 | ||
|
||
For more information on how to this works with other frontends/backends, head over to the [RealWorld](https://github.com/gothinkster/realworld) repo. | ||
## Stacks | ||
|
||
### Environment | ||
|
||
# How it works | ||
<div style="display: flex"> | ||
<img src="https://img.shields.io/badge/Visual Studio Code-007ACC?style=for-the-badge&logo=Visual Studio Code&logoColor=white"> | ||
<img src="https://img.shields.io/badge/Git-F05032?style=for-the-badge&logo=git&logoColor=white"> | ||
<img src="https://img.shields.io/badge/GitHub-181717?style=for-the-badge&logo=GitHub&logoColor=white"> | ||
</div> | ||
|
||
> Describe the general architecture of your app here | ||
### Config | ||
|
||
# Getting started | ||
<img src="https://img.shields.io/badge/Npm-CB3837?style=for-the-badge&logo=npm&logoColor=white"> | ||
|
||
> npm install, npm start, etc. | ||
### Development | ||
|
||
<div style="display: flex"> | ||
<img src="https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white"> | ||
<img src="https://img.shields.io/badge/Next-000000?style=for-the-badge&logo=next.js&logoColor=white"> | ||
<img src="https://img.shields.io/badge/Vanilla Extract-DB7093?style=for-the-badge&logo=vanilla extract&logoColor=white"> | ||
<img src="https://img.shields.io/badge/Zustand-3578E5?style=for-the-badge&logo=Zustand&logoColor=white"> | ||
<img src="https://img.shields.io/badge/React Query-FF4154?style=for-the-badge&logo=React Query&logoColor=white"> | ||
</div> | ||
|
||
## 페이지 구성 | ||
|
||
### 메인 페이지 (Article 목록) | ||
|
||
### Article 상세 페이지 | ||
|
||
### 로그인 페이지 | ||
|
||
### 회원가입 페이지 | ||
|
||
### 설정 페이지 | ||
|
||
### 글쓰기 페이지 | ||
|
||
### 프로필 페이지 | ||
|
||
## 주요 기능 | ||
|
||
- Article CRUD 기능 구현 (전체, 태그, 좋아요, 팔로우) | ||
- Comment CRD 기능 구현 | ||
- User & Auth 기능 구현 (로그인, 회원가입, 정보 수정) | ||
- 좋아요 & 팔로우 기능 구현 | ||
|
||
## Future Works | ||
|
||
- [ ] cookies 넣는 부분 util 함수로 빼기 | ||
- [ ] route handler Response 일관성 있게 통일하기 | ||
- [ ] Error Message에 따라 알맞은 에러 처리 | ||
- [ ] 사용하지 않는 함수들 제거하기 | ||
- [ ] 좋아요 & 팔로우 버튼 | ||
- [ ] Optimistic Updates를 활용한 사용자 경험 향상 | ||
- [ ] 일관된 UI를 위해 button 크기 고정 (좋아요 수가 99개가 넘어갈 경우 99+로 표시) | ||
- [ ] ArticlePreview | ||
- [ ] 제목 크기 고정 및 크기를 넘어가면 ... 처리 | ||
- [ ] 한 번 봤던 게시글 표시하기 (체크 표시 또는 배경색을 다르게) | ||
- [ ] alert을 사용하지 않고 Dialog 컴포넌트 구현 | ||
- [ ] 페이지 별 스켈레톤 UI 적용 | ||
- [ ] Vanilla Extract 기능을 활용하여 CSS 정리 (급하게 하느라 너무 막 짠 거 같습니다..) | ||
- [ ] 테스트 코드 추가 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { HTTP_METHOD, COMMON_HEADERS } from '@/constants/api'; | ||
import { API_BASE_URL } from '@/constants/env'; | ||
|
||
class HttpClient { | ||
BASE_URL = API_BASE_URL; | ||
|
||
constructor() {} | ||
|
||
async request(url: string, options: any, method: string) { | ||
const response = await fetch(`${this.BASE_URL}${url}`, { | ||
method, | ||
headers: { | ||
...COMMON_HEADERS, | ||
...options.headers, | ||
}, | ||
...options, | ||
}); | ||
|
||
if (!response.ok) { | ||
throw new Error(`HTTP error! Status: ${response.status}`); | ||
} | ||
|
||
return response; | ||
} | ||
|
||
get(url: string, options = {}) { | ||
return this.request(url, options, HTTP_METHOD.GET); | ||
} | ||
|
||
post(url: string, options = {}) { | ||
return this.request(url, options, HTTP_METHOD.POST); | ||
} | ||
|
||
put(url: string, options = {}) { | ||
return this.request(url, options, HTTP_METHOD.PUT); | ||
} | ||
|
||
delete(url: string, options = {}) { | ||
return this.request(url, options, HTTP_METHOD.DELETE); | ||
} | ||
} | ||
|
||
export const httpClient = new HttpClient(); |
This file was deleted.
Oops, something went wrong.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
'use client'; | ||
|
||
import ArticleList from '@/components/article/ArticleList'; | ||
import ProfileBox from '@/components/profile/ProfileBox'; | ||
import useProfile from '@/hooks/useProfile'; | ||
import { container } from '@/styles/common.css'; | ||
import dynamic from 'next/dynamic'; | ||
import { Suspense } from 'react'; | ||
|
||
const ArticleTab = dynamic(() => import('@/components/article/ArticleTab'), { ssr: false }); | ||
type Props = { | ||
params: { username: string }; | ||
}; | ||
const ProfilePage = ({ params: { username } }: Props) => { | ||
const { profile } = useProfile({ username }); | ||
|
||
return ( | ||
<section> | ||
<ProfileBox username={profile.username} following={profile.following} image={profile.image} /> | ||
<div className={container}> | ||
<ArticleTab /> | ||
<Suspense fallback={<div>리스트 로딩 중...</div>}> | ||
<ArticleList username={profile.username} /> | ||
</Suspense> | ||
</div> | ||
</section> | ||
); | ||
}; | ||
|
||
export default ProfilePage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { http } from '@/utils/http'; | ||
import { NextRequest, NextResponse } from 'next/server'; | ||
|
||
async function GET(req: NextRequest, route: { params: { slug: string } }) { | ||
try { | ||
const slug = route.params.slug; | ||
const token = req.cookies.get('token')?.value || ''; | ||
|
||
const res = await http.get(`/articles/${slug}`, { | ||
headers: { | ||
'Content-Type': 'application/json; charset=utf-8', | ||
Authorization: `Token ${token}`, | ||
}, | ||
}); | ||
|
||
return NextResponse.json({ message: 'Article Get Success', success: true, data: res }); | ||
} catch (error: any) { | ||
console.log(error); | ||
return NextResponse.json({ error: error.message }, { status: 400 }); | ||
} | ||
} | ||
|
||
async function PUT(req: NextRequest, route: { params: { slug: string } }) { | ||
try { | ||
const body = await req.json(); | ||
const slug = route.params.slug; | ||
const token = req.cookies.get('token')?.value || ''; | ||
|
||
const res = await http.put(`/articles/${slug}`, body, { | ||
headers: { | ||
'Content-Type': 'application/json; charset=utf-8', | ||
Authorization: `Token ${token}`, | ||
}, | ||
}); | ||
|
||
return NextResponse.json({ message: 'Article Update Success', success: true, data: res }); | ||
} catch (error: any) { | ||
return NextResponse.json({ error: error.message }, { status: 400 }); | ||
} | ||
} | ||
|
||
async function DELETE(req: NextRequest, route: { params: { slug: string } }) { | ||
try { | ||
const slug = route.params.slug; | ||
const token = req.cookies.get('token')?.value || ''; | ||
|
||
const res = await http.delete(`/articles/${slug}`, { | ||
headers: { | ||
'Content-Type': 'application/json; charset=utf-8', | ||
Authorization: `Token ${token}`, | ||
}, | ||
}); | ||
|
||
return NextResponse.json({ message: 'Article Delete Success', success: true, data: res }); | ||
} catch (error: any) { | ||
return NextResponse.json({ error: error.message }, { status: 400 }); | ||
} | ||
} | ||
|
||
export { GET, PUT, DELETE }; |
Oops, something went wrong.