Skip to content

Commit

Permalink
Merge pull request #25 from raipen/feat/change-user-name
Browse files Browse the repository at this point in the history
Feat: change user name & refactor wordbook list page
  • Loading branch information
raipen authored Mar 30, 2024
2 parents 90d502c + 71c9890 commit e7f0260
Show file tree
Hide file tree
Showing 27 changed files with 314 additions and 133 deletions.
1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ model Wordbook {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
isHidden Boolean @default(false)
deletedAt DateTime?
}

model Voca {
Expand Down
43 changes: 43 additions & 0 deletions src/DTO/user.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,47 @@ export const signOutSchema = {
},
} as const;

export const getUserNameSchema = {
tags: ['User'],
summary: '닉네임 조회',
headers: AuthorizationHeader,
response: {
200: {
type: 'object',
description: '닉네임 조회 성공',
required: ['name'],
properties: {
name: { type: 'string' },
},
},
...errorSchema(
E.NoAuthorizationInCookieError,
E.UserAuthorizationError,
)
},
} as const;

export const changeUserNameSchema = {
tags: ['User'],
summary: '닉네임 변경',
headers: AuthorizationHeader,
body: {
type: 'object',
required: ['name'],
properties: {
name: { type: 'string' },
},
},
response: {
200: {
},
...errorSchema(
E.NoAuthorizationInCookieError,
E.UserAuthorizationError,
)
},
} as const;

export const refreshSchema = {
tags: ['User'],
summary: '토큰 재발급',
Expand Down Expand Up @@ -67,3 +108,5 @@ export const profileSchema = {
export type signOutInterface = SchemaToInterface<typeof signOutSchema>;
export type refreshInterface = SchemaToInterface<typeof refreshSchema> & { Body: { userId: string } };
export type profileInterface = SchemaToInterface<typeof profileSchema> & { Body: { userId: string } };
export type getUserNameInterface = SchemaToInterface<typeof getUserNameSchema> & { Body: { userId: string } };
export type changeUserNameInterface = SchemaToInterface<typeof changeUserNameSchema> & { Body: { userId: string } };
22 changes: 22 additions & 0 deletions src/back/api/routes/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,28 @@ const api: FastifyPluginAsync = async (server: FastifyInstance) => {
reply.status(200).send(profile);
}
);
server.get<User.getUserNameInterface>(
'/name',
{
schema: User.getUserNameSchema,
preValidation: checkUser
},
async (request, reply) => {
const { name } = await userService.getUserName(request.body);
reply.status(200).send({ name });
}
);
server.patch<User.changeUserNameInterface>(
'/name',
{
schema: User.changeUserNameSchema,
preValidation: checkUser
},
async (request, reply) => {
await userService.changeUserName(request.body);
reply.status(200).send();
}
);
};

export default api;
12 changes: 12 additions & 0 deletions src/back/repository/user.repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const createUser = async ({name,socialId,socialType}:{name: string, socia
}
});
}

export const getUser = async (socialType: SocialType, socialId: string) => {
return prisma.user.findFirst({
where: {
Expand All @@ -26,3 +27,14 @@ export const getUserById = async (uuid: string) => {
}
});
}

export const changeUserName = async (uuid: string, name: string) => {
return prisma.user.update({
where: {
uuid
},
data: {
name
}
});
}
19 changes: 17 additions & 2 deletions src/back/services/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,26 @@ export default {
};
},

async getUserName({userId}: UserDTO.getUserNameInterface['Body']){
const { name } = (await UserRepo.getUserById(userId))!;
return { name };
},

async changeUserName({userId, name}: UserDTO.changeUserNameInterface['Body']){
await UserRepo.changeUserName(userId, name);
},

