Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"@mui/icons-material": "^5.14.12",
"@mui/material": "^5.14.9",
"@mui/styles": "^5.15.13",
"@pvi/core": "workspace:^",
"@pvi/react": "workspace:^",
"@policy-maker/core": "workspace:^",
"@policy-maker/next": "workspace:^",
"@tanstack/react-query": "^5.4.3",
"@types/node": "20.6.0",
"@types/react": "18.2.21",
Expand Down
13 changes: 13 additions & 0 deletions client/src/app/article/[articleId]/ArticleDetail.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.page {
background-color: black;
}

.header {
margin: 0 0 var(--primitives_spacing-4) 0;
}

.body {
color: white;
padding: 0 24%;
margin-bottom: var(--primitives_spacing-10);
}
109 changes: 109 additions & 0 deletions client/src/app/article/[articleId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"use client";

import "./ArticleDetail.css";
import Header from "@components/Header";

import Footer from "@components/Footer";
import { useEffect, useState } from "react";
import { z } from "zod";
import typography from "@styles/typography.module.css";

type ArticleDetailProps = {
params: {
articleId: number;
};
};

export default function ArticleDetail(props: ArticleDetailProps) {
const id = props.params.articleId;
const [result, setResult] = useState("");

useEffect(() => {
fetch("http://localhost:8080/articles/" + id)
.then((res) => res.json())
.then(z.object({ content: z.string() }).parse)
.then(({ content }) => setResult(content));
}, []);

return (
<div className="page">
<div className="header">
<Header />
</div>
<div className="body">
<div className={typography.h3}>{result}</div>
</div>
<div className="footer">
<Footer />
</div>
</div>
);
}

// 서버 데이터로 대체 예정입니다.
const samplePostData = {
id: 1,
title: "지원 사업 관련 질문 있습니다!",
content:
"나무들이 서로 속삭이는 듯한 소리가 숲속을 가득 채우고 있었다. 간간히 부는 바람이 나뭇잎을 흔들며, 그 소리는 마치 오래된 이야기를 들려주는 것 같았다. 이 숲의 한가운데서, 한 소년이 고개를 들어 하늘을 바라보았다. 햇빛이 나뭇가지 사이로 비치며, 그의 얼굴에 따스한 빛을 더했다. 소년은 숲이 주는 평화로움 속에서 잠시의 여유를 즐기며, 모험을 꿈꾸었다. 이 순간, 그는 어떠한 걱정도, 두려움도 잊고 오직 순수한 기쁨을 느끼며, 자연과 하나가 되었다.",
postType: "INFORMATION" as const,
likesCount: 20,
viewsCount: 100,
commentsCount: 5,
createdTime: new Date("2021-08-01"),
lastModifiedTime: new Date("2021-08-01"),
createdUser: {
id: 1,
name: "날아오르는 고라파덕",
thumbnailImage: null,
},
tags: [
{
id: 1,
name: "정보",
},
{
id: 2,
name: "월간Best",
},
],
};

const sampleCommentData = [
{
id: 1,
content: "너무 좋은 글이네요! 감사합니다.",
likesCount: 20,
createdTime: new Date("2021-08-01"),
lastModifiedTime: new Date("2021-08-01"),
createdUser: {
id: 1,
name: "날아오르는 고라파덕",
thumbnailImage: null,
},
},
{
id: 2,
content: "너무 좋은 글이네요! 감사합니다.",
likesCount: 20,
createdTime: new Date("2021-08-01"),
lastModifiedTime: new Date("2021-08-01"),
createdUser: {
id: 1,
name: "날아오르는 고라파덕",
thumbnailImage: null,
},
},
{
id: 3,
content: "너무 좋은 글이네요! 감사합니다.",
likesCount: 20,
createdTime: new Date("2021-08-01"),
lastModifiedTime: new Date("2021-08-01"),
createdUser: {
id: 1,
name: "날아오르는 고라파덕",
thumbnailImage: null,
},
},
];
32 changes: 32 additions & 0 deletions client/src/app/article/write/ArticleWrite.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.page {
background-color: black;
}

.header {
margin: 0 0 var(--primitives_spacing-4) 0;
}

.body {
color: white;
padding: 0 24%;
margin-bottom: var(--primitives_spacing-10);
}

.body input {
border: none;
outline: none;
background-color: var(--surface-primary);
padding: 0;
height: 50px;
margin-right: var(--primitives_spacing-4);
}

.body button {
border: none;
outline: none;
cursor: pointer;
background-color: var(--surface-primary);
color: var(--text-secondary);
text-align: left;
padding: 10px;
}
50 changes: 50 additions & 0 deletions client/src/app/article/write/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"use client";

import "./ArticleWrite.css";
import Header from "@components/Header";

import Footer from "@components/Footer";
import { useCallback, useState } from "react";
import { useRouter } from "next/navigation";
import { z } from "zod";

import typography from "@styles/typography.module.css";

export default function ArticleWrite() {
const [data, setData] = useState("");
const router = useRouter();

const onSubmit = useCallback(() => {
fetch("http://localhost:8080/articles", {
method: "POST",
body: JSON.stringify({ content: data }),
headers: {
"Content-Type": "application/json",
},
})
.then((res) => res.json())
.then(
z.object({
insertedId: z.number(),
}).parse,
)
.then(({ insertedId }) => router.push(`/article/${insertedId}`));
}, [data, router]);

return (
<div className="page">
<div className="header">
<Header />
</div>
<div className="body">
<div className={typography.h4}>
<input value={data} onChange={(e) => setData(e.target.value)} />
<button onClick={onSubmit}>전송</button>
</div>
</div>
<div className="footer">
<Footer />
</div>
</div>
);
}
9 changes: 9 additions & 0 deletions client/src/app/dev/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ import styled from "@emotion/styled";
import Lottie from "lottie-react";
import logoAnimation from "../../../public/logo_animation.json";
import layout from "../../styles/layout";
import { useView } from "library/policy-maker/next";
import viewPolicy from "@core/policy/view";
import { UserRepository } from "@core/repository/user";

export default function Dev() {
const { view } = useView({
policy: viewPolicy.user.user(1),
from: () => UserRepository.getUser(1),
});

console.log(view);
return (
<Main>
<Title>COMPONENTS</Title>
Expand Down
30 changes: 30 additions & 0 deletions client/src/app/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"use client";

import { useEffect } from "react";

export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Log the error to an error reporting service
console.error(error);
}, [error]);

return (
<div>
<h2>Something went wrong!</h2>
<button
onClick={
// Attempt to recover by trying to re-render the segment
() => reset()
}
>
Try again
</button>
</div>
);
}
6 changes: 3 additions & 3 deletions client/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Metadata } from "next";
import Providers from "@/utils/next-query-resolver/Providers";
import "./globals.css";
import "./fonts.css";
import { Provider } from "@policy-maker/next";

