Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
students
travel
Commits
0ce1f449
Commit
0ce1f449
authored
Jul 11, 2022
by
Yoon, Daeki
😅
Browse files
Merge branch 'develop' into main-dev
parents
a2cd7201
94bd52bc
Changes
19
Hide whitespace changes
Inline
Side-by-side
frontend/src/App.tsx
View file @
0ce1f449
...
@@ -12,7 +12,6 @@ export const App = () => {
...
@@ -12,7 +12,6 @@ export const App = () => {
<
Routes
>
<
Routes
>
<
Route
path
=
"login"
element
=
{
<
Login
/>
}
/>
<
Route
path
=
"login"
element
=
{
<
Login
/>
}
/>
<
Route
path
=
"signup"
element
=
{
<
Signup
/>
}
/>
<
Route
path
=
"signup"
element
=
{
<
Signup
/>
}
/>
<
Route
path
=
"/"
element
=
{
<
Header
/>
}
>
<
Route
path
=
"/"
element
=
{
<
Header
/>
}
>
<
Route
index
element
=
{
<
Body
/>
}
/>
<
Route
index
element
=
{
<
Body
/>
}
/>
<
Route
path
=
"board"
element
=
{
<
Board
/>
}
/>
<
Route
path
=
"board"
element
=
{
<
Board
/>
}
/>
...
...
frontend/src/Pages/picpagination.tsx
View file @
0ce1f449
import
React
from
"
react
"
;
import
React
from
"
react
"
;
type
num
=
{
type
num
=
{
total
:
number
,
//
total: number,
page
:
number
,
page
:
number
,
setPage
:
Function
setPage
:
Function
}
}
export
default
function
Pagination
({
total
,
page
,
setPage
}
:
num
)
{
// export function PaginationLeft ({total, page, setPage} : num) {
const
numPages
=
Math
.
ceil
(
total
/
15
);
// const numPages = Math.ceil(total / 15);
export
function
PaginationLeft
({
page
,
setPage
}
:
num
)
{
const
numPages
=
3
return
(
return
(
<
div
>
<
div
>
<
button
onClick
=
{
()
=>
setPage
(
page
-
1
)
}
disabled
=
{
page
===
1
}
>
<
button
onClick
=
{
()
=>
setPage
(
page
-
1
)
}
disabled
=
{
page
===
1
}
>
<
<
</
button
>
</
button
>
{
Array
(
numPages
)
{
/*
{Array(numPages)
.fill(1)
.fill(1)
.map((_, i) => (
.map((_, i) => (
<button key={i + 1} onClick={() => setPage(i + 1)}>
<button key={i + 1} onClick={() => setPage(i + 1)}>
{i + 1}
{i + 1}
</button>
</button>
))
}
))} */
}
</
div
>
);
};
// export function PaginationRight ({total, page, setPage} : num) {
// const numPages = Math.ceil(total / 15);
export
function
PaginationRight
({
page
,
setPage
}
:
num
)
{
const
numPages
=
3
;
return
(
<
div
>
<
button
onClick
=
{
()
=>
setPage
(
page
+
1
)
}
disabled
=
{
page
===
numPages
}
>
<
button
onClick
=
{
()
=>
setPage
(
page
+
1
)
}
disabled
=
{
page
===
numPages
}
>
>
>
</
button
>
</
button
>
...
...
frontend/src/apis/index.ts
View file @
0ce1f449
import
axios
from
"
axios
"
;
import
axios
from
"
axios
"
;
export
*
as
authApi
from
"
./auth.api
"
;
export
*
as
authApi
from
"
./auth.api
"
;
export
*
as
postApi
from
"
./post.api
"
;
frontend/src/apis/post.api.ts
0 → 100644
View file @
0ce1f449
import
axios
from
"
axios
"
;
import
baseUrl
from
"
./baseUrl
"
;
import
{
PostingType
}
from
"
../types
"
;
export
const
posting
=
async
(
post
:
PostingType
)
=>
{
const
{
data
}
=
await
axios
.
post
(
`
${
baseUrl
}
/posts/`
,
post
);
return
data
;
};
frontend/src/auth/auth.helper.tsx
0 → 100644
View file @
0ce1f449
import
{
authApi
}
from
"
../apis
"
;
import
{
IUser
}
from
"
../types
"
;
const
LOCAL_USER_INFO
=
"
survey-user-info
"
;
/**
* 1. 백엔드 로그인을 호출하여 로그인 정보를 얻습니다.
* 2. 로컬 저장소에 저장합니다.
* 3. 사용자 정보를 반환합니다.
* @param email 이메일
* @param password 비밀번호
* @returns 사용자 정보
*/
export
const
handleLogin
=
async
(
email
:
string
,
password
:
string
)
=>
{
const
user
:
IUser
=
await
authApi
.
login
(
email
,
password
);
// 로컬 저장소에는 로그인 여부만 저장
localStorage
.
setItem
(
LOCAL_USER_INFO
,
JSON
.
stringify
({
isLoggedIn
:
user
.
isLoggedIn
,
})
);
return
user
;
};
/**
* 로컬 저장소의 정보를 삭제합니다.
* 백엔드 로그아웃을 호출하여 쿠키를 제거합니다.
*/
export
const
handleLogout
=
async
()
=>
{
console
.
log
(
"
handle logout called
"
);
localStorage
.
removeItem
(
LOCAL_USER_INFO
);
try
{
await
authApi
.
logout
();
}
catch
(
error
)
{
console
.
log
(
"
logout 중에 에러 발생:
"
,
error
);
}
};
/**
* 1. 로컬 저장소에 저장된 사용자 로그인 정보를 반환합니다.
* 2. 로컬 저장소에 정보가 없으면 { isLoggedIn: false }를 반환합니다.
* @returns 로컬 저장소에 저장된 사용자 정보
*/
export
const
getLocalUser
=
()
=>
{
console
.
log
(
"
get local user called
"
);
const
userInfo
=
localStorage
.
getItem
(
LOCAL_USER_INFO
);
const
user
:
IUser
=
{
isLoggedIn
:
false
};
if
(
!
userInfo
)
{
return
user
;
}
const
userData
=
JSON
.
parse
(
userInfo
);
if
(
userData
.
isLoggedIn
)
{
user
.
isLoggedIn
=
true
;
}
else
{
user
.
isLoggedIn
=
false
;
}
return
user
;
};
frontend/src/auth/login.tsx
View file @
0ce1f449
import
{
Link
}
from
"
react-router-dom
"
;
import
{
Link
,
useNavigate
}
from
"
react-router-dom
"
;
import
React
,
{
useState
,
FormEventHandler
}
from
"
react
"
;
import
React
,
{
useState
,
useEffect
,
FormEvent
}
from
"
react
"
;
import
{
LoginUser
}
from
"
../types
"
;
import
{
catchErrors
}
from
"
../helpers
"
;
import
{
useAuth
}
from
"
./auth.context
"
;
interface
login
{
export
default
function
Login
()
{
id
:
string
;
const
[
user
,
setUser
]
=
useState
<
LoginUser
>
({
password
:
string
;
email
:
""
,
}
password
:
""
,
});
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
error
,
setError
]
=
useState
(
""
);
const
[
disabled
,
setDisabled
]
=
useState
(
false
);
const
[
success
,
setSuccess
]
=
useState
(
false
);
const
navigate
=
useNavigate
();
const
{
login
}
=
useAuth
();
// const fake = { id: "asdf", password: "qwer" };
useEffect
(()
=>
{
setDisabled
(
!
(
user
.
email
&&
user
.
password
));
},
[
user
]);
function
Logindata
()
{
function
handleChange
(
event
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
{
const
[
id
,
setId
]
=
useState
(
""
);
const
{
name
,
value
}
=
event
.
currentTarget
;
const
[
password
,
setPassword
]
=
useState
(
""
);
setUser
({
...
user
,
[
name
]:
value
});
}
function
login
()
{
async
function
handleSubmit
(
event
:
FormEvent
)
{
fetch
(
`http://localhost:3000/api/auth/login`
,
{
event
.
preventDefault
();
method
:
"
POST
"
,
try
{
setError
(
""
);
console
.
log
(
"
user data
"
,
user
);
body
:
JSON
.
stringify
({
// setLoading(true);
email
:
`
${
id
}
`
,
await
login
(
user
.
email
,
user
.
password
,
()
=>
{
password
:
`
${
password
}
`
,
navigate
(
"
/
"
,
{
replace
:
true
});
}),
});
}).
then
((
response
)
=>
{
// console.log("서버연결됬나요", res);
console
.
log
(
response
.
json
());
// console.log("로그인");
});
// setSuccess(true);
// setError("");
}
catch
(
error
)
{
console
.
log
(
"
에러발생
"
);
// setError("이메일 혹은 비밀번호를 다시 입력해주세요.");
catchErrors
(
error
,
setError
);
}
finally
{
setLoading
(
false
);
}
}
}
return
(
return
(
<
div
className
=
"flex flex-col md:w-2/3 md:gap-2"
>
<
div
className
=
"flex flex-col items-center my-10"
>
<
input
<
div
className
=
"bg-white w-1/2 md:w-1/3 my-8 text-center text-2xl"
>
className
=
"
placeholder
:
text
-
slate
-
300
<
Link
to
=
"/"
>
Travel Report
</
Link
>
bg
-
white
border
border
-
slate
-
500
rounded
-
2xl
</
div
>
py
-
2
pl
-
9
pr
-
3
<
div
className
=
"flex flex-col w-full md:w-1/3 p-8 md:p-4 md:p-0"
>
<
form
onSubmit
=
{
handleSubmit
}
>
<
div
className
=
"flex flex-col md:flex-row border-2 border-black rounded-xl p-8 md:p-12 gap-y-4 md:gap-x-6"
>
<
div
className
=
"flex flex-col md:w-2/3 md:gap-2 "
>
<
input
className
=
"
placeholder
:
text
-
slate
-
300
bg
-
white
border
border
-
slate
-
500
md
:
rounded
-
2xl
my
-
2
py
-
3
md
:
py
-
2
pl
-
9
pr
-
3
focus
:
border
-
black
focus
:
border
-
black
"
"
placeholder
=
"Id"
placeholder
=
"이메일"
type
=
"text"
type
=
"email"
name
=
"Id"
name
=
"email"
onChange
=
{
(
e
)
=>
setId
(
e
.
target
.
value
)
}
onChange
=
{
handleChange
}
/>
/>
<
input
className
=
"
placeholder
:
italic
placeholder
:
text
-
slate
-
300
<
input
bg
-
white
border
border
-
slate
-
500
rounded
-
2xl
className
=
"
placeholder
:
italic
placeholder
:
text
-
slate
-
300
py
-
2
pl
-
9
pr
-
3
bg
-
white
border
border
-
slate
-
500
md
:
rounded
-
2xl
py
-
3
md
:
py
-
2
pl
-
9
pr
-
3
focus
:
border
-
black
focus
:
border
-
black
"
"
placeholder
=
"Password"
placeholder
=
"Password"
type
=
"password"
type
=
"password"
name
=
"Password"
name
=
"password"
onChange
=
{
(
e
)
=>
setPassword
(
e
.
target
.
value
)
}
onChange
=
{
handleChange
}
/>
/>
</
div
>
<
button
<
button
type
=
"submit"
disabled
=
{
disabled
}
className
=
"md:w-1/3 bg-sky-600 hover:bg-sky-700 rounded-xl"
type
=
"submit"
onClick
=
{
login
}
className
=
"my-4 md:my-0 md:w-1/3 bg-sky-600 hover:bg-sky-700 rounded-xl text-xl py-4"
>
>
<
Link
to
=
{
"
/
"
}
>
login
</
Link
>
login
</
button
>
</
div
>
);
}
export
default
function
Login
()
{
return
(
<
div
>
{
/* <form onSubmit={loginsubmit}> */
}
<
div
className
=
"flex flex-row grid-rows-2"
>
<
div
className
=
" p-12 w-1/2 h-1/2 md:w-40 md:h-40 bg-red-400 place-self-center rounded-2xl"
>
<
Link
to
=
"/"
>
Travel Report
</
Link
>
</
div
>
<
div
className
=
" flex-row w-auto h-60 md:w-1/2 bg-white border-2 border-black grid place-items-center rounded-xl place-self-center"
>
<
div
className
=
"flex flex-col w-full md:flex-row md:p-20 md:gap-10"
>
<
Logindata
/>
</
div
>
<
div
className
=
"flex-row grid grid-cols-3"
>
<
button
className
=
"bg-white bottom-0 right-0"
>
<
Link
to
=
"/signup"
>
회원가입
</
Link
>
</
button
>
</
button
>
</
div
>
</
form
>
<
div
className
=
"flex justify-around m-4"
>
<
button
className
=
"bg-white "
>
<
Link
to
=
"/signup"
>
회원가입
</
Link
>
</
button
>
<
div
className
=
"grid grid-cols-2 divide-x-2"
>
<
div
></
div
>
<
div
></
div
>
<
div
></
div
>
<
button
className
=
"bg-white inset-x-0"
>
<
Link
to
=
"/forgot"
>
비밀번호 찾기
</
Link
>
</
button
>
</
div
>
</
div
>
<
button
className
=
"bg-white"
>
<
Link
to
=
"/forgot"
>
비밀번호 찾기
</
Link
>
</
button
>
</
div
>
</
div
>
</
div
>
</
div
>
{
/* </form> */
}
</
div
>
</
div
>
// Login Page
);
);
}
}
frontend/src/board/board.tsx
View file @
0ce1f449
...
@@ -14,6 +14,7 @@ interface Posts {
...
@@ -14,6 +14,7 @@ interface Posts {
export
const
fakes
=
[
export
const
fakes
=
[
{
{
id
:
"
a
"
,
id
:
"
a
"
,
username
:
"
lsb
"
,
title
:
"
여행가고싶다...
"
,
title
:
"
여행가고싶다...
"
,
date
:
"
2022-06-30
"
,
date
:
"
2022-06-30
"
,
counts
:
0
,
counts
:
0
,
...
@@ -22,6 +23,7 @@ export const fakes = [
...
@@ -22,6 +23,7 @@ export const fakes = [
},
},
{
{
id
:
"
b
"
,
id
:
"
b
"
,
username
:
"
lsb
"
,
title
:
"
바다!바다!바다!
"
,
title
:
"
바다!바다!바다!
"
,
date
:
"
2022-08-01
"
,
date
:
"
2022-08-01
"
,
counts
:
0
,
counts
:
0
,
...
@@ -30,6 +32,7 @@ export const fakes = [
...
@@ -30,6 +32,7 @@ export const fakes = [
},
},
{
{
id
:
"
c
"
,
id
:
"
c
"
,
username
:
"
lsb
"
,
title
:
"
Jeju-island
"
,
title
:
"
Jeju-island
"
,
date
:
"
2022-9-10
"
,
date
:
"
2022-9-10
"
,
counts
:
0
,
counts
:
0
,
...
@@ -38,6 +41,7 @@ export const fakes = [
...
@@ -38,6 +41,7 @@ export const fakes = [
},
},
{
{
id
:
"
d
"
,
id
:
"
d
"
,
username
:
"
lsb
"
,
title
:
"
마! 부싼 가봤나!
"
,
title
:
"
마! 부싼 가봤나!
"
,
date
:
"
2022-9-22
"
,
date
:
"
2022-9-22
"
,
counts
:
0
,
counts
:
0
,
...
@@ -46,26 +50,29 @@ export const fakes = [
...
@@ -46,26 +50,29 @@ export const fakes = [
},
},
{
{
id
:
"
e
"
,
id
:
"
e
"
,
username
:
"
lsb
"
,
title
:
"
Daegu
"
,
title
:
"
Daegu
"
,
date
:
"
2022-10-1
"
,
date
:
"
2022-10-1
"
,
counts
:
0
,
counts
:
0
,
theme
:
"
s
urfing
"
,
theme
:
"
s
ki
"
,
city
:
"
seoul
"
,
city
:
"
Daegu
"
,
},
},
{
{
id
:
"
f
"
,
id
:
"
f
"
,
username
:
"
lsb
"
,
title
:
"
강원도 감자는 맛있다.
"
,
title
:
"
강원도 감자는 맛있다.
"
,
date
:
"
2022-12-12
"
,
date
:
"
2022-12-12
"
,
counts
:
0
,
counts
:
0
,
theme
:
"
surf
ing
"
,
theme
:
"
camp
ing
"
,
city
:
"
seoul
"
,
city
:
"
강원도
"
,
},
},
{
{
id
:
"
g
"
,
id
:
"
g
"
,
username
:
"
lsb
"
,
title
:
"
부산남자의 서울여행
"
,
title
:
"
부산남자의 서울여행
"
,
date
:
"
2022-12-25
"
,
date
:
"
2022-12-25
"
,
counts
:
0
,
counts
:
0
,
theme
:
"
surfing
"
,
theme
:
"
activity
"
,
city
:
"
seoul
"
,
city
:
"
seoul
"
,
},
},
];
];
...
...
frontend/src/home/body.tsx
View file @
0ce1f449
...
@@ -3,7 +3,7 @@ import { Outlet, useSearchParams } from "react-router-dom";
...
@@ -3,7 +3,7 @@ import { Outlet, useSearchParams } from "react-router-dom";
import
Theme
from
"
./theme
"
;
import
Theme
from
"
./theme
"
;
import
Citylist
from
"
../Pages/citylist
"
;
import
Citylist
from
"
../Pages/citylist
"
;
import
{
getPicure
}
from
"
../Pages/pic
"
;
import
{
getPicure
}
from
"
../Pages/pic
"
;
import
Pagination
from
"
../Pages/picpagination
"
;
import
{
Pagination
Left
,
PaginationRight
}
from
"
../Pages/picpagination
"
;
const
initSearchParams
=
{
theme
:
""
,
city
:
""
};
const
initSearchParams
=
{
theme
:
""
,
city
:
""
};
...
@@ -12,7 +12,7 @@ export default function Body() {
...
@@ -12,7 +12,7 @@ export default function Body() {
const
[
searchParams
,
setSearchParams
]
=
useSearchParams
(
initSearchParams
);
const
[
searchParams
,
setSearchParams
]
=
useSearchParams
(
initSearchParams
);
const
[
page
,
setPage
]
=
useState
(
1
);
const
[
page
,
setPage
]
=
useState
(
1
);
const
offset
=
(
page
-
1
)
*
limit
;
const
offset
=
(
page
-
1
)
*
limit
;
const
[
selected
,
setSelected
]
=
useState
(
1
);
let
getPics
=
getPicure
();
let
getPics
=
getPicure
();
useEffect
(()
=>
{
useEffect
(()
=>
{
...
@@ -48,29 +48,55 @@ export default function Body() {
...
@@ -48,29 +48,55 @@ export default function Body() {
);
);
});
});
const
pre
=
()
=>
{
setSelected
(
selected
-
1
)
};
const
next
=
()
=>
{
setSelected
(
selected
+
1
)
};
return
(
return
(
<
div
className
=
"flex flex-col px-1 py-1"
>
<
div
className
=
"flex flex-col px-1 py-1"
>
<
Theme
handleClick
=
{
themeHandleClick
}
/>
<
Theme
handleClick
=
{
themeHandleClick
}
/>
<
div
className
=
"flex flex-col md:flex-row py-10 "
>
<
div
className
=
"flex flex-col md:flex-row py-10 "
>
<
Citylist
handleClick
=
{
cityHandleClick
}
/>
<
div
className
=
"w-50"
>
<
div
className
=
"flex-row md:mr-10 md:basis-4/5 grid grid-rows-3 grid-cols-5"
>
<
Citylist
handleClick
=
{
cityHandleClick
}
/>
{
Idpics
.
slice
(
offset
,
offset
+
limit
).
map
((
pic
,
index
:
number
)
=>
(
</
div
>
<
div
<
div
className
=
"flex flex-col"
>
className
=
"m-1 shrink-0 bg-gray-200 rounded overflow-hidden shadow-md"
<
div
>
key
=
{
index
}
<
button
onClick
=
{
pre
}
disabled
=
{
selected
===
1
}
>
<
{
selected
}
</
button
>
>
<
button
onClick
=
{
next
}
disabled
=
{
selected
===
3
}
>
>
</
button
>
<
img
</
div
>
src
=
{
pic
.
url
}
<
div
>
className
=
"w-full h-10 md:h-20 object-center"
<
div
className
=
" md:mr-10 md:basis-4/5 overflow-hidden"
>
/>
<
div
className
=
" flex flex-row transition duration-500 relative"
style
=
{
{
"
transform
"
:
'
translate(-
'
+
(
selected
-
1
)
*
100
+
'
%)
'
}
}
>
<
p
className
=
"text-center text-xs"
>
{
pic
.
name
}
</
p
>
<
img
className
=
"border-2"
src
=
"https://t1.daumcdn.net/cfile/tistory/9947E0365C31C1BF0E"
/>
<
img
className
=
"border-2"
src
=
"https://t1.daumcdn.net/cfile/tistory/9947E0365C31C1BF0E"
/>
<
img
className
=
"border-2"
src
=
"https://t1.daumcdn.net/cfile/tistory/9947E0365C31C1BF0E"
/>
</
div
>
</
div
>
</
div
>
))
}
</
div
>
<
Pagination
total
=
{
Idpics
.
length
}
page
=
{
page
}
setPage
=
{
setPage
}
/>
</
div
>
</
div
>
</
div
>
</
div
>
<
Outlet
/>
<
Outlet
/>
</
div
>
</
div
>
// Body Page
// Body Page
);
);
}
}
{
/* <div className=" md:mr-10 md:basis-4/5 grid grid-rows-3 grid-cols-5"></div> */
}
{
/* {Idpics.slice(offset, offset + limit).map((pic, index: number) => (
<div
className="m-1 shrink-0 bg-gray-200 rounded overflow-hidden shadow-md"
key={index}
>
<img
src={pic.url}
className="w-full h-10 md:h-20 object-center"
/>
<p className="text-center text-xs">{pic.name}</p>
</div>
))} */
}
{
/* <PaginationLeft total={Idpics.length} page={page} setPage={setPage} /> */
}
{
/* <PaginationRight total={Idpics.length} page={page} setPage={setPage} /> */
}
\ No newline at end of file
frontend/src/home/header.tsx
View file @
0ce1f449
import
React
from
"
react
"
;
import
React
,
{
useState
}
from
"
react
"
;
import
{
Link
,
Outlet
}
from
"
react-router-dom
"
;
import
{
Link
,
Outlet
}
from
"
react-router-dom
"
;
import
{
useAuth
}
from
"
../auth/auth.context
"
;
import
"
tailwindcss/tailwind.css
"
;
import
"
tailwindcss/tailwind.css
"
;
export
default
function
Header
()
{
export
default
function
Header
()
{
return
(
const
{
logout
}
=
useAuth
();
<
div
className
=
"flex flex-col "
>
return
(
<
div
className
=
"flex flex-row px-5 py-20 md:place-content-between"
>
<
div
className
=
"flex flex-col "
>
<
button
className
=
"px-5 py-2"
>
<
div
className
=
"flex flex-row px-5 py-20 md:place-content-between"
>
<
Link
to
=
"/"
className
=
"hover:bg-gray-200 focus:text-purple-500"
>
Travel Report
</
Link
>
<
button
className
=
"px-5 py-2"
>
<
Link
to
=
"/"
className
=
"hover:bg-gray-200 focus:text-purple-500"
>
Travel Report
</
Link
>
</
button
>
<
div
className
=
"flex flex-row-reverse"
>
<
div
className
=
"px-5 py-2 bg-teal-400 rounded"
>
{
localStorage
.
getItem
(
"
survey-user-info
"
)
?
(
<
button
onClick
=
{
()
=>
{
logout
();
}
}
>
Logout
</
button
>
)
:
(
<
button
>
<
Link
to
=
"/login"
>
Login
</
Link
>
</
button
>
)
}
</
div
>
<
button
className
=
"px-5 py-2 bg-purple-400 rounded"
>
<
Link
to
=
"/board"
className
=
"hover:bg-purple-300 focus:text-purple-500 "
>
Board
</
Link
>
</
button
>
</
button
>
<
div
className
=
"flex flex-row-reverse"
>
<
div
>
<
button
className
=
"px-5 py-2 bg-teal-400 rounded"
>
<
label
>
<
Link
to
=
"/login"
className
=
"hover:bg-teal-100 focus:text-purple-500 "
>
Login
</
Link
>
{
/* <span className="sr-only">Search</span> */
}
<
span
className
=
"absolute inset-y-0 left-0 flex items-center pl-2"
>
</
button
>
<
svg
<
button
className
=
"px-5 py-2 bg-purple-400 rounded"
>
className
=
"h-5 w-5 fill-slate-300"
<
Link
to
=
"/board"
className
=
"hover:bg-purple-300 focus:text-purple-500 "
>
Board
</
Link
>
viewBox
=
"0 0 20 20"
</
button
>
></
svg
>
<
div
>
</
span
>
<
label
>
<
input
{
/* <span className="sr-only">Search</span> */
}
className
=
"placeholder:italic placeholder:text-slate-400 block bg-white w-full border border-slate-300 rounded-md py-2 pl-9 pr-3 shadow-sm focus:outline-none focus:border-sky-500 focus:ring-sky-500 focus:ring-1 sm:text-sm"
<
span
className
=
"absolute inset-y-0 left-0 flex items-center pl-2"
>
placeholder
=
"Search for anything..."
<
svg
className
=
"h-5 w-5 fill-slate-300"
viewBox
=
"0 0 20 20"
></
svg
>
type
=
"text"
</
span
>
name
=
"search"
<
input
className
=
"placeholder:italic placeholder:text-slate-400 block bg-white w-full border border-slate-300 rounded-md py-2 pl-9 pr-3 shadow-sm focus:outline-none focus:border-sky-500 focus:ring-sky-500 focus:ring-1 sm:text-sm"
placeholder
=
"Search for anything..."
type
=
"text"
name
=
"search"
/>
/>
</
label
>
</
label
>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
<
Outlet
/>
</
div
>
</
div
>
);
<
Outlet
/>
}
</
div
>
\ No newline at end of file
);
}
frontend/src/index.tsx
View file @
0ce1f449
import
React
from
"
react
"
;
import
React
from
"
react
"
;
import
{
createRoot
}
from
"
react-dom/client
"
;
import
{
createRoot
}
from
"
react-dom/client
"
;
import
{
App
}
from
"
./App
"
;
import
{
App
}
from
"
./App
"
;
import
{
AuthProvider
}
from
"
./auth/auth.context
"
;
const
container
=
document
.
getElementById
(
"
root
"
);
const
container
=
document
.
getElementById
(
"
root
"
);
const
root
=
createRoot
(
container
!
);
const
root
=
createRoot
(
container
!
);
root
.
render
(
root
.
render
(
<
React
.
StrictMode
>
<
React
.
StrictMode
>
<
App
/>
<
AuthProvider
>
<
App
/>
</
AuthProvider
>
</
React
.
StrictMode
>
</
React
.
StrictMode
>
);
);
frontend/src/post/posting.tsx
View file @
0ce1f449
import
React
,
{
useState
}
from
"
react
"
;
import
React
,
{
FormEvent
,
useState
}
from
"
react
"
;
import
isLength
from
"
validator/lib/isLength
"
;
import
equals
from
"
validator/lib/equals
"
;
import
{
catchErrors
}
from
"
../helpers
"
;
import
{
PostingType
}
from
"
../types
"
;
import
{
postApi
}
from
"
../apis
"
;
function
Title
()
{
export
default
function
Posting
()
{
const
[
title
,
setTitle
]
=
useState
<
string
>
(
"
질문종류
"
);
const
[
city
,
setCity
]
=
useState
<
string
>
(
"
질문종류
"
);
const
[
theme
,
setTheme
]
=
useState
<
string
>
(
"
질문종류
"
);
const
[
title
,
setTitle
]
=
useState
<
string
>
(
""
);
const
[
text
,
setText
]
=
useState
<
string
>
(
""
);
function
TitleChange
(
e
:
{
target
:
{
value
:
React
.
SetStateAction
<
string
>
}
})
{
const
[
user
,
setUser
]
=
useState
<
PostingType
>
({
setTitle
(
e
.
target
.
value
);
title
:
""
,
}
text
:
""
,
}
theme
:
""
,
city
:
""
,
username
:
""
,
});
function
Body
()
{
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
body
,
setBody
]
=
useState
<
string
>
(
"
질문종류
"
);
const
[
error
,
setError
]
=
useState
(
""
);
const
[
disabled
,
setDisabled
]
=
useState
(
false
);
const
[
success
,
setSuccess
]
=
useState
(
false
);
function
BodyChange
(
e
:
{
target
:
{
value
:
React
.
SetStateAction
<
string
>
}
})
{
async
function
handlePostSubmit
(
event
:
FormEvent
)
{
setBody
(
e
.
target
.
value
);
event
.
preventDefault
();
// prevent onSubmit -> rerendering
try
{
setError
(
""
);
console
.
log
(
"
user data
"
,
user
);
if
(
postingFormMatch
())
{
setLoading
(
true
);
const
res
=
await
postApi
.
posting
(
user
);
console
.
log
(
"
서버연결됬나요
"
,
res
);
console
.
log
(
"
user save
"
);
setSuccess
(
true
);
setError
(
""
);
}
}
catch
(
error
)
{
console
.
log
(
"
에러발생
"
);
catchErrors
(
error
,
setError
);
}
finally
{
setLoading
(
false
);
}
}
}
}
function
SelectCity
()
{
const
[
selectCity
,
setSelectCity
]
=
useState
<
string
>
(
"
질문종류
"
);
function
CityChange
(
e
:
{
target
:
{
value
:
React
.
SetStateAction
<
string
>
}
})
{
function
postingFormMatch
()
{
setSelectCity
(
e
.
target
.
value
);
if
(
!
isLength
(
user
.
title
??
""
,
{
min
:
1
}))
{
setError
(
"
제목을 입력해 주세요.
"
);
return
false
;
}
else
if
(
!
isLength
(
user
.
text
??
""
,
{
min
:
1
}))
{
setError
(
"
내용을 입력해 주세요.
"
);
return
false
;
}
else
if
(
equals
(
city
,
"
질문종류
"
))
{
setError
(
"
테마를 선택해 주세요.
"
);
return
false
;
}
else
if
(
equals
(
theme
,
"
질문종류
"
))
{
setError
(
"
도시를 선택해 주세요.
"
);
return
false
;
}
else
{
return
true
;
}
}
}
return
(
<
select
id
=
"Questions"
className
=
"border border-3 border-black w-1/12"
onChange
=
{
CityChange
}
defaultValue
=
"질문종류"
>
<
option
value
=
"질문종류"
>
도시
</
option
>
<
option
value
=
"Seoul"
>
서울
</
option
>
<
option
value
=
"Busan"
>
부산
</
option
>
<
option
value
=
"Incheon"
>
인천
</
option
>
<
option
value
=
"Daegoo"
>
대구
</
option
>
<
option
value
=
"Kwangjoo"
>
광주
</
option
>
<
option
value
=
"Daejeon"
>
대전
</
option
>
<
option
value
=
"Woolsan"
>
울산
</
option
>
<
option
value
=
"Sejong"
>
세종
</
option
>
<
option
value
=
"Dokdo"
>
독도
</
option
>
<
option
value
=
"Jeju"
>
제주
</
option
>
</
select
>
);
}
function
SelectTheme
()
{
const
titleChange
=
(
event
:
React
.
ChangeEvent
<
HTMLTextAreaElement
>
)
=>
{
const
[
selectTheme
,
setSelectTheme
]
=
useState
<
string
>
(
"
질문종류
"
);
const
title
=
event
.
currentTarget
.
value
;
const
newUser
=
{
...
user
,
title
:
title
};
console
.
log
(
event
.
currentTarget
.
value
);
setTitle
(
event
.
currentTarget
.
value
);
setUser
(
newUser
);
};
function
ThemeChange
(
e
:
{
target
:
{
value
:
React
.
SetStateAction
<
string
>
}
})
{
const
textChange
=
(
event
:
React
.
ChangeEvent
<
HTMLTextAreaElement
>
)
=>
{
setSelectTheme
(
e
.
target
.
value
);
const
text
=
event
.
currentTarget
.
value
;
}
const
newUser
=
{
...
user
,
text
:
text
};
return
(
console
.
log
(
event
.
currentTarget
.
value
);
<
select
setText
(
event
.
currentTarget
.
value
);
id
=
"Questions"
setUser
(
newUser
);
className
=
"border border-3 border-black w-1/12"
};
onChange
=
{
ThemeChange
}
defaultValue
=
"질문종류"
>
<
option
value
=
"질문종류"
>
테마
</
option
>
<
option
value
=
"cycling"
>
사이클링
</
option
>
<
option
value
=
"surfing"
>
서핑
</
option
>
<
option
value
=
"activity"
>
액티비티
</
option
>
<
option
value
=
"camping"
>
캠핑
</
option
>
<
option
value
=
"sking"
>
스키
</
option
>
<
option
value
=
"boat"
>
보트
</
option
>
<
option
value
=
"desert"
>
사막
</
option
>
<
option
value
=
"golf"
>
골프
</
option
>
<
option
value
=
"cave"
>
동굴
</
option
>
<
option
value
=
"history"
>
문화재
</
option
>
<
option
value
=
"zoo"
>
동물원
</
option
>
<
option
value
=
"cycling"
>
사이클링
</
option
>
<
option
value
=
"cycling"
>
{
selectTheme
}
</
option
>
</
select
>
);
}
// 눌렀다는 데이터가 어딘가에 있어야 한다. Map 객체를 이용해서 기타등등
// function postup() {
const
cityChange
=
(
event
:
React
.
ChangeEvent
<
HTMLSelectElement
>
)
=>
{
// axios.post("localhost:3000/api/post/up", {
const
city
=
event
.
currentTarget
.
value
;
// id: "a",
const
newUser
=
{
...
user
,
city
:
city
};
// title: title,
console
.
log
(
event
.
currentTarget
.
value
);
// body: body,
setCity
(
event
.
currentTarget
.
value
);
// date: `${() => new Date()}`,
setUser
(
newUser
);
// theme: selectTheme,
};
// city: selectCity,
// });
const
themeChange
=
(
event
:
React
.
ChangeEvent
<
HTMLSelectElement
>
)
=>
{
// }
const
theme
=
event
.
currentTarget
.
value
;
const
newUser
=
{
...
user
,
theme
:
theme
};
console
.
log
(
event
.
currentTarget
.
value
);
setTheme
(
event
.
currentTarget
.
value
);
setUser
(
newUser
);
};
export
default
function
Posting
()
{
return
(
return
(
<
div
className
=
"flex flex-col border-3"
>
<
div
className
=
"flex flex-col border-3"
>
<
form
className
=
"w-full items-center"
>
<
form
onSubmit
=
{
handlePostSubmit
}
className
=
"w-full items-center"
>
<
div
className
=
"flex flex-row relative"
>
<
div
className
=
"flex flex-row relative"
>
<
p
className
=
"basis-1/12 gap-x-8"
>
Id
</
p
>
<
p
className
=
"basis-1/12 gap-x-8"
>
Id
</
p
>
<
p
className
=
"basis-8/12 invisible"
>
empty
</
p
>
<
p
className
=
"basis-8/12 invisible"
>
empty
</
p
>
<
SelectCity
/>
<
select
<
SelectTheme
/>
name
=
"city"
id
=
"Questions"
className
=
"border border-3 border-black w-1/12"
onChange
=
{
cityChange
}
defaultValue
=
"질문종류"
>
<
option
value
=
"질문종류"
>
도시
</
option
>
<
option
value
=
"Seoul"
>
서울
</
option
>
<
option
value
=
"Busan"
>
부산
</
option
>
<
option
value
=
"Incheon"
>
인천
</
option
>
<
option
value
=
"Daegoo"
>
대구
</
option
>
<
option
value
=
"Kwangjoo"
>
광주
</
option
>
<
option
value
=
"Daejeon"
>
대전
</
option
>
<
option
value
=
"Woolsan"
>
울산
</
option
>
<
option
value
=
"Sejong"
>
세종
</
option
>
<
option
value
=
"Dokdo"
>
독도
</
option
>
<
option
value
=
"Jeju"
>
제주
</
option
>
</
select
>
<
select
name
=
"theme"
id
=
"Questions"
className
=
"border border-3 border-black w-1/12"
onChange
=
{
themeChange
}
defaultValue
=
"질문종류"
>
<
option
value
=
"질문종류"
>
테마
</
option
>
<
option
value
=
"cycling"
>
사이클링
</
option
>
<
option
value
=
"surfing"
>
서핑
</
option
>
<
option
value
=
"activity"
>
액티비티
</
option
>
<
option
value
=
"camping"
>
캠핑
</
option
>
<
option
value
=
"sking"
>
스키
</
option
>
<
option
value
=
"boat"
>
보트
</
option
>
<
option
value
=
"desert"
>
사막
</
option
>
<
option
value
=
"golf"
>
골프
</
option
>
<
option
value
=
"cave"
>
동굴
</
option
>
<
option
value
=
"history"
>
문화재
</
option
>
<
option
value
=
"zoo"
>
동물원
</
option
>
<
option
value
=
"cycling"
>
사이클링
</
option
>
</
select
>
<
button
<
button
type
=
"submit"
type
=
"submit"
...
@@ -107,13 +152,19 @@ export default function Posting() {
...
@@ -107,13 +152,19 @@ export default function Posting() {
<
div
className
=
"flex border-4"
>
<
div
className
=
"flex border-4"
>
<
textarea
<
textarea
onChange
=
{
Title
}
name
=
"title"
onChange
=
{
titleChange
}
placeholder
=
"title"
placeholder
=
"title"
className
=
"w-full h-8"
className
=
"w-full h-8"
></
textarea
>
></
textarea
>
</
div
>
</
div
>
<
div
onChange
=
{
Body
}
className
=
"flex border-2"
>
<
div
className
=
"flex border-2"
>
<
textarea
placeholder
=
"body"
className
=
"w-full h-96"
></
textarea
>
<
textarea
onChange
=
{
textChange
}
name
=
"text"
placeholder
=
"text"
className
=
"w-full h-96"
></
textarea
>
</
div
>
</
div
>
</
form
>
</
form
>
</
div
>
</
div
>
...
...
frontend/src/types/index.tsx
View file @
0ce1f449
...
@@ -4,14 +4,23 @@ export interface IUser {
...
@@ -4,14 +4,23 @@ export interface IUser {
_id
?:
string
;
_id
?:
string
;
}
}
export
interface
PostType
{
export
interface
LoginUser
{
id
:
string
;
email
:
string
;
title
:
string
;
password
:
string
;
body
?:
string
;
}
date
:
string
;
export
interface
PostType
extends
PostingType
{
date
?:
string
;
counts
:
number
;
counts
:
number
;
theme
?:
string
;
id
?:
string
;
city
?:
string
;
}
export
interface
PostingType
{
title
:
string
;
text
?:
string
;
theme
:
string
;
city
:
string
;
username
:
string
;
}
}
export
interface
SignupUser
{
export
interface
SignupUser
{
...
...
src/controllers/post.controller.ts
View file @
0ce1f449
import
{
NextFunction
,
Request
,
Response
}
from
"
express
"
;
import
{
NextFunction
,
Request
,
Response
}
from
"
express
"
;
import
isLength
from
"
validator/lib/isLength
"
;
import
equals
from
"
validator/lib/equals
"
;
import
{
asyncWrap
}
from
"
../helpers
"
;
import
{
asyncWrap
}
from
"
../helpers
"
;
import
jwt
,
{
JwtPayload
}
from
"
jsonwebtoken
"
;
import
{
jwtCofig
,
envConfig
,
cookieConfig
}
from
"
../config
"
;
import
{
postDb
}
from
"
../db
"
;
import
{
postDb
}
from
"
../db
"
;
export
const
posting
=
asyncWrap
(
async
(
req
,
res
)
=>
{
const
{
title
,
text
,
theme
,
city
,
username
}
=
req
.
body
;
console
.
log
(
"
body
"
,
req
.
body
);
// 1) title 빈 문자열인지 확인
if
(
!
isLength
(
title
??
""
,
{
min
:
1
}))
{
return
res
.
status
(
422
).
send
(
"
제목을 한 글자 이상 입력해주세요
"
);
}
// 2) body 빈 문자열인지 확인
if
(
!
isLength
(
text
??
""
,
{
min
:
1
}))
{
return
res
.
status
(
422
).
send
(
"
제목을 한 글자 이상 입력해주세요
"
);
}
// 3) theme dropdown default-value "테마"일 경우 에러
if
(
equals
(
theme
,
"
질문종류
"
))
{
return
res
.
status
(
422
).
send
(
"
테마를 입력해 주세요
"
);
}
// 4) city dropdown default-value "도시"일 경우 에러
if
(
equals
(
city
,
"
질문종류
"
))
{
return
res
.
status
(
422
).
send
(
"
도시를 선택해 주세요
"
);
}
// 5) username 확인 필요 없음
// 6)
const
newPosting
=
await
postDb
.
createPosting
({
title
,
text
,
theme
,
city
,
username
,
});
return
res
.
json
(
newPosting
);
});
export
const
post
=
asyncWrap
(
async
(
req
,
res
)
=>
{
const
{
title
,
theme
,
city
,
username
,
date
,
counts
}
=
req
.
body
;
export
const
posting
=
asyncWrap
(
async
(
req
,
res
)
=>
{
console
.
log
(
"
body
"
,
req
.
body
);
const
{
title
,
body
,
date
,
theme
,
city
}
=
req
.
body
;
// 1) DB postings에서 title, theme, city, username, date 불러오기
// 1) title 빈 문자열인지 확인
const
titleString
=
postDb
.
checkTitleNull
(
title
,
);
// 2) 불러온 데이터 + counts posts에 저장
if
(
!
titleString
)
{
return
res
.
status
(
422
).
send
(
`
${
title
}
제목을 입력해 주세요`
);
// 3) DB posts에서 데이터 불러서 frontend로 보내기
}
// 2) body 빈 문자열인지 확인
const
bodyString
=
postDb
.
checkBodyNull
(
body
,
);
if
(
!
bodyString
)
{
return
res
.
status
(
422
).
send
(
`
${
body
}
여행 후기를 입력해 주세요`
);
}
// 3) submit 이벤트 발생시 date값 입력
const
dateGet
=
postDb
.
getSubmitDate
(
date
,
);
// 4) theme dropdown default-value일 경우 에러
const
themeSelect
=
postDb
.
selectTheme
(
theme
,
);
if
(
!
themeSelect
)
{
return
res
.
status
(
422
).
send
(
`
${
theme
}
테마를 선택해 주세요`
)
}
// 5) cuty dropdown default-value일 경우 에러
const
citySelect
=
postDb
.
selectCity
(
city
,
);
if
(
!
citySelect
)
{
return
res
.
status
(
422
).
send
(
`
${
city
}
도시를 선택해 주세요`
)
}
// 6) 토큰 생성
const
token
=
jwt
.
sign
({
},
jwtCofig
.
secret
,
{
expiresIn
:
jwtCofig
.
expires
,
});
// 7) 쿠키에 토큰 저장
res
.
cookie
(
cookieConfig
.
name
,
token
,
{
maxAge
:
cookieConfig
.
maxAge
,
path
:
"
/
"
,
httpOnly
:
envConfig
.
mode
===
"
production
"
,
secure
:
envConfig
.
mode
===
"
production
"
,
});
// 8) 사용자 반환
res
.
json
({});
});
});
src/db/post.db.ts
View file @
0ce1f449
import
{
PostType
,
Post
}
from
"
../models
"
;
import
{
Posting
,
PostingType
}
from
"
../models
"
;
import
{
Post
,
PostType
}
from
"
../models
"
;
export
const
createPosting
=
async
(
posting
:
PostingType
)
=>
{
const
newPosting
=
await
Posting
.
create
({
title
:
posting
.
title
,
text
:
posting
.
text
,
theme
:
posting
.
theme
,
city
:
posting
.
city
,
username
:
posting
.
username
,
});
return
newPosting
;
};
export
const
createPost
=
async
(
post
:
PostType
)
=>
{
export
const
createPost
=
async
(
post
:
PostType
)
=>
{
const
newPost
=
await
Post
.
create
(
post
);
const
newPost
=
await
Post
.
create
({
return
newPost
;
title
:
post
.
title
,
theme
:
post
.
theme
,
city
:
post
.
city
,
date
:
post
.
date
,
counts
:
post
.
counts
,
});
};
};
export
const
checkTitleNull
=
async
(
title
:
string
,
)
=>
{
}
export
const
checkBodyNull
=
async
(
body
:
string
,
)
=>
{
}
export
const
getSubmitDate
=
async
(
date
:
string
,
)
=>
{
}
export
const
selectTheme
=
async
(
theme
:
string
,
)
=>
{
let
user
;
if
(
theme
!=
"
테마
"
)
{
}
}
export
const
selectCity
=
async
(
city
:
string
,
)
=>
{
let
user
;
if
(
city
!=
"
도시
"
)
{
}
}
src/models/index.ts
View file @
0ce1f449
export
{
default
as
User
,
IUser
}
from
"
./user.model
"
;
export
{
default
as
User
,
IUser
}
from
"
./user.model
"
;
export
{
default
as
Posting
,
PostingType
}
from
"
./posting.model
"
;
export
{
default
as
Post
,
PostType
}
from
"
./post.model
"
;
export
{
default
as
Post
,
PostType
}
from
"
./post.model
"
;
src/models/post.model.ts
View file @
0ce1f449
import
{
model
,
Schema
}
from
"
mongoose
"
;
import
{
model
,
Schema
,
Types
}
from
"
mongoose
"
;
import
{
PostingType
}
from
"
./posting.model
"
;
export
interface
PostType
{
export
interface
PostType
extends
PostingType
{
id
:
string
;
date
?:
string
;
title
:
string
;
counts
:
number
;
date
:
Date
;
id
?:
string
;
body
:
string
;
}
counts
?:
number
;
theme
:
string
;
const
postSchema
=
new
Schema
<
PostType
>
({
city
:
string
;
title
:
{
type
:
String
},
}
theme
:
{
type
:
String
},
city
:
{
type
:
String
},
const
schema
=
new
Schema
<
PostType
>
({
username
:
{
type
:
String
},
id
:
{
type
:
String
},
date
:
{
type
:
String
},
title
:
{
type
:
String
},
counts
:
{
type
:
Number
},
date
:
{
type
:
Date
},
body
:
{
type
:
String
},
counts
:
{
type
:
Number
},
theme
:
{
type
:
String
},
city
:
{
type
:
String
},
});
});
export
default
model
<
PostType
>
(
"
Post
"
,
s
chema
);
export
default
model
<
PostType
>
(
"
Post
"
,
postS
chema
);
src/models/posting.model.ts
0 → 100644
View file @
0ce1f449
import
{
model
,
Schema
}
from
"
mongoose
"
;
export
interface
PostingType
{
title
:
string
;
text
?:
string
;
theme
:
string
;
city
:
string
;
username
?:
string
;
}
const
postingSchema
=
new
Schema
<
PostingType
>
({
title
:
{
type
:
String
,
required
:
true
,
},
text
:
{
type
:
String
,
required
:
true
,
},
theme
:
{
type
:
String
,
},
city
:
{
type
:
String
,
},
username
:
{
type
:
String
,
},
});
export
default
model
<
PostingType
>
(
"
Posting
"
,
postingSchema
);
src/routes/index.ts
View file @
0ce1f449
...
@@ -7,7 +7,7 @@ const router = express.Router();
...
@@ -7,7 +7,7 @@ const router = express.Router();
router
.
use
(
"
/users
"
,
userRouter
);
router
.
use
(
"
/users
"
,
userRouter
);
router
.
use
(
"
/auth
"
,
authRouter
);
router
.
use
(
"
/auth
"
,
authRouter
);
router
.
use
(
"
/posts
"
,
postRouter
);
//router.route
router
.
use
(
"
/posts
"
,
postRouter
);
//posting함수 -> mongodb에 posts json형식으로 저장
export
default
router
;
export
default
router
;
src/routes/post.route.ts
View file @
0ce1f449
...
@@ -3,6 +3,6 @@ import { postCtrl } from "../controllers";
...
@@ -3,6 +3,6 @@ import { postCtrl } from "../controllers";
const
router
=
express
.
Router
();
const
router
=
express
.
Router
();
router
.
route
(
"
/
posting
"
).
post
(
postCtrl
.
posting
);
router
.
route
(
"
/
"
).
post
(
postCtrl
.
posting
);
export
default
router
;
export
default
router
;
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment