├── .gitignore
├── README.md
├── package.json
├── public
├── background.jpg
├── favicon.ico
├── index.html
└── manifest.json
├── src
├── App.css
├── App.js
├── App.test.js
├── components
│ ├── Board.css
│ ├── Board.js
│ ├── Detail.css
│ ├── Detail.js
│ ├── Header.css
│ ├── Header.js
│ ├── NoticeDetail.css
│ ├── NoticeDetail.js
│ ├── Notification.css
│ ├── Notification.js
│ ├── Qa.css
│ └── Qa.js
├── i18n.js
├── icons
│ └── arrow_down.png
├── images
│ └── image.jpeg
├── index.css
├── index.js
├── logo.svg
├── pages
│ ├── About.js
│ ├── Faq.js
│ ├── Home.js
│ └── Notice.js
└── serviceWorker.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### React Multi Page Web Template 1
2 |
3 | ### How to use
4 | ```
5 | yarn install
6 | yarn start
7 | ```
8 | ### Tutorial
9 | * React Router: 멀티 페이지를 React에서 제공하기 위해 사용
10 | * How to make React App
11 | ```
12 | # node.js, yarn, Editor 설치 이후 특정 폴더 접근
13 | yarn global add create-react-app
14 | create-react-app react-multi-page-web-template-1
15 | # 만들어진 React App에 접근
16 | cd react-multi-page-web-template-1
17 | ```
18 | * Install React Router Dom
19 | ```
20 | yarn add react-router-dom
21 | # https://github.com/ndb796/React-Multi-Page-Web-Template-1/commit/94ee6fa902f12271a12c45a8a186296eab454412
22 | # /, /about, /faq 경로로 테스트
23 | ```
24 | * Add Header & Header Design
25 | ```
26 | # https://github.com/ndb796/React-Multi-Page-Web-Template-1/commit/00bb34a56d116cc28ceec955ed551b61e5a9c834
27 | # https://github.com/ndb796/React-Multi-Page-Web-Template-1/commit/bcfbe19ba46b7a06e0f51e7ac1ca797406c0906f
28 | ```
29 | * Add Q/A & Q/A Design
30 | ```
31 | # https://github.com/ndb796/React-Multi-Page-Web-Template-1/commit/d7e9c89a54178e3122276fb3e11eed8899d6b54f
32 | # https://github.com/ndb796/React-Multi-Page-Web-Template-1/commit/192a69d04926a18297ce9f49c38824dee4bb305c
33 | ```
34 | * Add Board & Detail Routing
35 | ```
36 | # https://github.com/ndb796/React-Multi-Page-Web-Template-1/commit/f794ffff1d5c03a171640ab27ce094ee150a0790
37 | # https://github.com/ndb796/React-Multi-Page-Web-Template-1/commit/5bb62f2cdd0086234d493231ee668824e328de61
38 | ````
39 | * Get FAQ From Firebase
40 |
41 | 
42 | ```
43 | # https://github.com/ndb796/React-Multi-Page-Web-Template-1/commit/b86d59e512fae01361a2dfa8f0e2056c7d18ebe8
44 | ```
45 |
46 | * Get About From Firebase
47 |
48 | 
49 | ```
50 | # https://github.com/ndb796/React-Multi-Page-Web-Template-1/commit/96e0e447c37149fdf94609556b8d62885da4f375
51 | ```
52 |
53 | * Add Notice
54 |
55 | 
56 | ```
57 | # https://github.com/ndb796/React-Multi-Page-Web-Template-1/commit/fd612e717ac385a17b82ef9d07da58a582fc3691
58 | ```
59 |
60 | * Add Multi Language Support
61 | ```
62 | yarn add react-i18next
63 | yarn add i18next
64 | yarn add i18next-browser-languagedetector
65 | ```
66 |
67 | * Deploy on AWS Cloud Front
68 | ```
69 | yarn build
70 | # check ./build folder
71 | # create AWS S3 Bucket
72 | # name: www-deploy-test (Only Next)
73 | # [Upload] all the build files
74 | # [Properties] - [Static website hosting] - [Use this bucket to host a website] - index: index.html, error: index.html
75 | # Open endpoint URL - 403 Forbidden Error
76 | # 버킷 정책 변환하기
77 | # [Block public access] - Off [Block public access to buckets and objects granted through new public bucket policies]
78 | # [Block public access] - Off [Block public and cross-account access to buckets and objects through any public bucket policies]
79 | # [Bucket Policy]
80 | {
81 | "Version": "2012-10-17",
82 | "Statement": [
83 | {
84 | "Sid": "AddPerm",
85 | "Effect": "Allow",
86 | "Principal": "*",
87 | "Action": [
88 | "s3:GetObject"
89 | ],
90 | "Resource": [
91 | "arn:aws:s3:::www-deploy-test/*"
92 | ]
93 | }
94 | ]
95 | }
96 | # Open endpoint URL - 200
97 | # [Cloud Front] - [Create Distribution] - Web [Get Started] - Origin Domain Name 선택 - [Redirect HTTP to HTTPS] - Default Root Object: index.html - [Create Distribution]
98 | # 10분 뒤 CDN 설정 완료
99 | # [Network] 탭에서 x-cache: Hit from cloudfront 확인
100 | # 1) Route53를 이용하는 경우 => 원하는 도메인 추가: ex) Route53에서 Name: test.xxx.com - Type: A Type - Value: 서버 IP 설정
101 | # 2) Cloud Flare와 연동된 경우 => Cloud Flare에서 test 서브 도메인 - 서버 IP 설정 (단, 어차피 이용이 어려움.)
102 | # 결과적으로 test.xxx.com에 들어갈 수 있는지 먼저 확인
103 | # Cloud Front 설정 - [General] - [Edit] - Alternate Domain Names: test.xxx.com - [Request or Import a Certificate with ACM] - Domain Name: test.xxx.com - DNS validation - [Review] - [Confirm and request]
104 | # 1) Route53를 이용하는 경우 => [Create record in Route 53] - 기존에 존재하는 도메인에 CNAME 값 추가 - [Continue] - 10분 이내로 Pending 상태에서 완료 상태로 변경 - 이후에 Route S3의 A Type 값을 Cloud Front Alias로 설정
105 | # 2) Cloud Flare와 연동된 경우 => 이용이 어려움. 대신 Cloud Flare의 기본적은 CDN 기능 이용.
106 | ```
107 |
108 | * Route 문제 해결
109 | ```
110 | # Cloud Front에서 /notice 페이지에서 새로고침을 하게 되면, 403 Forbidden 오류 발생. 왜냐하면 React App은 기본적으로 / 페이지에 접속을 하여, React Router의 기능이 동작하기 때문. 따라서 /notice 페이지를 /index.html 페이지로 보내면 정상적으로 동작.
111 | # Cloud Front를 이용할 때 React App의 Route 문제 발생 - [CloudFront Distributions] - [Edit] - [Error Pages] - [Create Custom Error Response] - HTTP Error Code: 403 Forbidden - TTL: 60 - Customize Error Response: Yes - Response Page Path: /index.html - HTTP Response Code: 200 OK
112 | # Cloud Front를 이용하는 것이 아니라, 다른 서비스를 이용할 때에도 비슷한 방식으로 문제를 해결할 수 있음. (Express, Apache, Nginx 등에 따라서 /*를 /index.html로 보내는 방법이 조금씩 다름.)
113 | # Apache2의 경우 다음과 같이 처리 가능.
114 |
115 | RewriteEngine On
116 | RewriteBase /
117 | RewriteRule ^index\.html$ - [L]
118 | RewriteCond %{REQUEST_FILENAME} !-f
119 | RewriteCond %{REQUEST_FILENAME} !-d
120 | RewriteRule . /index.html [L]
121 |
122 | ```
123 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-multi-page-web-template-1",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@material-ui/core": "^4.0.1",
7 | "@material-ui/icons": "^4.0.1",
8 | "i18next": "^17.0.2",
9 | "i18next-browser-languagedetector": "^3.0.1",
10 | "react": "^16.8.6",
11 | "react-dom": "^16.8.6",
12 | "react-i18next": "^10.11.0",
13 | "react-multi-lang": "^1.0.3",
14 | "react-multi-language": "^0.4.2",
15 | "react-router-dom": "^5.0.0",
16 | "react-scripts": "3.0.1"
17 | },
18 | "scripts": {
19 | "start": "react-scripts start",
20 | "build": "react-scripts build",
21 | "test": "react-scripts test",
22 | "eject": "react-scripts eject"
23 | },
24 | "eslintConfig": {
25 | "extends": "react-app"
26 | },
27 | "browserslist": {
28 | "production": [
29 | ">0.2%",
30 | "not dead",
31 | "not op_mini all"
32 | ],
33 | "development": [
34 | "last 1 chrome version",
35 | "last 1 firefox version",
36 | "last 1 safari version"
37 | ]
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/public/background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ndb796/React-Multi-Page-Web-Template-1/6ef3c2bc0c6a015017e83fae8b6d8989f5ffa0d0/public/background.jpg
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ndb796/React-Multi-Page-Web-Template-1/6ef3c2bc0c6a015017e83fae8b6d8989f5ffa0d0/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
22 | React App
23 |
24 |
25 | You need to enable JavaScript to run this app.
26 |
27 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 40vmin;
8 | pointer-events: none;
9 | }
10 |
11 | .App-header {
12 | background-color: #282c34;
13 | min-height: 100vh;
14 | display: flex;
15 | flex-direction: column;
16 | align-items: center;
17 | justify-content: center;
18 | font-size: calc(10px + 2vmin);
19 | color: white;
20 | }
21 |
22 | .App-link {
23 | color: #61dafb;
24 | }
25 |
26 | @keyframes App-logo-spin {
27 | from {
28 | transform: rotate(0deg);
29 | }
30 | to {
31 | transform: rotate(360deg);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { BrowserRouter as Router, Route } from 'react-router-dom';
3 |
4 | import Home from './pages/Home';
5 | import About from './pages/About';
6 | import Faq from './pages/Faq';
7 | import Notice from './pages/Notice';
8 |
9 | import Header from './components/Header';
10 | import { useTranslation, Trans } from "react-i18next";
11 |
12 | function App() {
13 | const { t, i18n } = useTranslation();
14 | const changeLanguage = lng => {
15 | i18n.changeLanguage(lng);
16 | };
17 | return (
18 |
19 |
25 | );
26 | }
27 |
28 | export default App;
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render( , div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/src/components/Board.css:
--------------------------------------------------------------------------------
1 | .content {
2 | margin: 10px;
3 | margin-top: 25px;
4 | text-align: center;
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/Board.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './Board.css';
3 |
4 | import Card from '@material-ui/core/Card';
5 | import CardActionArea from '@material-ui/core/CardActionArea';
6 | import CardActions from '@material-ui/core/CardActions';
7 | import CardContent from '@material-ui/core/CardContent';
8 | import Button from '@material-ui/core/Button';
9 | import Typography from '@material-ui/core/Typography';
10 |
11 | class Board extends React.Component {
12 | constructor(props) {
13 | super(props);
14 | this.state = {
15 | hover: false
16 | };
17 | }
18 | hover = () => {
19 | this.setState({ hover: true })
20 | }
21 | leave = () => {
22 | this.setState({ hover: false })
23 | }
24 | render() {
25 | return (
26 |
27 |
28 |
29 |
30 | {this.props.title}
31 |
32 |
33 |
34 |
35 |
36 | 자세히 보기
37 |
38 |
39 |
40 | );
41 | }
42 | }
43 |
44 | export default Board;
--------------------------------------------------------------------------------
/src/components/Detail.css:
--------------------------------------------------------------------------------
1 | .container {
2 | max-width: 720px;
3 | margin: 0 auto;
4 | }
--------------------------------------------------------------------------------
/src/components/Detail.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Paper from '@material-ui/core/Paper';
3 | import Typography from '@material-ui/core/Typography';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | const databaseURL = "https://react-multi-page-app.firebaseio.com";
7 |
8 | const useStyles = makeStyles(theme => ({
9 | root: {
10 | margin: theme.spacing(3, 10),
11 | padding: theme.spacing(3, 2),
12 | },
13 | }));
14 |
15 | function PaperSheet(props) {
16 | const classes = useStyles();
17 | return (
18 |
19 |
20 |
21 | {props.title}
22 |
23 |
24 | {props.content}
25 |
26 |
27 |
28 | );
29 | }
30 |
31 | class Detail extends React.Component {
32 | constructor(props) {
33 | super(props);
34 | this.state = {
35 | detail: {}
36 | }
37 | }
38 | _get() {
39 | fetch(`${databaseURL}/about/${this.props.match.params.id}.json`).then(res => {
40 | if(res.status !== 200) {
41 | throw new Error(res.statusText);
42 | }
43 | return res.json();
44 | }).then(detail => this.setState({detail: detail}))
45 | }
46 | componentDidMount() {
47 | this._get();
48 | }
49 | componentDidUpdate(prevProps) {
50 | if (this.props.match.params.id !== prevProps.match.params.id) {
51 | this._get();
52 | }
53 | }
54 | render() {
55 | return (
56 |
57 | );
58 | }
59 | }
60 |
61 | export default Detail;
--------------------------------------------------------------------------------
/src/components/Header.css:
--------------------------------------------------------------------------------
1 | /* 색상 참고: https://material.io/design/color/ */
2 | .link {
3 | display: table-cell;
4 | padding: 10px;
5 | color: white;
6 | text-align: center;
7 | text-decoration: none;
8 | font-size: 18px;
9 | margin-left: 10px;
10 | margin-right: 10px;
11 | }
12 |
13 | .link:hover {
14 | background: rgba(0, 0, 0, 0.1);
15 | color: white;
16 | }
17 |
18 | .link.active {
19 | background: rgba(0, 0, 0, 0.3);
20 | }
--------------------------------------------------------------------------------
/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NavLink } from 'react-router-dom';
3 | import AppBar from '@material-ui/core/AppBar';
4 | import Toolbar from '@material-ui/core/Toolbar';
5 | import TypoGraphy from '@material-ui/core/Typography';
6 | import { MultiLang, Determinator } from "react-multi-language";
7 |
8 | import './Header.css';
9 |
10 | function Header(props) {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 | {props.t('Home')}
18 |
19 |
20 |
21 |
22 | {props.t('About')}
23 |
24 |
25 |
26 |
27 | {props.t('Faq')}
28 |
29 |
30 |
31 |
32 | {props.t('Notice')}
33 |
34 |
35 | props.i18n.changeLanguage(props.i18n.language === 'ENG' ? 'KOR' : 'ENG')}>
36 | {props.i18n.language}
37 |
38 |
39 |
40 |
41 | );
42 | }
43 |
44 | export default Header;
--------------------------------------------------------------------------------
/src/components/NoticeDetail.css:
--------------------------------------------------------------------------------
1 | .detail {
2 | padding: 20px;
3 | }
--------------------------------------------------------------------------------
/src/components/NoticeDetail.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './NoticeDetail.css';
3 | import { NavLink } from 'react-router-dom';
4 |
5 | const databaseURL = "https://react-multi-page-app.firebaseio.com";
6 |
7 | class NoticeDetail extends React.Component {
8 | constructor() {
9 | super();
10 | this.state = {
11 | notice: {}
12 | };
13 | }
14 | _get() {
15 | fetch(`${databaseURL}/notice/${this.props.match.params.id}.json`).then(res => {
16 | if(res.status !== 200) {
17 | throw new Error(res.statusText);
18 | }
19 | return res.json();
20 | }).then(data => this.setState({notice: data}));
21 | }
22 | componentDidMount() {
23 | this._get();
24 | }
25 | render() {
26 | return (
27 |
28 | 제목: {this.state.notice['title']}
29 | 내용: {this.state.notice['content']}
30 | 날짜: {this.state.notice['date']}
31 |
32 | 목록 보기
33 |
34 |
35 | );
36 | }
37 | };
38 |
39 | export default NoticeDetail;
--------------------------------------------------------------------------------
/src/components/Notification.css:
--------------------------------------------------------------------------------
1 | .notification {
2 | max-width: 720px;
3 | margin: 0 auto;
4 | }
5 | .notification-left {
6 | width: 15%;
7 | float: left;
8 | text-align: center;
9 | }
10 | .notification-center {
11 | width: 60%;
12 | float: left;
13 | }
14 | .notification-right {
15 | width: 20%;
16 | float: left;
17 | }
18 | @media screen and (max-width: 660px) {
19 | .notification-left {
20 | display: none;
21 | }
22 | .notification-center {
23 | width: 100%;
24 | }
25 | .notification-right {
26 | display: none;
27 | }
28 | }
--------------------------------------------------------------------------------
/src/components/Notification.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import './Notification.css';
4 | import { NavLink } from 'react-router-dom';
5 | import TableCell from '@material-ui/core/TableCell';
6 | import TableRow from '@material-ui/core/TableRow';
7 |
8 | class Notification extends React.Component {
9 | constructor(props) {
10 | super(props);
11 | this.state = {
12 |
13 | };
14 | }
15 | render() {
16 | return (
17 |
18 | {this.props.id}
19 |
20 |
21 | {this.props.title}
22 |
23 |
24 | {this.props.date}
25 |
26 | );
27 | }
28 | }
29 |
30 | export default Notification;
--------------------------------------------------------------------------------
/src/components/Qa.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ndb796/React-Multi-Page-Web-Template-1/6ef3c2bc0c6a015017e83fae8b6d8989f5ffa0d0/src/components/Qa.css
--------------------------------------------------------------------------------
/src/components/Qa.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import ExpansionPanel from '@material-ui/core/ExpansionPanel';
4 | import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary';
5 | import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails';
6 | import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
7 | import Typography from '@material-ui/core/Typography';
8 | import { makeStyles } from '@material-ui/core/styles';
9 |
10 | const useStyles = makeStyles(theme => ({
11 | root: {
12 | paddingTop: 40,
13 | paddingLeft: 20,
14 | paddingRight: 20
15 | },
16 | panel: {
17 | padding: theme.spacing(0.2, 0),
18 | }
19 | }));
20 |
21 | function Qa(props) {
22 | const classes = useStyles();
23 | return (
24 |
25 |
26 | }>
27 | 질문: {props.question}
28 |
29 |
30 |
31 | 답변: {props.answer}
32 |
33 |
34 |
35 |
36 | );
37 | }
38 |
39 | export default Qa;
--------------------------------------------------------------------------------
/src/i18n.js:
--------------------------------------------------------------------------------
1 | import i18n from "i18next";
2 | import LanguageDetector from "i18next-browser-languagedetector";
3 | import { initReactI18next } from "react-i18next";
4 |
5 | i18n
6 | .use(LanguageDetector)
7 | .use(initReactI18next)
8 | .init({
9 | // we init with resources
10 | resources: {
11 | KOR: {
12 | translations: {
13 | "Home": "메인",
14 | "About": "소개",
15 | "Faq": "질의응답",
16 | "Notice": "공지사항"
17 | }
18 | },
19 | ENG: {
20 | translations: {
21 | "Home": "Home",
22 | "About": "About",
23 | "Faq": "Faq",
24 | "Notice": "Notice"
25 | }
26 | }
27 | },
28 | fallbackLng: "KOR",
29 | debug: true,
30 |
31 | // have a common namespace used around the full app
32 | ns: ["translations"],
33 | defaultNS: "translations",
34 |
35 | keySeparator: false, // we use content as keys
36 |
37 | interpolation: {
38 | escapeValue: false
39 | }
40 | });
41 |
42 | export default i18n;
43 |
--------------------------------------------------------------------------------
/src/icons/arrow_down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ndb796/React-Multi-Page-Web-Template-1/6ef3c2bc0c6a015017e83fae8b6d8989f5ffa0d0/src/icons/arrow_down.png
--------------------------------------------------------------------------------
/src/images/image.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ndb796/React-Multi-Page-Web-Template-1/6ef3c2bc0c6a015017e83fae8b6d8989f5ffa0d0/src/images/image.jpeg
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import url(https://fonts.googleapis.com/earlyaccess/notosanskr.css);
2 |
3 | div {
4 | font-family: 'Noto Sans KR' !important;
5 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 | import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
7 | import "./i18n"
8 |
9 | const theme = createMuiTheme({
10 | typography: {
11 | useNextVariants: true,
12 | fontFamily: '"Noto Sans KR"'
13 | }
14 | });
15 |
16 | ReactDOM.render( , document.getElementById('root'));
17 |
18 | // If you want your app to work offline and load faster, you can change
19 | // unregister() to register() below. Note this comes with some pitfalls.
20 | // Learn more about service workers: https://bit.ly/CRA-PWA
21 | serviceWorker.unregister();
22 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/pages/About.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Board from '../components/Board';
3 | import Detail from '../components/Detail';
4 | import Grid from '@material-ui/core/Grid';
5 | import { Route, Link } from 'react-router-dom';
6 |
7 | const databaseURL = "https://react-multi-page-app.firebaseio.com";
8 |
9 | class About extends React.Component {
10 | constructor() {
11 | super();
12 | this.state = {
13 | list: {}
14 | }
15 | }
16 | _get() {
17 | fetch(`${databaseURL}/about.json`).then(res => {
18 | if(res.status !== 200) {
19 | throw new Error(res.statusText);
20 | }
21 | return res.json();
22 | }).then(list => this.setState({list: list}));
23 | }
24 | componentDidMount() {
25 | this._get();
26 | }
27 | render() {
28 | return (
29 |
30 |
31 |
32 | {Object.keys(this.state.list).map(id => {
33 | const board = this.state.list[id];
34 | return (
35 |
36 |
37 |
38 |
39 |
40 | );
41 | })}
42 |
43 |
44 |
45 |
49 |
50 |
51 | );
52 | }
53 | }
54 |
55 | export default About;
56 |
--------------------------------------------------------------------------------
/src/pages/Faq.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Qa from '../components/Qa';
3 |
4 | const databaseURL = "https://react-multi-page-app.firebaseio.com";
5 |
6 | class Faq extends React.Component {
7 | constructor() {
8 | super();
9 | this.state = {
10 | list: {}
11 | };
12 | }
13 | _get() {
14 | fetch(`${databaseURL}/faq.json`).then(res => {
15 | if(res.status !== 200) {
16 | throw new Error(res.statusText);
17 | }
18 | return res.json();
19 | }).then(list => this.setState({list: list}));
20 | }
21 | componentDidMount() {
22 | this._get();
23 | }
24 | render() {
25 | return (
26 |
27 | {Object.keys(this.state.list).map(id => {
28 | const qa = this.state.list[id];
29 | return
30 | })}
31 |
32 | );
33 | }
34 | }
35 |
36 | export default Faq;
--------------------------------------------------------------------------------
/src/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Card from '@material-ui/core/Card';
3 | import CardActionArea from '@material-ui/core/CardActionArea';
4 | import CardContent from '@material-ui/core/CardContent';
5 | import CardMedia from '@material-ui/core/CardMedia';
6 |
7 | function Home() {
8 | return (
9 |
10 |
11 |
12 |
13 |
19 |
20 | 메인 페이지입니다.
21 |
22 |
23 |
24 |
25 | );
26 | }
27 |
28 | export default Home;
--------------------------------------------------------------------------------
/src/pages/Notice.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Notification from '../components/Notification';
3 | import NoticeDetail from '../components/NoticeDetail';
4 | import { Route } from 'react-router-dom';
5 | import { withRouter } from 'react-router-dom';
6 | import Table from '@material-ui/core/Table';
7 | import TableBody from '@material-ui/core/TableBody';
8 | import TableCell from '@material-ui/core/TableCell';
9 | import TableHead from '@material-ui/core/TableHead';
10 | import TableRow from '@material-ui/core/TableRow';
11 | import Paper from '@material-ui/core/Paper';
12 | import Typography from '@material-ui/core/Typography';
13 |
14 |
15 | const databaseURL = "https://react-multi-page-app.firebaseio.com";
16 |
17 | class Notice extends React.Component {
18 | constructor() {
19 | super();
20 | this.state = {
21 | list: {}
22 | };
23 | }
24 | _get() {
25 | fetch(`${databaseURL}/notice.json`).then(res => {
26 | if(res.status !== 200) {
27 | throw new Error(res.statusText);
28 | }
29 | return res.json();
30 | }).then(list => this.setState({list: list}));
31 | }
32 | componentDidMount() {
33 | this._get();
34 | }
35 | render() {
36 | return (
37 |
38 |
39 |
40 |
41 |
42 | {this.props.location.pathname === '/notice' &&
43 |
44 |
45 |
46 | 번호
47 |
48 |
49 |
50 |
51 | 제목
52 |
53 |
54 |
55 |
56 | 날짜
57 |
58 |
59 |
60 | }
61 |
62 |
63 | {this.props.location.pathname === '/notice' && Object.keys(this.state.list).map(id => {
64 | const notification = this.state.list[id];
65 | return
66 | })}
67 |
68 |
69 |
73 |
74 |
75 | );
76 | }
77 | }
78 |
79 | export default withRouter(Notice);
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type');
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------