[modern-react] 리액트 스터디 파일 추가
11
modern-react/my-modern/App.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const APP_TITLE = 'React 앱';
|
||||
|
||||
export function getTriangle(base, height) {
|
||||
return base * height / 2;
|
||||
}
|
||||
|
||||
export class Article {
|
||||
getAppTitle() {
|
||||
return APP_TITLE;
|
||||
}
|
||||
}
|
||||
5
modern-react/my-modern/Util.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export default class Util {
|
||||
static getCircleArea(radius) {
|
||||
return (radius ** 2) * Math.PI;
|
||||
}
|
||||
}
|
||||
10
modern-react/my-modern/const.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>리액트 입문</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="const.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
3
modern-react/my-modern/const.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const author = '야마다 요시히로';
|
||||
author = 'WINGS 프로젝트';
|
||||
console.log(author);
|
||||
10
modern-react/my-modern/destruct_list.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>리액트 입문</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="destruct_list.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
12
modern-react/my-modern/destruct_list.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const list = [10, 20, 30];
|
||||
const [x, y, z] = list;
|
||||
console.log(x, y, z); // 결과: 10 20 30
|
||||
|
||||
const [a, b] = list;
|
||||
console.log(a, b); // 결과: 10 20
|
||||
|
||||
const [l, m, n, o] = list;
|
||||
console.log(l, m, n, o); // 결과: 10 20 30 undefined
|
||||
|
||||
const [p, , r] = list;
|
||||
console.log(p, r); // 결과: 10 30
|
||||
10
modern-react/my-modern/destruct_list_rest.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>리액트 입문</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="destruct_list_rest.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
3
modern-react/my-modern/destruct_list_rest.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const list = [10, 20, 30];
|
||||
const [one, ...rest] = list;
|
||||
console.log(one, rest); // 결과: 10 [20, 30]
|
||||
10
modern-react/my-modern/destruct_nest.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>리액트 입문</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="destruct_nest.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
10
modern-react/my-modern/destruct_nest.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const member = {
|
||||
fullname: '사토 리오',
|
||||
address: {
|
||||
prefecture: '스즈오카현',
|
||||
city: '후지에다시'
|
||||
}
|
||||
};
|
||||
const { address, address: { city } } = member;
|
||||
console.log(address); // 결과: { prefecture: '스즈오카현', city: '후지에다시' }
|
||||
console.log(city); // 결과: 후지에다시
|
||||
10
modern-react/my-modern/destruct_nest_array.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>리액트 입문</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="destruct_nest_array.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
3
modern-react/my-modern/destruct_nest_array.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const list = [200, [300, 301, 302]];
|
||||
const [x, [y1, y2, y3]] = list;
|
||||
console.log(y1, y2, y3); // 결과: 300 301 302
|
||||
10
modern-react/my-modern/destruct_obj.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>리액트 입문</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="destruct_obj.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
19
modern-react/my-modern/destruct_obj.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const member = {
|
||||
fullname: '사토 리오',
|
||||
sex: '여성',
|
||||
age: 18
|
||||
};
|
||||
const { fullname, sex, memo = '---' } = member;
|
||||
console.log(sex, fullname, memo); // 결과: 여성 사토 리오 ---
|
||||
|
||||
// const { sex: gender } = member;
|
||||
// console.log(gender); // 결과: 여성
|
||||
|
||||
// const { fullname, ...rest } = member;
|
||||
// console.log(fullname); // 결과: 사토 리오
|
||||
// console.log(rest); // 결과: { sex: '여성', age: 18 }
|
||||
|
||||
// let fullname, sex, memo;
|
||||
// ({ fullname, sex, memo = '---' } = member);
|
||||
// console.log(sex, fullname, memo); // 결과: 여성 사토 리오 ---
|
||||
|
||||
10
modern-react/my-modern/destruct_obj_param.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>리액트 입문</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="destruct_obj_param.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
10
modern-react/my-modern/destruct_obj_param.js
Normal file
@@ -0,0 +1,10 @@
|
||||
function greet({ name, age }) {
|
||||
console.log(`안녕하세요, 저는 ${name}, ${age}세 입니다.`);
|
||||
}
|
||||
|
||||
const my = { name: '사토리오', sex: '여성', age: 18 };
|
||||
greet(my); // 결과: 안녕하세요, 저는 사토리오, 18세 입니다.
|
||||
|
||||
// function greet(obj) {
|
||||
// console.log(` 안녕하세요, 저는 ${obj.name}, ${obj.age}세 입니다. `);
|
||||
// }
|
||||
10
modern-react/my-modern/func_def.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>리액트 입문</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="func_def.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
10
modern-react/my-modern/func_def.js
Normal file
@@ -0,0 +1,10 @@
|
||||
function getTrapezoidArea(upper = 1, lower = 1, height = 1) {
|
||||
return (upper + lower) * height / 2;
|
||||
}
|
||||
|
||||
console.log(getTrapezoidArea(10, 5, 3)); // 결과: 22.5 (=(10 + 5) × 3 ÷ 2)
|
||||
console.log(getTrapezoidArea(10, 5)) // 결과: 7.5 (=(10 + 5) × 1 ÷ 2)
|
||||
console.log(getTrapezoidArea(10)); // 결과: 5 (=(10 + 1) × 1 ÷ 2)
|
||||
|
||||
|
||||
// function getTrapezoidArea(upper = 1, lower = upper, height = upper) { ... }
|
||||
10
modern-react/my-modern/func_rest.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>리액트 입문</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="func_rest.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
modern-react/my-modern/func_rest.js
Normal file
@@ -0,0 +1,13 @@
|
||||
function sum(...nums) {
|
||||
let result = 0;
|
||||
for (const num of nums) {
|
||||
result += num;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
console.log(sum(10, 25, 2)); // 결과: 37
|
||||
console.log(sum(7, 13, 25, 6, 100)); // 결과: 151
|
||||
// console.log(sum(...[10, 25, 2]));
|
||||
|
||||
|
||||
10
modern-react/my-modern/let.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>리액트 입문</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="let.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
5
modern-react/my-modern/let.js
Normal file
@@ -0,0 +1,5 @@
|
||||
if (true) {
|
||||
let x = 13;
|
||||
}
|
||||
// 블록 아래에서 선언한 변수를 참조하면 ...
|
||||
console.log(x);
|
||||
10
modern-react/my-modern/module_alias.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>리액트 입문</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module" src="module_alias.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
3
modern-react/my-modern/module_alias.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import { getTriangle as tri } from './App.js';
|
||||
|
||||
console.log(tri(10, 2)); // 결과: 10
|
||||
10
modern-react/my-modern/module_all.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>리액트 입문</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module" src="module_all.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
3
modern-react/my-modern/module_all.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import * as app from './App.js';
|
||||
|
||||
console.log(app.getTriangle(10, 2)); // 결과: 10
|
||||
10
modern-react/my-modern/module_basic.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>리액트 입문</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module" src="module_basic.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
6
modern-react/my-modern/module_basic.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Article, getTriangle } from './App.js';
|
||||
|
||||
console.log(getTriangle(10, 5)); // 결과: 25
|
||||
|
||||
const a = new Article();
|
||||
console.log(a.getAppTitle()); // 결과: React 앱
|
||||
10
modern-react/my-modern/module_dynamic.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>리액트 입문</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module" src="module_dynamic.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
6
modern-react/my-modern/module_dynamic.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import('./App.js').then(app => {
|
||||
console.log(app.getTriangle(10, 5)); // 결과: 25
|
||||
|
||||
const a = new app.Article();
|
||||
console.log(a.getAppTitle()); // 결과: React 앱
|
||||
});
|
||||
10
modern-react/my-modern/module_use_util.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>리액트 입문</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module" src="module_use_util.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
3
modern-react/my-modern/module_use_util.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import Util from './Util.js';
|
||||
|
||||
console.log(Util.getCircleArea(10)); // 결과: 314.1592653589793
|
||||
10
modern-react/my-modern/number.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>리액트 입문</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="number.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
2
modern-react/my-modern/number.js
Normal file
@@ -0,0 +1,2 @@
|
||||
const value = 123_456_789;
|
||||
console.log(value);
|
||||
10
modern-react/my-modern/obj_computed.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>리액트 입문</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="obj_computed.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
8
modern-react/my-modern/obj_computed.js
Normal file
@@ -0,0 +1,8 @@
|
||||
let i = 0;
|
||||
const member = {
|
||||
[`attr${++i}`]: '사토 리오',
|
||||
[`attr${++i}`]: '여성',
|
||||
[`attr${++i}`]: '18세'
|
||||
};
|
||||
console.log(member);
|
||||
// 결과: { attr1: '사토 리오', attr2: '여성', attr3: '18세' }
|
||||
10
modern-react/my-modern/obj_method.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>리액트 입문</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="obj_method.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
15
modern-react/my-modern/obj_method.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const member = {
|
||||
name: '사토 리오',
|
||||
greet: function() {
|
||||
console.log(`안녕하세요, ${this.name} 님!`);
|
||||
}
|
||||
}
|
||||
|
||||
// const member = {
|
||||
// name: '사토 리오',
|
||||
// greet() {
|
||||
// console.log(`안녕하세요, ${this.name} 님!`);
|
||||
// }
|
||||
// }
|
||||
|
||||
member.greet();
|
||||
10
modern-react/my-modern/obj_prop.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>리액트 입문</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="obj_prop.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
7
modern-react/my-modern/obj_prop.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const title = '리액트 입문';
|
||||
const price = 500;
|
||||
|
||||
const book = { title, price };
|
||||
// const book = { title: title, price: price };
|
||||
|
||||
console.log(book);
|
||||
10
modern-react/my-modern/optional.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>리액트 입문</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="optional.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
modern-react/my-modern/optional.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const str = null;
|
||||
console.log(str.substring(1));
|
||||
|
||||
// if (str !== null && str !== undefined) {
|
||||
// console.log(str.substring(1));
|
||||
// }
|
||||
|
||||
// const str = null;
|
||||
// console.log(str?.substring(1));
|
||||
|
||||
// const str = '위키북스';
|
||||
// console.log(str?.substring(1));
|
||||
|
||||
13
modern-react/my-modern/package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "my-modern",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "App.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
10
modern-react/my-modern/template.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>리액트 입문</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="template.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
5
modern-react/my-modern/template.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const fullname = '홍길동';
|
||||
const msg = `안녕하세요, ${fullname} 님!
|
||||
오늘 하루 잘 지내셨나요?`;
|
||||
console.log(msg);
|
||||
|
||||
36
modern-react/my-next/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
7
modern-react/my-next/jsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
18
modern-react/my-next/next.config.mjs
Normal file
@@ -0,0 +1,18 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
serverActions: true,
|
||||
},
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
hostname: 'books.google.com'
|
||||
},
|
||||
{
|
||||
hostname: 'wikibook.co.kr'
|
||||
},
|
||||
]
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
5029
modern-react/my-next/package-lock.json
generated
Normal file
26
modern-react/my-next/package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "my-next",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"vercel-build": "prisma generate && next build",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.12.1",
|
||||
"@vercel/postgres": "^0.8.0",
|
||||
"next": "14.2.2",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.2",
|
||||
"postcss": "^8",
|
||||
"prisma": "^5.17.0",
|
||||
"tailwindcss": "^3.4.1"
|
||||
}
|
||||
}
|
||||
8
modern-react/my-next/postcss.config.mjs
Normal file
@@ -0,0 +1,8 @@
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
BIN
modern-react/my-next/prisma/dev.db
Normal file
29
modern-react/my-next/prisma/schema.prisma
Normal file
@@ -0,0 +1,29 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// datasource db {
|
||||
// provider = "postgresql"
|
||||
// url = env("POSTGRES_PRISMA_URL")
|
||||
// directUrl = env("POSTGRES_URL_NON_POOLING")
|
||||
// }
|
||||
|
||||
model reviews {
|
||||
id String @id
|
||||
title String
|
||||
author String
|
||||
price Int
|
||||
publisher String
|
||||
published String
|
||||
image String
|
||||
read DateTime @default(now())
|
||||
memo String
|
||||
}
|
||||
1
modern-react/my-next/public/next.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
modern-react/my-next/public/vercel.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
|
||||
|
After Width: | Height: | Size: 629 B |
28
modern-react/my-next/src/app/books/[[...keyword]]/layout.js
Normal file
@@ -0,0 +1,28 @@
|
||||
'use client';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useRef } from 'react';
|
||||
|
||||
// "/books/keyword" 아래에 적용되는 레이아웃
|
||||
export default function BooksLayout({ children }) {
|
||||
const router = useRouter();
|
||||
const txtKeyword = useRef(null);
|
||||
// [검색] 버튼 클릭 시 '/books/keyword'로 리디렉션된다.
|
||||
const handleSearch = () => {
|
||||
router.push(`/books/${txtKeyword.current.value}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<form className="mt-2 mb-4">
|
||||
<input type="text" ref={txtKeyword}
|
||||
className="bg-gray-100 text-black border border-gray-600 rounded mr-2 px-2 py-2 focus:bg-white focus:outline-none focus:border-red-500" />
|
||||
<button type="button" onClick={handleSearch}
|
||||
className="bg-blue-600 text-white rounded px-4 py-2 hover:bg-blue-500">
|
||||
검색</button>
|
||||
</form>
|
||||
<hr />
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export default function ApiLoading() {
|
||||
return (
|
||||
<div className="flex justify-center" aria-label="Now Loading...">
|
||||
<div className="animate-spin h-20 w-20 mt-5 border-8 border-blue-500 rounded-full border-b-transparent"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
16
modern-react/my-next/src/app/books/[[...keyword]]/page.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import LinkedBookDetails from '@/components/LinkedBookDetails';
|
||||
import { getBooksByKeyword } from '@/lib/getter';
|
||||
|
||||
// 루트 매개변수 키워드 가져오기(기본값은 리액트)
|
||||
export default async function BookResult({ params: { keyword = '리액트' } }) {
|
||||
// 주어진 키워드로 도서 정보 검색
|
||||
const books = await getBooksByKeyword(keyword);
|
||||
return (
|
||||
<>
|
||||
{/* 획득한 도서 목록 보기 */}
|
||||
{books.map((b,i) => (
|
||||
<LinkedBookDetails book={b} index={i + 1} key={b.id} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
30
modern-react/my-next/src/app/edit/[id]/page.js
Normal file
@@ -0,0 +1,30 @@
|
||||
// export default function EditPage({ params }) {
|
||||
// return <p>No. {params.id}의 리뷰를 표시하고 있다.</p>;
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// Code 11-4-12
|
||||
import BookDetails from '@/components/BookDetails';
|
||||
import FormEdit from '@/components/FormEdit';
|
||||
import { getBookById, getReviewById } from '@/lib/getter';
|
||||
|
||||
export default async function EditPage({ params }) {
|
||||
const book = await getBookById(params.id);
|
||||
const review = await getReviewById(params.id);
|
||||
// 'YYYY-MM-DD' 형식의 날짜 생성
|
||||
const read = (review?.read || new Date()).toLocaleDateString('sv-SE');
|
||||
|
||||
// const read = (review?.read || new Date()).toLocaleDateString('ko-KR',
|
||||
// { year: 'numeric', month: '2-digit', day: '2-digit' }
|
||||
// ).replaceAll('/', '-')
|
||||
|
||||
return (
|
||||
<div id="form">
|
||||
<BookDetails book={book} />
|
||||
<hr />
|
||||
{/* 편집 양식 생성 */}
|
||||
<FormEdit src={{ id: book.id, read, memo: review?.memo }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
BIN
modern-react/my-next/src/app/favicon.ico
Normal file
|
After Width: | Height: | Size: 25 KiB |
34
modern-react/my-next/src/app/globals.css
Normal file
@@ -0,0 +1,34 @@
|
||||
/* Tailwind CSS에서 사용하는 스타일 정의 활성화 */
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--foreground-rgb: 0, 0, 0;
|
||||
--background-start-rgb: 214, 219, 220;
|
||||
--background-end-rgb: 255, 255, 255;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--foreground-rgb: 255, 255, 255;
|
||||
--background-start-rgb: 0, 0, 0;
|
||||
--background-end-rgb: 0, 0, 0;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
color: rgb(var(--foreground-rgb));
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent,
|
||||
rgb(var(--background-end-rgb))
|
||||
)
|
||||
rgb(var(--background-start-rgb));
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
}
|
||||
65
modern-react/my-next/src/app/layout.js
Normal file
@@ -0,0 +1,65 @@
|
||||
// import { Inter } from "next/font/google";
|
||||
// import "./globals.css";
|
||||
|
||||
// // 폰트 정보 설정
|
||||
// const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
// // 메타 정보 준비
|
||||
// export const metadata = {
|
||||
// title: "Create Next App",
|
||||
// description: "Generated by create next app",
|
||||
// };
|
||||
|
||||
// // 루트 레이아웃 준비
|
||||
// export default function RootLayout({ children }) {
|
||||
// return (
|
||||
// <html lang="en">
|
||||
// <body className={inter.className}>{children}</body>
|
||||
// </html>
|
||||
// );
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// Code 11-4-1
|
||||
import Link from 'next/link';
|
||||
// Tailwind.css 설정 가져오기
|
||||
import './globals.css';
|
||||
import { Inconsolata } from 'next/font/google';
|
||||
|
||||
// 구글 폰트 활성화
|
||||
const fnt = Inconsolata({ subsets: ['latin'] })
|
||||
|
||||
// 메타데이터 정의
|
||||
export const metadata = {
|
||||
title: 'Reading Recorder',
|
||||
description: '내가 읽은 책을 기록하는 앱',
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="ko">
|
||||
<body className={fnt.className}>
|
||||
<h1 className="text-4xl text-indigo-800 font-bold my-2">
|
||||
Reading Recorder</h1>
|
||||
{/* 공통 메뉴 준비 */}
|
||||
<ul className="flex bg-blue-600 mb-4 pl-2">
|
||||
<li className="block px-4 py-2 my-1 hover:bg-gray-100 rounded">
|
||||
<Link className="no-underline text-blue-300" href="/">
|
||||
Home</Link></li>
|
||||
<li className="block text-blue-300 px-4 py-2 my-1 hover:bg-gray-100 rounded">
|
||||
<Link className="no-underline text-blue-300" href="/books">
|
||||
Search</Link></li>
|
||||
<li className="block text-blue-300 px-4 py-2 my-1 hover:bg-gray-100 rounded">
|
||||
<a className="no-underline text-blue-300"
|
||||
href="https://wikibook.co.kr/support/contact/" target="_blank">Support</a></li>
|
||||
</ul>
|
||||
{/* 페이지 구성 요소를 반영하는 영역 */}
|
||||
<div className="ml-2">
|
||||
{children}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
39
modern-react/my-next/src/app/page.js
Normal file
@@ -0,0 +1,39 @@
|
||||
// import { getAllReviews } from '@/lib/getter';
|
||||
// import LinkedBookDetails from '@/components/LinkedBookDetails';
|
||||
|
||||
// // 항상 최신 정보 얻기
|
||||
// export const dynamic = 'force-dynamic';
|
||||
// export default async function Home() {
|
||||
// // 모든 리뷰 정보 얻기
|
||||
// const reviews = await getAllReviews();
|
||||
// return (
|
||||
// <>
|
||||
// {/* 획득한 리뷰 정보를 바탕으로 리스트 생성 */}
|
||||
// {reviews.map((b, i) => (
|
||||
// <LinkedBookDetails book={b} index={i + 1} key={b.id} />
|
||||
// ))}
|
||||
// </>
|
||||
// );
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// Code 11-4-7
|
||||
import { getAllReviews } from '@/lib/getter';
|
||||
import LinkedBookDetails from '@/components/LinkedBookDetails';
|
||||
|
||||
// 항상 최신 정보 얻기
|
||||
export const dynamic = 'force-dynamic';
|
||||
export default async function Home() {
|
||||
// 모든 리뷰 정보 얻기
|
||||
const reviews = await getAllReviews();
|
||||
console.log(reviews);
|
||||
return (
|
||||
<>
|
||||
{/* 획득한 리뷰 정보를 바탕으로 리스트 생성 */}
|
||||
{reviews.map((b, i) => (
|
||||
<LinkedBookDetails book={b} index={i + 1} key={b.id} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
23
modern-react/my-next/src/components/BookDetails.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import Image from 'next/image';
|
||||
|
||||
export default function BookDetails({ index, book }) {
|
||||
return (
|
||||
<div className="flex w-full mb-4">
|
||||
<div>
|
||||
{/* 책 그림자 표시 */}
|
||||
<Image src={book.image} alt="" width={140} height={180} />
|
||||
</div>
|
||||
<div>
|
||||
{/* 도서 정보 목록 표시 (index 속성이 지정되면 연속 번호도 표시) */}
|
||||
<ul className="list-none text-black ml-4">
|
||||
<li>{index && index + '.'}</li>
|
||||
<li>{book.title}({book.price}원)</li>
|
||||
<li>{book.author} 지음</li>
|
||||
<li>{book.publisher} 출판</li>
|
||||
<li>{book.published} 출시</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
72
modern-react/my-next/src/components/FormEdit.js
Normal file
@@ -0,0 +1,72 @@
|
||||
// 'use client';
|
||||
|
||||
// import { addReview, removeReview } from '@/lib/actions';
|
||||
|
||||
// export default function FormEdit({ src: { id, read, memo } }) {
|
||||
// return (
|
||||
// // 제출 시 addReview 메서드를 호출한다.
|
||||
// <form action={addReview}>
|
||||
// <input type="hidden" name="id" defaultValue={id} />
|
||||
// <div className="mb-3">
|
||||
// <label className="font-bold" htmlFor="read">읽은 날짜:</label>
|
||||
// <input type="date" id="read" name="read"
|
||||
// className="block bg-gray-100 border-2 border-gray-600 rounded focus:bg-white focus:outline-none focus:border-red-500"
|
||||
// defaultValue={read}/>
|
||||
// </div>
|
||||
// <div className="mb-3">
|
||||
// <label className="font-bold" htmlFor="memo">소감:</label>
|
||||
// <textarea id="memo" name="memo" rows="3"
|
||||
// className="block bg-gray-100 border-2 border-gray-600 w-full rounded focus:bg-white focus:outline-none focus:border-red-500"
|
||||
// defaultValue={memo}></textarea>
|
||||
// </div>
|
||||
// <button type="submit"
|
||||
// className="bg-blue-600 text-white rounded px-4 py-2 mr-2 hover:bg-blue-500">
|
||||
// 등록하기</button>
|
||||
// {/* [삭제하기] 버튼으로 removeReview 함수를 호출 */}
|
||||
// <button type="submit"
|
||||
// className="bg-red-600 text-white rounded px-4 py-2 hover:bg-red-500"
|
||||
// formAction={removeReview}>
|
||||
// 삭제하기</button>
|
||||
// </form>
|
||||
// );
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// Code 11-4-16
|
||||
'use client';
|
||||
|
||||
import { useTransition } from 'react';
|
||||
import { addReview, removeReview } from '@/lib/actions';
|
||||
|
||||
export default function FormEdit({ src: { id, read, memo } }) {
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
// 이벤트 핸들러를 통해 서버 액션을 호출한다.
|
||||
return (
|
||||
<form action={addReview}>
|
||||
<input type="hidden" name="id" defaultValue={id} />
|
||||
<div className="mb-3">
|
||||
<label className="font-bold" htmlFor="read">읽은 날짜:</label>
|
||||
<input type="date" id="read" name="read"
|
||||
className="block bg-gray-100 border-2 border-gray-600 rounded focus:bg-white focus:outline-none focus:border-red-500"
|
||||
defaultValue={read}/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label className="font-bold" htmlFor="memo">소감:</label>
|
||||
<textarea id="memo" name="memo" rows="3"
|
||||
className="block bg-gray-100 border-2 border-gray-600 w-full rounded focus:bg-white focus:outline-none focus:border-red-500"
|
||||
defaultValue={memo}></textarea>
|
||||
</div>
|
||||
<button type="submit"
|
||||
className="bg-blue-600 text-white rounded px-4 py-2 mr-2 hover:bg-blue-500">
|
||||
등록하기</button>
|
||||
<button type="button"
|
||||
className="bg-red-600 text-white rounded px-4 py-2 hover:bg-red-500"
|
||||
onClick={() => {
|
||||
startTransition(() => removeReview(id));
|
||||
}}>
|
||||
삭제하기</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
13
modern-react/my-next/src/components/LinkedBookDetails.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import Link from 'next/link';
|
||||
import BookDetails from './BookDetails';
|
||||
|
||||
export default function LinkedBookDetails({ index, book }) {
|
||||
// BookDetails 컴포넌트에 링크 부여
|
||||
return (
|
||||
<Link href={`/edit/${book.id}`}>
|
||||
<div className="hover:bg-green-50">
|
||||
<BookDetails index={index} book={book} />
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
91
modern-react/my-next/src/lib/actions.js
Normal file
@@ -0,0 +1,91 @@
|
||||
// 'use server';
|
||||
|
||||
// import { redirect } from 'next/navigation';
|
||||
|
||||
// import prisma from './prisma';
|
||||
// import { getBookById } from './getter';
|
||||
|
||||
// // 폼에서 입력한 값을 데이터베이스에 등록
|
||||
// export async function addReview(data) {
|
||||
// const book = await getBookById(data.get('id'));
|
||||
// const input = {
|
||||
// title: book.title,
|
||||
// author: book.author,
|
||||
// price: Number(book.price),
|
||||
// publisher: book.publisher,
|
||||
// published: book.published,
|
||||
// image: book.image,
|
||||
// read: new Date(data.get('read')),
|
||||
// memo: data.get('memo')
|
||||
// };
|
||||
|
||||
// // 신규 데이터라면 등록, 기존 데이터라면 업데이트
|
||||
// await prisma.reviews.upsert({
|
||||
// update: input,
|
||||
// create: Object.assign({}, input, { id: data.get('id') }),
|
||||
// where: {
|
||||
// id: data.get('id')
|
||||
// }
|
||||
// });
|
||||
// // 처리 성공 후 홈페이지로 리디렉션
|
||||
// redirect('/');
|
||||
// }
|
||||
|
||||
// // 삭제 버튼으로 지정된 리뷰 정보 삭제
|
||||
// export async function removeReview(data) {
|
||||
// await prisma.reviews.delete({
|
||||
// where: {
|
||||
// id: data.get('id')
|
||||
// }
|
||||
// });
|
||||
// // 처리 성공 후 홈페이지로 리디렉션
|
||||
// redirect('/');
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// Code 11-4-17
|
||||
'use server';
|
||||
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
import prisma from './prisma';
|
||||
import { getBookById } from './getter';
|
||||
|
||||
// 폼에서 입력한 값을 데이터베이스에 등록
|
||||
export async function addReview(data) {
|
||||
const book = await getBookById(data.get('id'));
|
||||
const input = {
|
||||
title: book.title,
|
||||
author: book.author,
|
||||
price: Number(book.price),
|
||||
publisher: book.publisher,
|
||||
published: book.published,
|
||||
image: book.image,
|
||||
read: new Date(data.get('read')),
|
||||
memo: data.get('memo')
|
||||
};
|
||||
|
||||
// 신규 데이터라면 등록, 기존 데이터라면 업데이트
|
||||
await prisma.reviews.upsert({
|
||||
update: input,
|
||||
create: Object.assign({}, input, { id: data.get('id') }),
|
||||
where: {
|
||||
id: data.get('id')
|
||||
}
|
||||
});
|
||||
// 처리 성공 후 홈페이지로 리디렉션
|
||||
redirect('/');
|
||||
}
|
||||
|
||||
// 삭제 버튼으로 지정된 리뷰 정보 삭제
|
||||
export async function removeReview(data) {
|
||||
await prisma.reviews.delete({
|
||||
// 직접 id 값을 받기 때문에 수정
|
||||
where: {
|
||||
id: data
|
||||
}
|
||||
});
|
||||
// 처리 성공 후 홈페이지로 리디렉션
|
||||
redirect('/');
|
||||
}
|
||||
54
modern-react/my-next/src/lib/getter.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import prisma from './prisma';
|
||||
|
||||
// API를 통해 얻은 도서 정보에서 필요한 정보만을 객체로 재구성
|
||||
export function createBook(book) {
|
||||
const authors = book.volumeInfo.authors;
|
||||
const price = book.saleInfo.listPrice;
|
||||
const img = book.volumeInfo.imageLinks;
|
||||
return {
|
||||
id: book.id,
|
||||
title: book.volumeInfo.title,
|
||||
author: authors ? authors.join(',') : '',
|
||||
price: price ? price.amount : 0,
|
||||
publisher: book.volumeInfo.publisher,
|
||||
published: book.volumeInfo.publishedDate,
|
||||
image: img ? img.smallThumbnail : '/vercel.svg',
|
||||
};
|
||||
}
|
||||
|
||||
// 인수 keyword를 키워드로 Google Books API에서 책 검색하기
|
||||
export async function getBooksByKeyword(keyword) {
|
||||
const res = await fetch(`https://www.googleapis.com/books/v1/volumes?q=${keyword}&langRestrict=ko&maxResults=20&printType=books`);
|
||||
const result = await res.json();
|
||||
const books = [];
|
||||
// 응답 내용을 객체 배열로 리필
|
||||
for (const b of result.items) {
|
||||
books.push(createBook(b));
|
||||
}
|
||||
return books;
|
||||
}
|
||||
|
||||
// id값을 키로 하여 도서 정보를 가져옴
|
||||
export async function getBookById(id) {
|
||||
const res = await fetch(`https://www.googleapis.com/books/v1/volumes/${id}`);
|
||||
const result = await res.json();
|
||||
return createBook(result);
|
||||
}
|
||||
|
||||
// id값을 키로 리뷰 정보 가져오기
|
||||
export async function getReviewById(id) {
|
||||
return await prisma.reviews.findUnique({
|
||||
where: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function getAllReviews() {
|
||||
// 읽은 날짜(read) 내림차순으로 검색
|
||||
return await prisma.reviews.findMany({
|
||||
orderBy: {
|
||||
read: 'desc'
|
||||
}
|
||||
});
|
||||
}
|
||||
9
modern-react/my-next/src/lib/prisma.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
// global.prisma에 Prisma 클라이언트가 존재할 경우 재사용
|
||||
const prisma = global.prisma ??
|
||||
new PrismaClient({ log: ['query'] });
|
||||
// Non-Production 환경에서는 global.prisma에 오브젝트를 저장한다.
|
||||
if (process.env.NODE_ENV !== 'production') global.prisma = prisma;
|
||||
|
||||
export default prisma;
|
||||
20
modern-react/my-next/tailwind.config.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
// Tailwind CSS를 적용하는 파일군
|
||||
content: [
|
||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
// 공통 스타일 정의
|
||||
extend: {
|
||||
backgroundImage: {
|
||||
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
|
||||
"gradient-conic":
|
||||
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
46
modern-react/my-react-ts/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Getting Started with Create React App
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.\
|
||||
You will also see any lint errors in the console.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.\
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `npm run eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||
|
||||
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
18797
modern-react/my-react-ts/package-lock.json
generated
Normal file
48
modern-react/my-react-ts/package.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "my-react-ts",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.11.4",
|
||||
"@emotion/styled": "^11.11.5",
|
||||
"@mui/icons-material": "^5.15.15",
|
||||
"@mui/material": "^5.15.15",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.18.96",
|
||||
"@types/react": "^18.2.79",
|
||||
"@types/react-dom": "^18.2.25",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-query": "^3.39.3",
|
||||
"react-scripts": "5.0.1",
|
||||
"typescript": "^4.9.5",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
BIN
modern-react/my-react-ts/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
43
modern-react/my-react-ts/public/index.html
Normal file
@@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
BIN
modern-react/my-react-ts/public/logo192.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
modern-react/my-react-ts/public/logo512.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
25
modern-react/my-react-ts/public/manifest.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
3
modern-react/my-react-ts/public/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
38
modern-react/my-react-ts/src/App.css
Normal file
@@ -0,0 +1,38 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
9
modern-react/my-react-ts/src/App.test.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
26
modern-react/my-react-ts/src/App.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import logo from './logo.svg';
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<p>
|
||||
Edit <code>src/App.tsx</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn React
|
||||
</a>
|
||||
</header>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
7
modern-react/my-react-ts/src/Book.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export type Book = {
|
||||
isbn: string,
|
||||
title: string,
|
||||
price: number,
|
||||
summary: string,
|
||||
download: boolean
|
||||
};
|
||||
56
modern-react/my-react-ts/src/HookReducerUp.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { useReducer } from 'react';
|
||||
|
||||
// Props 유형 정의
|
||||
type HookReducerUpProps = {
|
||||
init: number
|
||||
};
|
||||
|
||||
// State 타입 정의
|
||||
type StateType = {
|
||||
count: number;
|
||||
};
|
||||
|
||||
// Action의 타입 정의
|
||||
type ActionType = {
|
||||
type: 'update',
|
||||
step: number
|
||||
} | {
|
||||
type: 'reset',
|
||||
init: number
|
||||
};
|
||||
|
||||
// Reducer 유형 정의
|
||||
type CountReducerType = (state: StateType, action: ActionType) => StateType;
|
||||
|
||||
export default function HookReducerUp({ init }: HookReducerUpProps) {
|
||||
// Reducer 타입 정의
|
||||
const [state, dispatch] = useReducer<CountReducerType>(
|
||||
// Reducer의 실체
|
||||
(state, action) => {
|
||||
switch (action.type) {
|
||||
case 'update':
|
||||
return { count: state.count + action.step };
|
||||
case 'reset' :
|
||||
return { count: action.init };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
},
|
||||
// State의 초깃값
|
||||
{
|
||||
count: init
|
||||
}
|
||||
);
|
||||
const handleUp = () => dispatch({ type: 'update', step: 1 });
|
||||
const handleDown = () => dispatch({ type: 'update', step: -1 });
|
||||
const handleReset = () => dispatch({ type: 'reset', init: 0 });
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={handleUp}>카운트업</button>
|
||||
<button onClick={handleDown}>카운트다운</button>
|
||||
<button onClick={handleReset}>리셋</button>
|
||||
<p>{state.count}번 클릭되었습니다.</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
13
modern-react/my-react-ts/src/HookThemeButton.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { useContext } from 'react';
|
||||
import { Button } from '@mui/material';
|
||||
import ThemeContext, { ThemeContextType } from './ThemeContext';
|
||||
|
||||
export default function HookThemeButton() {
|
||||
// const { mode, toggleMode } = useContext<ThemeContextType>(ThemeContext);
|
||||
const { mode, toggleMode } = useContext<Partial<ThemeContextType>>(ThemeContext);
|
||||
return (
|
||||
<Button variant="contained" onClick={toggleMode}>
|
||||
Mode {mode}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
49
modern-react/my-react-ts/src/ListTemplate.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
// import React, { FC, ReactNode } from 'react';
|
||||
// import type { Book } from './Book';
|
||||
|
||||
// // Props 타입 선언
|
||||
// type ListTemplateProps = {
|
||||
// src: Array<Book>,
|
||||
// children: (b: Book) => ReactNode
|
||||
// };
|
||||
|
||||
// export default function ListTemplate({ src, children }: ListTemplateProps) {
|
||||
// return (
|
||||
// <dl>
|
||||
// {
|
||||
// src.map((elem, index) => (
|
||||
// <React.Fragment key={elem.isbn}>
|
||||
// {children(elem)}
|
||||
// </React.Fragment>
|
||||
// ))
|
||||
// }
|
||||
// </dl>
|
||||
// );
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// Code 10-2-16
|
||||
import React, { FC, ReactNode } from 'react';
|
||||
import type { Book } from './Book';
|
||||
|
||||
type ListTemplateProps = {
|
||||
src: Array<Book>,
|
||||
children: (b: Book) => ReactNode
|
||||
};
|
||||
|
||||
const ListTemplate: FC<ListTemplateProps> = ({ src, children }) => {
|
||||
return (
|
||||
<dl>
|
||||
{
|
||||
src.map((elem, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{children(elem)}
|
||||
</React.Fragment>
|
||||
))
|
||||
}
|
||||
</dl>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListTemplate;
|
||||
50
modern-react/my-react-ts/src/MyThemeProvider.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { ReactNode, useState } from 'react';
|
||||
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
||||
import { amber, grey } from '@mui/material/colors';
|
||||
import { CssBaseline, PaletteMode } from '@mui/material';
|
||||
import ThemeContext, { ThemeContextType } from './ThemeContext';
|
||||
|
||||
// Props 타입 선언
|
||||
type MyThemeProviderProps = {
|
||||
children: ReactNode
|
||||
};
|
||||
|
||||
export default function MyThemeProvider({ children }: MyThemeProviderProps) {
|
||||
const [mode, setMode] = useState<PaletteMode>('light');
|
||||
const themeConfig: ThemeContextType = {
|
||||
mode,
|
||||
toggleMode: () => {
|
||||
setMode(prev =>
|
||||
prev === 'light' ? 'dark' : 'light'
|
||||
)}
|
||||
};
|
||||
|
||||
const theme = createTheme({
|
||||
palette: {
|
||||
mode,
|
||||
...(mode === 'light'
|
||||
? {
|
||||
primary: amber,
|
||||
}
|
||||
: {
|
||||
primary: {
|
||||
main: grey[500],
|
||||
contrastText: '#fff'
|
||||
},
|
||||
background: {
|
||||
default: grey[900],
|
||||
paper: grey[900],
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
});
|
||||
return (
|
||||
<ThemeContext.Provider value={themeConfig}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
}
|
||||
39
modern-react/my-react-ts/src/QueryBasic.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
type WeatherType = {
|
||||
weather: Array<{
|
||||
id: number
|
||||
main: string
|
||||
description: string
|
||||
icon: string
|
||||
}>
|
||||
}
|
||||
|
||||
// 날씨 정보를 얻기 위한 함수
|
||||
const fetchWeather = async () => {
|
||||
const res = await fetch(`https://api.openweathermap.org/data/2.5/weather?q=Seoul&lang=kr&appid=ef23a61e94f59784b8451d12c0d07da8`);
|
||||
if (res.ok) {
|
||||
return await res.json() as WeatherType;
|
||||
}
|
||||
throw new Error(res.statusText);
|
||||
};
|
||||
|
||||
export default function QuerBasic() {
|
||||
// fetchWeather 함수로 데이터 가져오기
|
||||
const { data, isLoading, isError, error } = useQuery<WeatherType, Error>('weather', fetchWeather);
|
||||
if (isLoading) {
|
||||
return <p>Loading...</p>
|
||||
}
|
||||
if (isError) {
|
||||
return <p>Error: {error.message}</p>
|
||||
}
|
||||
|
||||
return (
|
||||
<figure>
|
||||
<img
|
||||
src={`https://openweathermap.org/img/wn/${data?.weather?.[0]?.icon}.png`}
|
||||
alt={data?.weather?.[0]?.main} />
|
||||
<figcaption>{data?.weather?.[0]?.description}</figcaption>
|
||||
</figure>
|
||||
);
|
||||
}
|
||||
51
modern-react/my-react-ts/src/QueryPre.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
// fetch를 통해 취득한 날씨 정보 유형 정의
|
||||
type WeatherType = {
|
||||
weather: Array<{
|
||||
id: number
|
||||
main: string
|
||||
description: string
|
||||
icon: string
|
||||
}>
|
||||
}
|
||||
|
||||
// 날씨 정보를 얻기 위한 함수
|
||||
const fetchWeather = async () => {
|
||||
const res = await fetch(`https://api.openweathermap.org/data/2.5/weather?q=Seoul&lang=kr&appid=ef23a61e94f59784b8451d12c0d07da8`);
|
||||
if (res.ok) {
|
||||
return await res.json() as WeatherType;
|
||||
}
|
||||
throw new Error(res.statusText);
|
||||
};
|
||||
|
||||
export default function QueryPre() {
|
||||
// State 선언
|
||||
const [data, setData] = useState<WeatherType>();
|
||||
const [isLoading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string>('');
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
fetchWeather()
|
||||
.then(result => setData(result))
|
||||
.catch(err => setError(err.message))
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
if (isLoading) {
|
||||
return <p>Loading...</p>
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <p>Error: {error}</p>
|
||||
}
|
||||
|
||||
return (
|
||||
<figure>
|
||||
<img
|
||||
src={`https://openweathermap.org/img/wn/${data?.weather?.[0]?.icon}.png`}
|
||||
alt={data?.weather?.[0]?.main} />
|
||||
<figcaption>{data?.weather?.[0]?.description}</figcaption>
|
||||
</figure>
|
||||
);
|
||||
}
|
||||
19
modern-react/my-react-ts/src/StateBasic.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
// Props 타입 선언
|
||||
type StateBasicProps = {
|
||||
init: number
|
||||
};
|
||||
|
||||
export default function StateBasic({ init }: StateBasicProps) {
|
||||
const [count, setCount] = useState<number>(init);
|
||||
const handleClick = () => setCount(count + 1);
|
||||
return (
|
||||
<>
|
||||
<button onClick={handleClick}>카운트</button>
|
||||
<p>{count}번 클릭했습니다.</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
28
modern-react/my-react-ts/src/StateFormUC.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { useRef } from 'react';
|
||||
|
||||
export default function StateFormUC() {
|
||||
const name = useRef<HTMLInputElement>(null);
|
||||
const age = useRef<HTMLInputElement>(null);
|
||||
const show = () => {
|
||||
console.log(`안녕하세요, ${name.current?.value}(${age.current?.value}세) 님!`);
|
||||
};
|
||||
return (
|
||||
<form>
|
||||
<div>
|
||||
<label htmlFor="name">이름: </label>
|
||||
<input id="name" name="name" type="text"
|
||||
ref={name} defaultValue="홍길동" />
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="age">나이: </label>
|
||||
<input id="age" name="age" type="number"
|
||||
ref={age} defaultValue="18" />
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" onClick={show}>
|
||||
보내기</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
21
modern-react/my-react-ts/src/StyledPanel.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
// Props 타입 선언
|
||||
type StyledPanelProps = {
|
||||
children: ReactNode
|
||||
};
|
||||
|
||||
export default function StyledPanel({ children }: StyledPanelProps) {
|
||||
return (
|
||||
<div style={{
|
||||
margin: 50,
|
||||
padding: 20,
|
||||
border: '1px solid #000',
|
||||
width: 'fit-content',
|
||||
boxShadow: '10px 5px 5px #999',
|
||||
backgroundColor: '#fff'
|
||||
}}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
14
modern-react/my-react-ts/src/ThemeContext.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { PaletteMode } from '@mui/material';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type ThemeContextType = {
|
||||
mode: PaletteMode,
|
||||
toggleMode: () => void
|
||||
};
|
||||
|
||||
// export default createContext<ThemeContextType>({
|
||||
// mode: 'light',
|
||||
// toggleMode: () => {}
|
||||
// });
|
||||
|
||||
export default createContext<Partial<ThemeContextType>>({});
|
||||
43
modern-react/my-react-ts/src/books.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
const books = [
|
||||
{
|
||||
isbn: '9791158395124',
|
||||
title: '게임 개발을 위한 미드저니, 스테이블 디퓨전 완벽 활용법',
|
||||
slug: 'genai-game',
|
||||
price: 28000,
|
||||
summary: '생성형 AI를 활용한 게임 캐릭터, 배경, 아이템 제작부터 유니티 실전 프로젝트까지',
|
||||
download: true,
|
||||
},
|
||||
{
|
||||
isbn: '9791158395117',
|
||||
title: '디자인을 위한 미드저니 완벽 활용법',
|
||||
slug: 'midjourney-design',
|
||||
price: 24000,
|
||||
summary: '광고부터 캐릭터, 로고, 일러스트레이션, 표지, 포스터, 타이포까지 독창적인 디자인 만들기',
|
||||
download: false,
|
||||
},
|
||||
{
|
||||
isbn: '9791158395032',
|
||||
title: '만들면서 배우는 블렌더 3D 입문',
|
||||
slug: 'blender-basic',
|
||||
price: 28000,
|
||||
summary: '블렌더 기초, 모델링, 머티리얼, 애니메이션, 렌더링까지',
|
||||
download: true,
|
||||
},
|
||||
{
|
||||
isbn: '9791158395018',
|
||||
title: '모던 그로스 마케팅',
|
||||
slug: 'mgm',
|
||||
price: 24000,
|
||||
summary: '비용은 최소화하고 매출은 극대화하는 생존 마케팅 전략',
|
||||
download: false,
|
||||
},
|
||||
{
|
||||
isbn: '9791158395025',
|
||||
title: '도메인 스토리텔링',
|
||||
slug: 'domain-storytelling',
|
||||
price: 28000,
|
||||
summary: '도메인 주도 소프트웨어 구축을 위한 스토리텔링과 스토리 시각화 기법',
|
||||
download: true,
|
||||
},
|
||||
];
|
||||
export default books;
|
||||
13
modern-react/my-react-ts/src/index.css
Normal file
@@ -0,0 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
111
modern-react/my-react-ts/src/index.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import StateBasic from './StateBasic';
|
||||
import StyledPanel from './StyledPanel';
|
||||
import ListTemplate from './ListTemplate';
|
||||
import books from './books';
|
||||
import type { Book } from './Book';
|
||||
import MyThemeProvider from './MyThemeProvider';
|
||||
import HookThemeButton from './HookThemeButton';
|
||||
import HookReducerUp from './HookReducerUp';
|
||||
import StateFormUC from './StateFormUC';
|
||||
import QueryPre from './QueryPre';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import QueryBasic from './QueryBasic';
|
||||
/* eslint-enable @typescript-eslint/no-unused-vars */
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
);
|
||||
|
||||
// root.render(
|
||||
// <React.StrictMode>
|
||||
// <App />
|
||||
// </React.StrictMode>
|
||||
// );
|
||||
|
||||
|
||||
|
||||
// Code 10-2-3
|
||||
// root.render(
|
||||
// <StateBasic init={0} />
|
||||
// );
|
||||
|
||||
|
||||
|
||||
// Code 10-2-4
|
||||
// root.render(
|
||||
// <StyledPanel>
|
||||
// <p>회원 모집 중!</p>
|
||||
// <p>위키북스 프로젝트에 오신 것을 환영합니다!!</p>
|
||||
// </StyledPanel>
|
||||
// );
|
||||
|
||||
|
||||
|
||||
// Code 10-2-7
|
||||
// root.render(
|
||||
// <ListTemplate src={books}>
|
||||
// {(elem: Book) => (
|
||||
// <>
|
||||
// <dt>
|
||||
// <a href={`https://wikibook.co.kr/images/cover/s/${elem.isbn}.jpg`}>
|
||||
// {elem.title}({elem.price}원)
|
||||
// </a>
|
||||
// </dt>
|
||||
// <dd>{elem.summary}</dd>
|
||||
// </>
|
||||
// )}
|
||||
// </ListTemplate>
|
||||
// );
|
||||
|
||||
|
||||
|
||||
// Code 10-2-11
|
||||
// root.render(
|
||||
// <MyThemeProvider>
|
||||
// <HookThemeButton />
|
||||
// </MyThemeProvider>
|
||||
// );
|
||||
|
||||
|
||||
|
||||
// Code 10-2-12
|
||||
// root.render(
|
||||
// <HookReducerUp init={0} />
|
||||
// );
|
||||
|
||||
|
||||
|
||||
// Code 10-2-13
|
||||
// root.render(
|
||||
// <StateFormUC />
|
||||
// );
|
||||
|
||||
|
||||
|
||||
// Code 10-2-14
|
||||
root.render(
|
||||
<QueryPre />
|
||||
);
|
||||
|
||||
|
||||
|
||||
// Code 10-2-15
|
||||
const cli = new QueryClient();
|
||||
root.render(
|
||||
<QueryClientProvider client={cli}>
|
||||
<QueryBasic />
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
||||
1
modern-react/my-react-ts/src/logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
1
modern-react/my-react-ts/src/react-app-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
||||
15
modern-react/my-react-ts/src/reportWebVitals.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ReportHandler } from 'web-vitals';
|
||||
|
||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
||||
5
modern-react/my-react-ts/src/setupTests.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
||||