async getProfile({userId}: UserDTO.profileInterface['Body']): Promise<UserDTO.profileInterface['Reply']['200']> {
const { name } = (await UserRepo.getUserById(userId))!;
const wordbook = await WordbookRepo.getNonHiddenWordbookList(userId);
const wordbook = (await WordbookRepo.getNonHiddenWordbookList(userId)).map((wordbook) => {
const { _count, createdAt } = wordbook;
return {
createdAt: new Date(new Date(createdAt).getTime() + 1000 * 60 * 60 * 9),
count : _count.voca
};
});
const wordbookCount = wordbook.length;
const vocaCount = wordbook.reduce((acc, cur) => acc + cur._count.voca, 0);
const vocaCount = wordbook.reduce((acc, cur) => acc + cur.count, 0);
const loginDate = Array.from({ length: 70 },
(_, i) => new Date(new Date().getTime() + 1000 * 60 * 60 * 9 - 1000 * 60 * 60 * 24 * i).toISOString().slice(0, 10))
.map((date) => ({ date: date.slice(0, 10), count: wordbook.filter((wordbook) => wordbook.createdAt.toISOString().slice(0, 10) === date).length }));
Expand Down
3 changes: 2 additions & 1 deletion src/front/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Setting from '@pages/Setting';
import PrivacyPolicy from '@pages/PrivacyPolicy';
import TermsOfService from '@pages/TermsOfService';
import Error from '@pages/Error';
import { MainContainer } from '@components';

function App() {
const value = useInitLogin();
Expand All @@ -29,7 +30,7 @@ function App() {
<Route path="/privacy-policy" element={<PrivacyPolicy />} />
<Route path="/terms-of-service" element={<TermsOfService />} />
<Route path="/error" element={<Error />} />
<Route path="*" element={<div>404 Not Found</div>} />
<Route path="*" element={<MainContainer>잘못된 접근입니다</MainContainer>} />
</Routes>
<Footer />
</LoginContext.Provider>
Expand Down
7 changes: 3 additions & 4 deletions src/front/components/AddWordbook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@ import { WordbookInfo, WordbookMenu, WordbookName, AddWordbookContainer, Input,
import useFetchUpdate from "@hooks/useFetchUpdate";
import { addWordbook } from '@utils/apis/wordbook';
import { useContext,useRef } from 'react';
import { WordbookListContext } from '@context/WordbookListContext';
import WordbookListContext from '@context/WordbookListContext';
import WordbookDetailInfo from './WordbookDetailInfo';

function AddWordbookList({setNewWordbook}: {setNewWordbook: React.Dispatch<React.SetStateAction<boolean>>}) {
const [loadingWordbook, fetchWordbook] = useFetchUpdate(addWordbook);
const { setData } = useContext(WordbookListContext);
const { onClickWordbookElement } = useContext(WordbookListContext);
const inputRef = useRef<HTMLInputElement>(null);

const onClickAdd = async () => {
const name = inputRef.current?.value;
if (name) {
const wordbookData = await fetchWordbook(name);
setData(([profile]) => [profile, wordbookData]);
await onClickWordbookElement(fetchWordbook, name)();
setNewWordbook(false);
}
}
Expand Down
20 changes: 0 additions & 20 deletions src/front/components/HiddenWordbookList.tsx

This file was deleted.

18 changes: 18 additions & 0 deletions src/front/components/HiddenWordbookTitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useContext } from 'react';
import WordbookListContext from '@context/WordbookListContext';
import { Title, Expend } from './index';

function HiddenWordbookTitle() {
const {expend, expendOnClick} = useContext(WordbookListContext);

return (
<Title onClick={expendOnClick}>
<span>숨긴 단어장</span>
<Expend className="material-icons-sharp">
{expend ? 'expand_less' : 'expand_more'}
</Expend>
</Title>
);
}

export default HiddenWordbookTitle;
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
import { useState } from "react";
import { ButtonContainingIcon, WordbookListContainer, Title } from './index';
import { ButtonContainingIcon, Title } from './index';
import AddWordbook from './AddWordbook';

function WordbookList({children}: {children: React.ReactNode}) {
function MyWordbookTitle() {
const [newWordbook, setNewWordbook] = useState(false);

return (
<WordbookListContainer>
<Title>

return [<Title key="title">
<span>내 단어장</span>
{!newWordbook && <ButtonContainingIcon onClick={() => setNewWordbook(true)}>
<span className="material-icons-sharp">
menu_book
</span>
<span>New</span>
</ButtonContainingIcon>}
</Title>
{newWordbook && <AddWordbook setNewWordbook={setNewWordbook} />}
{children}
</WordbookListContainer>
);
</Title>, newWordbook && <AddWordbook key="add" setNewWordbook={setNewWordbook} />]
}

export default WordbookList;
export default MyWordbookTitle;
30 changes: 19 additions & 11 deletions src/front/components/Profile.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { styled } from 'styled-components';
import WordbookListContext from '@context/WordbookListContext';
import { useContext } from 'react';
import { Link } from 'react-router-dom';

const ProfileContainer = styled.div`
display: flex;
Expand All @@ -13,17 +16,23 @@ const ProfileContainer = styled.div`
&>div:first-child {
display: flex;
align-items: center;
gap: 5px;
font-size: 1.5rem;
font-weight: 300;
margin-bottom: 10px;
>.material-icons-sharp {
font-size: 1rem;
user-select: none;
}
}
&>div:nth-child(2) {
display: flex;
align-items: flex-end;
align-items: center;
gap: 3px;
color: var(--muted-text-color);
>.material-icons-sharp {
font-size: 1rem;
user-select: none;
}
>span:nth-child(3n+2) {
font-weight: 600;
Expand Down Expand Up @@ -114,19 +123,18 @@ function LoginDate({loginDate}: {loginDate: {date: string, count: number}[]}) {
);
}

function Profile({profile}: {profile:{
name: string;
wordbookCount: number;
vocaCount: number;
loginDate: {
date: string;
count: number;
}[];
}}) {
function Profile() {
const {profile} = useContext(WordbookListContext);
return (
<ProfileContainer>
<div>
{profile.name}
<span className="material-icons-sharp">
account_circle
</span>
<span>{profile.name}</span>
<Link to="/setting" className="material-icons-sharp" style={{marginLeft: 'auto'}}>
settings
</Link>
</div>
<div>
<span className="material-icons-sharp">
Expand Down
2 changes: 1 addition & 1 deletion src/front/components/TestVocaList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { VocaMode } from "@utils/vocaModeEnum";
import { VocaListContext } from '@context/VocaListContext';
import VocaListContext from '@context/VocaListContext';
import useFetchUpdate from '@hooks/useFetchUpdate';
import { useContext,useState } from 'react';
import {
Expand Down
2 changes: 1 addition & 1 deletion src/front/components/ViewVocaList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { VocaMode } from "@utils/vocaModeEnum";
import { useState, useEffect,useContext } from 'react';
import { VocaListContext } from '@context/VocaListContext';
import VocaListContext from '@context/VocaListContext';
import useFetchUpdate from '@hooks/useFetchUpdate';
import { getVocaList, increaseCheckCount, decreaseCheckCount } from "@utils/apis/voca";
import {
Expand Down
2 changes: 1 addition & 1 deletion src/front/components/VocaSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ function VocaSidebar({vocaMode,setVocaMode,wordbook}:{
<span className="material-icons-sharp" onClick={() => setVocaMode(VocaMode.EDIT)}>
event
</span>
<span>{ISOStringToDateString(wordbook.createdAt)}</span>
{wordbook.createdAt&&<span>{ISOStringToDateString(wordbook.createdAt)}</span>}
</div>
</WordbookInfo>
<DefaultListElement>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { Link } from 'react-router-dom';
import useFetchUpdate from "@hooks/useFetchUpdate";
import { hideWordbook,showWordbook } from '@utils/apis/wordbook';
import { useContext } from 'react';
import { WordbookListContext } from '@context/WordbookListContext';
import WordbookListContext from '@context/WordbookListContext';
import WordbookDetailInfo from './WordbookDetailInfo';

function WordbookListElement({ wordbook: { uuid, title, isHidden, createdAt, vocaCount } }: {
function WordbookElement({ wordbook: { uuid, title, isHidden, createdAt, vocaCount } }: {
wordbook: {
uuid: string;
title: string;
Expand All @@ -16,12 +16,8 @@ function WordbookListElement({ wordbook: { uuid, title, isHidden, createdAt, voc
}
}) {
const [loadingWordbook, fetchWordbook] = useFetchUpdate(isHidden ? showWordbook : hideWordbook);
const { setData } = useContext(WordbookListContext);
const { onClickWordbookElement } = useContext(WordbookListContext);

const onClick = async () => {
const data = await fetchWordbook(uuid);
setData(([profile])=>[profile, data]);
}
return (
<WordbookContainer>
<WordbookInfo>
Expand All @@ -34,12 +30,12 @@ function WordbookListElement({ wordbook: { uuid, title, isHidden, createdAt, voc
<WordbookDetailInfo createdAt={createdAt} vocaCount={vocaCount} />
</WordbookInfo>
<WordbookMenu>
{!loadingWordbook && <span className="material-icons-sharp" onClick={onClick}>{isHidden ? "visibility" : "visibility_off"}</span>}
{!loadingWordbook && <span className="material-icons-sharp" onClick={onClickWordbookElement(fetchWordbook, uuid)}>{isHidden ? "visibility" : "visibility_off"}</span>}
{loadingWordbook && <span className="material-icons-sharp">hourglass_bottom</span>}
{loadingWordbook && <span>이동 중</span>}
</WordbookMenu>
</WordbookContainer>
)
}

export default WordbookListElement;
export default WordbookElement;
Loading

0 comments on commit e7f0260

Please sign in to comment.