export const metadata: Metadata = {
title: "Create Next App",
Expand All @@ -14,10 +14,10 @@ export default function RootLayout({
children: React.ReactNode;
}) {
return (
<Providers>
<Provider>
<html lang="en">
<body>{children}</body>
</html>
</Providers>
</Provider>
);
}
3 changes: 3 additions & 0 deletions client/src/app/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Loading() {
return <div>Loading...</div>;
}
49 changes: 32 additions & 17 deletions client/src/app/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,39 @@ import {
Snackbar,
} from "@mui/material";
import "./Login.css";
import { UserApi } from "@core/api/user";
import { useView } from "library/policy-maker-2/react";
import { VPMe } from "@core/policy/user/view/me";

import { useIntentInput, useIntentSubmit } from "library/policy-maker/next";
import intentPolicy from "@core/policy/intent";
import { UserRepository } from "@core/repository/user";
import { useRouter } from "next/navigation";

function Login() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const { submit, isValid } = useIntentSubmit({
policy: intentPolicy.user.login(),
to: UserRepository.postLogin,
});

const {
set,
values: { email, password },
} = useIntentInput({
policy: intentPolicy.user.login(),
initialValue: () => ({ email: "", password: "" }),
});

// const [email, setEmail] = useState("");
// const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null);
const [openSnackbar, setOpenSnackbar] = useState<boolean>(false);
const { view } = useView({
policy: VPMe(),
from: () => UserApi.getMe.client({}).catch(() => null),
});
const router = useRouter();

const handleLogin = () => {
UserApi.postSignIn
.client({ body: { email, password } })
.then((data) => {
localStorage.setItem("xctoken", data.token);
if (!isValid) alert("이메일 또는 비밀번호를 확인해주세요");

submit()
.then(({ token }) => {
localStorage.setItem("xctoken", token);
router.push("/");
})
.catch((e) => {
setError(e.message);
Expand All @@ -51,23 +65,24 @@ function Login() {
fullWidth
variant="outlined"
margin="normal"
value={email}
onChange={(e) => setEmail(e.target.value)}
value={email.value}
onChange={(e) => set({ email: e.target.value })}
/>
<TextField
label="Password"
fullWidth
variant="outlined"
type="password"
margin="normal"
value={password}
onChange={(e) => setPassword(e.target.value)}
value={password.value}
onChange={(e) => set({ password: e.target.value })}
/>
<Button
variant="contained"
color="primary"
fullWidth
onClick={handleLogin}
disabled={!isValid}
>
Login
</Button>
Expand Down
Loading