<repo_name>You-Ting-Li/UT0903.github.io<file_sep>/components/todolist/functions/Data.js
const packet = {
  failed: [],
  info: [
    {
      course: "計算機網路實驗 Computer Network Laboratory",
      hw: "Lab 1 Trace route concept and implementation",
      due: "2021:03:25:23:59:59",
      src_from: "cool",
    },
    {
      course: "計算機網路實驗 Computer Network Laboratory",
      hw: "Lab2 Security Wireless LANs",
      due: "2021:04:22:23:59:59",
      src_from: "cool",
    },
    {
      course: "計算機網路實驗 Computer Network Laboratory",
      hw: "Lab 3 SIMULATING SDN WITH MININET",
      due: "2021:06:03:23:59:59",
      src_from: "cool",
    },
    {
      course: "計算機網路實驗 Computer Network Laboratory",
      hw: "The Presentation of demonstration",
      src_from: "cool",
    },
    {
      course: "計算機網路實驗 ComputerNetwork Laboratory",
      hw: "The Presentation of proposal",
      due: "2021:05:20:11:59:59",
      src_from: "cool",
    },
    {
      course: "普通心理學 General Psychology",
      hw: "心理學實驗參與加分證明上傳",
      due: "2021:06:21:23:59:59",
      src_from: "cool",
    },
    {
      course: "普通心理學 General Psychology",
      hw: "期中報告",
      due: "2021:04:25:23:59:59",
      src_from: "cool",
    },
    {
      course: "普通心理學 General Psychology",
      hw: "期末報告",
      due: "2021:06:21:23:59:59",
      src_from: "cool",
    },
    {
      course: "普通心理學 General Psychology",
      hw: "第三週討論：注意力何時介入",
      src_from: "cool",
    },
    {
      course: "普通心理學 General Psychology",
      hw: "第二週討論：心靈的本質",
      due: "2021:03:07:23:59:00",
      src_from: "cool",
    },
    {
      course: "普通心理學 General Psychology",
      hw: "第八週討論：化學的腦切除術",
      due: "2021:04:18:23:59:59",
      src_from: "cool",
    },
    {
      course: "普通心理學 General Psychology",
      hw: "第六週討論：陳敬鎧案",
      due: "2021:04:04:23:59:59",
      src_from: "cool",
    },
    {
      course: "普通心理學 General Psychology",
      hw: "第十一週討論：記憶理論提問與回答",
      due: "2021:05:09:23:59:59",
      src_from: "cool",
    },
    {
      course: "普通心理學 General Psychology",
      hw: "第十三週討論：錯放罪犯 vs 錯抓無辜",
      due: "2021:05:23:23:59:59",
      src_from: "cool",
    },
    {
      course: "普通心理學 General Psychology",
      hw: "第十二週討論：情緒性事件的記憶",
      due: "2021:05:16:23:59:59",
      src_from: "cool",
    },
    {
      course: "普通心理學 General Psychology",
      hw: "第十五週討論：鏡子測驗",
      due: "2021:06:06:23:59:59",
      src_from: "cool",
    },
    {
      course: "普通心理學 General Psychology",
      hw: "第十六週討論：社會心理學問答",
      due: "2021:06:13:23:59:59",
      src_from: "cool",
    },
    {
      course: "普通心理學 General Psychology",
      hw: "第十四週討論：學習舉例",
      due: "2021:05:30:23:59:59",
      src_from: "cool",
    },
    {
      course: "普通心理學 General Psychology",
      hw: "第十週討論：心靈與大腦",
      due: "2021:05:02:23:59:59",
      src_from: "cool",
    },
    {
      course: "普通心理學 General Psychology",
      hw: "第四週作業：object-based attention",
      src_from: "cool",
    },
    {
      course: "普通心理學 General Psychology",
      hw: "第四週討論：半邊忽略症",
      src_from: "cool",
    },
    {
      course: "普通心理學 General Psychology",
      hw: "1-1-Tolman",
      src_from: "cool",
    },
    {
      course: "普通心理學 General Psychology",
      hw: "1-2-Tolman",
      src_from: "cool",
    },
    {
      course: "普通心理學 General Psychology",
      hw: "2-1-Donders",
      src_from: "cool",
    },
    {
      course: "機器學習 Machine Learning",
      hw: "HW13 - Model Description",
      src_from: "cool",
    },
    {
      course: "機器學習 Machine Learning",
      hw: "HW14",
      due: "2021:07:02:23:59:00",
      src_from: "cool",
    },
    {
      course: "機器學習 Machine Learning",
      hw: "HW15",
      due: "2021:07:09:23:59:00",
      src_from: "cool",
    },
    {
      course: "機器學習 Machine Learning",
      hw: "HW01 total score",
      src_from: "cool",
    },
    {
      course: "機器學習 Machine Learning",
      hw: "HW02 total score",
      src_from: "cool",
    },
    {
      course: "機器學習 Machine Learning",
      hw: "HW03 total score",
      src_from: "cool",
    },
    {
      course: "機器學習 Machine Learning",
      hw: "HW04 total score",
      due: "2021:05:02:23:59:59",
      src_from: "cool",
    },
    {
      course: "機器學習 Machine Learning",
      hw: "HW05 total score",
      src_from: "cool",
    },
    {
      course: "機器學習 Machine Learning",
      hw: "HW06 total score",
      src_from: "cool",
    },
    {
      course: "機器學習 Machine Learning",
      hw: "HW07 total score",
      src_from: "cool",
    },
    {
      course: "機器學習 Machine Learning",
      hw: "HW08 total score",
      src_from: "cool",
    },
    {
      course: "機器學習 Machine Learning",
      hw: "HW09",
      due: "2021:05:28:23:59:00",
      src_from: "cool",
    },
    {
      course: "機器學習 Machine Learning",
      hw: "HW10 total score",
      src_from: "cool",
    },
    {
      course: "機器學習 Machine Learning",
      hw: "GitHub account survey for JudgeBoi",
      due: "2021:04:04:23:59:00",
      src_from: "cool",
    },
    {
      course: "機器學習 Machine Learning",
      hw: "HW02-1 total score",
      src_from: "cool",
    },
    {
      course: "機器學習 Machine Learning",
      hw: "HW02-2 total score",
      src_from: "cool",
    },
    {
      course: "機器學習 Machine Learning",
      hw: "HW01 Code Submission",
      due: "2021:03:28:23:59:59",
      src_from: "cool",
    },
    {
      course: "機器學習 Machine Learning",
      hw: "HW02 Code Submission",
      due: "2021:04:04:23:59:59",
      src_from: "cool",
    },
    {
      course: "機器學習 Machine Learning",
      hw: "HW03 Code Submission",
      due: "2021:04:18:23:59:59",
      src_from: "cool",
    },
    {
      course: "機器學習 MachineLearning",
      hw: "HW04 Code Submission",
      due: "2021:04:18:23:59:59",
      src_from: "cool",
    },
    {
      course: "機器學習 MachineLearning",
      hw: "HW05 Code Submission",
      due: "2021:05:02:23:59:59",
      src_from: "cool",
    },
    {
      course: "機器學習 MachineLearning",
      hw: "HW06 Code Submission",
      due: "2021:05:16:23:59:59",
      src_from: "cool",
    },
    {
      course: "機器學習 MachineLearning",
      hw: "HW07 Code Submission",
      due: "2021:05:23:23:59:59",
      src_from: "cool",
    },
    {
      course: "機器學習 MachineLearning",
      hw: "HW08 Code Submission",
      due: "2021:05:23:23:59:59",
      src_from: "cool",
    },
    {
      course: "機器學習 MachineLearning",
      hw: "HW10 Code Submission",
      due: "2021:05:30:23:59:59",
      src_from: "cool",
    },
    {
      course: "機器學習 MachineLearning",
      hw: "HW11 Code Submission",
      due: "2021:06:13:23:59:59",
      src_from: "cool",
    },
    {
      course: "機器學習 MachineLearning",
      hw: "HW12 Code Submission",
      due: "2021:06:27:23:59:59",
      src_from: "cool",
    },
    {
      course: "機器學習 MachineLearning",
      hw: "HW13 Code Submission",
      due: "2021:07:04:23:59:59",
      src_from: "cool",
    },
    {
      course: "籃球中級",
      hw: "影片欣賞",
      due: "2021:06:02:17:00:00",
      src_from: "ceiba",
    },
    {
      course: "籃球中級",
      hw: "影片欣賞2",
      due: "2021:06:18:17:00:00",
      src_from: "ceiba",
    },
    {
      course: "一般醫學保健",
      hw: "期末報告(一)",
      due: "2021:06:03:00:00:00",
      src_from: "ceiba",
    },
    {
      course: "一般醫學保健",
      hw: "期末報告(二)",
      due: "2021:06:03:00:00:00",
      src_from: "ceiba",
    },
    {
      course: "一般醫學保健",
      hw: "期末報告(三)",
      due: "2021:06:03:00:00:00",
      src_from: "ceiba",
    },
    {
      course: "網路服務程式設計",
      hw: "HW#1",
      due: "2021:03:08:21:00:00",
      src_from: "ceiba",
    },
    {
      course: "網路服務程式設計",
      hw: "HW#2",
      due: "2021:03:15:21:00:00",
      src_from: "ceiba",
    },
    {
      course: "網路服務程式設計",
      hw: "HW#3",
      due: "2021:04:07:21:00:00",
      src_from: "ceiba",
    },
    {
      course: "網路服務程式設計",
      hw: "HW#4",
      due: "2021:04:22:21:00:00",
      src_from: "ceiba",
    },
    {
      course: "網路服務程式設計",
      hw: "HW#5",
      due: "2021:05:10:21:00:00",
      src_from: "ceiba",
    },
    {
      course: "網路服務程式設計",
      hw: "HW#6",
      due: "2021:05:20:21:00:00",
      src_from: "ceiba",
    },
    {
      course: "網路服務程式設計",
      hw: "HW#7",
      due: "2021:06:21:21:00:00",
      src_from: "ceiba",
    },
    {
      course: "資料庫系統-從SQL到NoSQL",
      hw: "hw1",
      due: "2021:03:16:14:00:00",
      src_from: "ceiba",
    },
    {
      course: "資料庫系統-從SQL到NoSQL",
      hw: "hw2",
      due: "2021:03:31:00:00:00",
      src_from: "ceiba",
    },
    {
      course: "資料庫系統-從SQL到NoSQL",
      hw: "hw3",
      due: "2021:04:20:14:00:00",
      src_from: "ceiba",
    },
    {
      course: "資料庫系統-從SQL到NoSQL",
      hw: "hw4",
      due: "2021:05:07:14:00:00",
      src_from: "ceiba",
    },
    {
      course: "資料庫系統-從SQL到NoSQL",
      hw: "hw5",
      due: "2021:05:18:14:00:00",
      src_from: "ceiba",
    },
    {
      course: "資料庫系統-從SQL到NoSQL",
      hw: "hw6",
      due: "2021:06:16:14:00:00",
      src_from: "ceiba",
    },
    {
      course: "資料庫系統-從SQL到NoSQL",
      hw: "Final project PPT + Video",
      due: "2021:06:13:00:00:00",
      src_from: "ceiba",
    },
    {
      course: "資料庫系統-從SQL到NoSQL",
      hw: "Final project Report + Code",
      due: "2021:06:30:00:00:00",
      src_from: "ceiba",
    },
  ],
};

// 2021/06/09 21/00/undefined undefined:undefined

const tidyUpDate = (dateString) => {
  const tok = dateString.split(":");
  return tok[0] + "/" + tok[1] + "/" + tok[2] + " " + tok[3] + ":" + tok[4];
};

const tidyUpData = (rawData) => {
  const today = new Date();
  const date = "2020:06:16:";
  // const date = today.getFullYear() + ':' + (today.getMonth() + 1) + ':' + today.getDate() + ':';
  const time = today.getHours() + ":" + today.getMinutes();
  if (rawData) {
    const validData = rawData.filter(
      (data) => data.due === undefined || data.due > date + time
    );
    return validData.map((data, index) => {
      data.key = index;
      data.due = data.due ? tidyUpDate(data.due) : "";
      return data;
    });
  }
  return null;
};

const ReadyData = tidyUpData(packet.info);

export { ReadyData, tidyUpData, packet };
<file_sep>/components/search/SearchTable.js
/* eslint-disable react/display-name */
import React, { useState } from "react";
import "antd/dist/antd.css";
import { PlusSquareOutlined } from "@ant-design/icons";

import { Table, Space, Button } from "antd";

const MAX_COURSE_NUM = 10;

const SearchTable = ({
  data,
  selected,
  setSelected,
  setModalVisible,
  displayStatus,
  current,
  total,
  onPagechange,
}) => {
  const pagenation = {
    // pageSize: 50,
    current: current,
    total: total,
    onChange: onPagechange,
    pageSizeOptions: [10],
    position: ["topRight", "bottomRight"],
    showTotal: () => (
      <div className="Search-button_wrap">
        <Button
          onClick={() => setModalVisible(true)}
          type="primary"
          disabled={selected.length <= 0}
        >
          刪除或查看已選課程
        </Button>
      </div>
    ),
  };
  const columns = [
    {
      title: "年份",
      dataIndex: "semester",
      key: "semester",
    },
    {
      title: "流水號",
      dataIndex: "id",
      key: "id",
    },
    {
      title: "課程名稱",
      dataIndex: "class_name",
      key: "class_name",
    },
    {
      title: "學分",
      dataIndex: "credit",
      key: "credit",
    },
    {
      title: "加選方式",
      dataIndex: "reg_method",
      key: "reg_method",
    },
    {
      title: "課程識別碼",
      dataIndex: "class_id",
      key: "class_id",
    },
    {
      title: "授課教師",
      key: "teacher_name",
      dataIndex: "teacher_name",
    },
    {
      title: "",
      key: "add",
      width: "4%",
      render: (record) => (
        <Space size="middle">
          <PlusSquareOutlined
            onClick={() => {
              // already in list
              if (
                selected.find(
                  (course) =>
                    course.semester === record.semester &&
                    course.id === record.id
                )
              ) {
                displayStatus({
                  type: "error",
                  msg: "此課程已存在於清單中",
                });
                return;
              }
              // out of maxima
              if (selected.length >= MAX_COURSE_NUM) {
                displayStatus({
                  type: "error",
                  msg: `已達課程清單上限 (${MAX_COURSE_NUM}堂)`,
                });
                return;
              }
              const newSelected = [...selected];
              newSelected.push(record);
              displayStatus({
                type: "success",
                msg: "成功加入此課程",
              });
              setSelected(newSelected);
            }}
            style={{ color: "#08c" }}
          />
        </Space>
      ),
    },
  ];

  return (
    <>
      <div className="Search-table">
        <Table
          columns={columns}
          dataSource={data}
          bordered
          pagination={pagenation}
          scroll={{ y: 480 }}
        />
      </div>
    </>
  );
};

export default SearchTable;
<file_sep>/public/posts/web-knowledge.md
title: 台大學生都應該要懂的網路基礎知識（特別是住宿生）
date: 2020-09-24
img: web-knowledge/main.webp
description: 身為宿舍網管，這篇整理了有關宿舍網路的基本知識
tags: 台大, 網管
+-+-+-+-
## 前言
每次都搞不懂網路的設定嗎？照著別人寫好的SOP做，還是不知道為什麼不能用？上網google設定網路的資料，卻都看不懂？不管你是文學院還是理學院，本科生還是非本科生，本篇用淺顯易懂的介紹與比喻，帶你了解大學生平常會遇到的網路問題，背後的原因與原理！  
※本篇作為最基礎的觀念理解，所用的詞彙與敘述在專業領域上可能不甚精準，如果想追求完全正確的說法，歡迎自行google  

## Ch1. 【從真實世界走入虛擬世界】 - IP、MAC與網卡

![](https://i.imgur.com/Wx4oHdS.jpg)

#### IP是什麼  
**IP**就是網路虛擬世界中的**房屋門牌地址**，你**必須透過IP才有辦法在網路世界寄信給其他人、跟他溝通**。
IP是由一串長度為32的「數字0跟1」所組成的字串，如果你數學夠好的話，就可以知道這個字串的形式有「2的32次方」種可能，也就是全世界最多可以有「2的32次方」個不同的IP。  

#### 網卡是什麼  
網卡是一塊被設計用來允許電腦在電腦網路上進行通訊的電腦硬體，這是維基百科上面的解釋。  
翻成白話文，它是電腦上的一塊零件，你必須要透過這塊零件才能連wifi（無線網路）、連有線網路等等；可以理解為讓你**從真實世界進入網路虛擬世界的橋樑**。  
**每一張網卡都對應到一個MAC位址**，也就是它的ID；可以理解為**那座橋的名字**。
而**MAC位址**理應上要獨一無二，但事實上是**有可能重複**；如同台灣有這麼多座橋，也一定**有幾座剛好有同樣的名字**。  
這邊要補充一下︰連wifi的是一張網卡，**如果你的電腦有可以接網路線的插孔，那它就會有另外一張網卡拿來連有線網路**；連無線網路就要走橋A，連有線網路就要走橋B。  
再補充一點︰除了電腦，手機、**路由器**、**網路孔轉USB的轉接頭**上也都有網卡。  

#### MAC位址（實體位址）是什麼  
你要先理解網路卡（簡稱網卡）是什麼，然後**MAC位址就是那塊網卡的ID**。  


### 到這裡，先整理一下網路世界與現實世界的對應關係  

|**網路世界**|**現實世界**|
|--|--|
|IP|房屋門牌|
|網卡|從真實世界進入網路世界的橋樑|
|MAC位址|橋樑的名字|

### 你也可以歸納出幾個重點
* IP的數量是**有限的**
* **一台裝置依不同的連線方式可能有多個網卡**（從真實世界進入網路世界的橋可以有很多座）
* **MAC位址是有可能重複的**(台灣有這麼多座橋，也一定有幾座是有同樣名字的)

### 更進一步來解惑  
**Q:** 上面不是說IP是由一串長度為32的「數字0跟1」所組成的字串，那我為什麼平常看到的IP都是xxx.xxx.xxx.xxx的樣子？  
**A:** 我們可以把這串數字切成4等分，而每一等份長度都是8，而將每一等分的二進位字串用十進位數字來表達，就會變成一個0 ~ 255的數字，之後再把這4等分的數字用點連起來就變成了我們平常看到的IP的樣子。之後會提到的「子網路遮罩」、「預設閘道」等等，只要長相是xxx.xxx.xxx.xxx就也是同樣的概念。  


## Ch2. 【郵局的出現】 - 區域網路的概念、子網路遮罩與預設閘道

![](https://i.imgur.com/YyJ6Xa7.jpg)

現在，我們要介紹一個新角色───郵局，沒錯，網路世界中也有郵局。  
在現實世界中，我們寄信一定要透過郵局幫我們轉送信件，而一間郵局也會負責處理一個地區內的所有信件。  
同時，郵局也有**階層之分**，比如說鄉鎮等級的小郵局會把要寄給外縣市的信轉交給縣市等級的大郵局；如果是要寄去國外的信，大郵局則會再轉給國家等級的總郵局，藉此轉送到其他國家。  
在網路世界中，**郵局**就好比一個個的**區域網路**，同一個區域網路下的IP都由同一個的郵局管轄，而**子網路遮罩**則規定了每間**郵局管轄範圍的大小（**也就是**郵局的等級**），當遮罩長度越長，代表那間郵局的管轄範圍越小（等級越低），也就是那個區域網路下的IP數量越少。  
**預設閘道**則是當這封信的收信地址不在這間郵局的管轄範圍時，要**轉送給其他郵局的管道**。  


### 到這裡，先整理一下網路世界與現實世界的對應關係  

|**網路世界**|**現實世界**|
|--|--|
|區域網路|郵局|
|子網路遮罩|郵局管轄範圍的大小(郵局的等級)|
|預設閘道|轉送給其他郵局的管道|

### 你也可以歸納出幾個重點
* 區域網路是有階級之分的，**階級越高，遮罩長度越短，郵局的管轄範圍越大**
* 大的郵局下面可能有小的郵局，同樣地，大的區域網路下面可能有小的區域網路
* 只要**有了IP+子網路遮罩**，你就**可以反推你在哪個區域網路之下**（只要有自己的地址與郵局的等級，你就可以知道幫自己寄信的郵局是哪間）
* 在區域網路之中，每個人的IP是**不一樣**的(門牌地址不一樣)（對比Ch4的最後一點）


### 更進一步來解惑  
**Q:** 為什麼遮罩長度越長，那間郵局的管轄範圍是越小（等級越低）而不是越大啊？  
**A:** 上面有提到IP是長度為32的一串數字，而被遮罩遮到的部份代表網路位址（郵局管轄的行政區），剩下沒被遮罩遮到的部份代表主機位址（也就是那個 行政區之下之下某間住戶的地址）。沒被遮罩遮到的部份長度越長，就代表那間郵局之下有更多的住戶。

## Ch3. 【門牌的取得方式】 - 浮動IP(DHCP)和固定IP
前面講到每間郵局都有自己的管轄範圍，而在管轄範圍裡，肯定常常會有新的住戶搬進來或舊的住戶搬出去。而每間郵局管轄範圍裡面的房子數量是固定的，所以**門牌的數量也是固定的**。  
現在，郵局有兩種方法可以收發門牌給這些遷徙的住戶：  
第一種**（屬人主義）**：現在有住戶要搬走，郵局就把他們家的**門牌回收到郵局**，之後如果有新的住戶要進來，我再從郵局的這一疊門牌拿一片給他。  
第二種**（屬地主義）**：一片門牌都只對應到一間房子，他如果搬走了**郵局也不收回來**，那間房子就這樣空著，直到某個新住戶又搬進那間房子，他用的同樣那片門牌。  
對應到網路上，第一種就是所謂的**浮動IP**，而負責收發這疊門牌的人就是**DHCP**。當有新的電腦要在這個區域網路連線時，這個電腦就會被DHCP分配到一個沒人用的IP，等到他**斷開連線之後，DHCP就會把這個IP回收**；第二種則是所謂的**固定IP**，當某台電腦想要連線時，它必須**把自己的IP設定到某個沒人用的IP**，才有辦法連線。  

### 到這裡，先整理一下網路世界與現實世界的對應關係  

|**網路世界**|**現實世界**|
|--|--|
|浮動IP|第一種方式（屬人主義）|
|固定IP|第二種方式（屬地主義）|
|DHCP|在屬人主義之下，負責收發這疊門牌的人|

### 你也可以歸納出幾個重點
* DHCP會**自動分配**一個沒人用的IP給你（郵局發送一片沒人用的門牌）
* 採用固定IP的方式的話要**自己去找沒人用的IP**來用（你要自己去找沒人住的房子來住）
* 只有固定IP才要設定**「IP位址」、「子網路遮罩」、「預設閘道」**等等的參數，因為你必須要告訴你的電腦，**自己的門牌地址是什麼、負責的郵局是哪一間、那間郵局轉送給其他郵局的管道**等等

### 更進一步來解惑 
**Q:** 這樣聽起來，第一種方式顯然比較好啊，有人會主動發門牌給你欸，省的我還要一間一間房子去看有沒有人住。第二種方法的好處在哪啊？  
**A:** 固定IP的好處是，你每次連上線之後，對外的IP都是一樣的，所以別人可以靠著這組IP找到你；如果你用
的是浮動IP，你的IP在每次連線就會都不一樣，別人會很難找到你。

## Ch4. 【門牌快發完了怎麼辦？】 - 路由器上的外網(WAN)與內網(LAN)

![](https://i.imgur.com/LhDczFP.jpg)

Ch1中說到，**IP就是網路虛擬世界中的房屋住址**，所以假設全球70億人每人都有一棟房子，那就要有70億張獨一無二的門牌號碼。這在現實生活中也許可行，但在虛擬世界中，門牌號碼（IP）是有長度限制的（長度為32），這導致**門牌(IP)的總數量是有限的**(只有「2的32次方」個，Ch1有提到過)。  
在網路日益發達之下，門牌（IP）已經快被發完了，於是有人想出了一個解決辦法：一整個社區只有一個門牌號碼，而自己社區裡面的人則用社區內部的代號來識別彼此。如果想跟**自己社區內**其他人溝通，可以透過**社區內部的代號**來找到他；如果想跟**其他社區**的人溝通，就**只能寫信到他的社區**，至於他住在那個社區的哪一戶，你也無從知道，畢竟你沒有他們社區內部的住戶名單，也不可能叫社區警衛把信拆開來看後再決定這封信要給誰吧？信裡可能有一些個人隱私啊。  
看到這裡，可能有些人會有疑問：那我就想要寄給裡面的某個人，阿這樣信要怎麼交到他手上？？？這個問題的確棘手，但我們先留到後面的章節再來解決。  
總而言之，至少現在這樣，地址就可以少一些，也就可以減緩門牌匱乏的危機；對比到網路世界，**外網(WAN)**就是所謂**「自己社區以外的世界」**，**內網(LAN)**就是**「自己社區以內的世界」**，而**路由器**就像是**社區警衛**，負責處理那些寄來寄去的信件，**（外網）IP**就是所謂的**社區門牌地址**，而**內網IP**則是**社區內部**用來區分不同人而給每個人的**代號**。  
**備註：原本所稱的IP(門牌地址)在這裡被改稱為外網IP(同樣是門牌地址)，這是為了與內網IP(社區內部代號)做出區別。**  

### 到這裡，先整理一下網路世界與現實世界的對應關係  

|**網路世界**|**現實世界**|
|--|--|
|外網(WAN)|自己社區以外的世界|
|內網(LAN)|自己社區以內的世界|
|路由器|社區警衛|
|（外網）IP|社區門牌|
|內網IP|社區內部代號|

### 你也可以歸納出幾個重點
* 內網是為了**解決IP不夠用**的補救辦法
* 信上面**只有寫社區住址**，至於要怎麼往下分送到指定的人手上我們無從知道
* **社區內部的人則用社區自己的代號來識別彼此**
* **社區與社區之間用門牌來識別彼此**
* 在內網中，每個人的外網IP是**一樣**的(門牌地址一樣)（對比Ch2的最後一點）

### 更進一步來解惑
**Q:** **內網與外網**（Ch4）的關係為什麼聽起來這麼像**區域網路與區域外網路的關係**(Ch2)啊？他們之間有什麼差別？  
**A:** 內網的所有電腦都是用同一個IP，而區域網路內的電腦是用不同的IP


<!--## DNS （待補充）
## VPN （待補充）
## 上面的東西沒搞懂？沒關係，我們來做幾題練習題 (待補充)
Q1. 台大宿網需要使用固定IP與MAC位址，註冊的MAC位址可以在台大宿網註冊系統裡面修改，但台大宿網註冊系統的網站必須在台大校園網路內才能打開-->

## 附錄 你可以在這找到與台大網路有關的資訊

[台大宿舍網路註冊系統(需於台大校園網路內開啟)](https://dorm.ntu.edu.tw/)  
[台大宿舍 超過限制流量與流量異常清單(需於台大校園網路內開啟)](https://dorm.ntu.edu.tw/block.php?personal=no)  
[網路基本名詞介紹](https://www.itread01.com/content/1542099547.html)  
[台大宿舍網路常見問題](http://172.16.58.3/NTU/DORM/dorm_qa.htm)  
[台大宿舍網路使用說明](https://hackmd.io/@AdXSbob-RhG3gBR4lnaNIw/ryEZjbK4Z?type=view)  
[台大宿舍網路路由器安裝詳解](https://ut0903.github.io/router-install/)
[台大校園無線網路連線設定（**NTUpeap**）](https://ccnet.ntu.edu.tw/wireless2/wireless.html)  
[台大VPN服務](https://ccnet.ntu.edu.tw/vpn/)  <file_sep>/out/posts/first-post.md
title: Blog建立歷程紀錄
date: 2020-09-20
img: first-post/main.jpg
description: 紀錄這個blog的建立過程與參考資料等等
tags: 網站
+-+-+-+-

# 建立動機

當初看到高中同學用一些簡單的github工具就可以建成一個不錯看的blog，於是先嘗試建了一個高中社團的網站，有初步概念後才開始經營這個網站  

# 你所需要查詢的資料

## 基礎

### 如何使用Github指令建立jekyll網站

此篇參考那位高中同學的建立方式，他講的滿詳細的  
Ref: [https://poi0905.github.io/blog/githubpages-tutorial/](https://poi0905.github.io/blog/githubpages-tutorial/)

### jekyll網站架構理解

Ref: [https://jekyllrb.com/docs/step-by-step/01-setup/](https://jekyllrb.com/docs/step-by-step/01-setup/ "Jekyll Step by Step Tutorial")

### Karmdown語法快速導覽

Blog中日誌使用Karmdown的語法來渲染  
Ref: [https://kramdown.gettalong.org/quickref.html](https://kramdown.gettalong.org/quickref.html)

### Blog留言區(Disqus)註冊與設定

Ref: [https://disqus.com/](https://disqus.com/)

## 進階

### Html, Css, Javascript認識

讓你可以更自由地修改網站樣式  

### 網站轉址服務

Ref: [https://js.org/](https://js.org/)

### 認識DNS

Ref: [http://linux.vbird.org/linux_server/0350dns.php](http://linux.vbird.org/linux_server/0350dns.php)
<file_sep>/components/todolist/functions/ExportCsv.js
import { noDataWarning, exportSuccess, emptyWarning } from "./HintModal";
import {Button} from 'antd'
const MakeCsv = (data) => {
  let csvData = "Subject,Description,Start date,Start Time,End Time\n";
  for (let i = 0; i < data.length; i++) {
    const row = data[i];
    if (row.due === "" || row.hw === "" || row.course === "") return "";
    let newCsvData = row.hw + "," + row.course + ",";
    const tok = row.due.split(" ");
    const tokDate = tok[0].split("/"); // yyyy/mm/dd
    const tokTime = tok[1].split(":"); // hh:mm:ss
    newCsvData += tokDate[1] + "/" + tokDate[2] + "/" + tokDate[0] + ","; // mm/dd/yyyy
    const time =
      tokTime[0] > 12
        ? tokTime[0] - 12 + ":" + tokTime[1] + " PM"
        : tokTime[0] + ":" + tokTime[1] + " AM";
    newCsvData += time + "," + time + "\n";
    csvData += newCsvData;
  }
  return csvData;
};
const ExportCsv = ({selectedRowKeys, tableData})=>{
  const onClick = ()=>{
    if (selectedRowKeys.length === 0) {
      noDataWarning();
      return;
    }
    const selectedData = tableData.filter((row) =>
      selectedRowKeys.includes(row.key)
    );
    const csvData = MakeCsv(selectedData);
    if (csvData) {
      exportSuccess();
      const blob = new Blob(["\uFEFF" + csvData], {
        type: "text/csv;charset=gb2312;",
      });
      const a = document.createElement("a");
      a.download = "todo.csv";
      a.href = URL.createObjectURL(blob);
      a.click();
    } else {
      emptyWarning();
    }
  }
  
  return (
    <Button type="primary" htmlType="button" onClick={onClick}>
      Export to CSV
    </Button>
  )
}
  
export default ExportCsv<file_sep>/contexts/AuthContext.js
import { useEffect, useState } from "react";
import { createContext } from "react";

const AuthContext = createContext({
  userInfo: null,
  setUserInfo:()=>{},
  modalShow: false,
  setModalShow: () => {},
});
export const AuthContextProvider = ({ children }) => {
  const [modalShow, setModalShow] = useState(false);
  const [userInfo, setUserInfo] = useState(null)
  useEffect(()=>{
    const s = sessionStorage.getItem("UserInfoJWT")
    console.log('s', s)
    if(!s){
      setUserInfo(s)
    }
  }, [])
  useEffect(()=>{
    sessionStorage.setItem("UserInfoJWT", userInfo)
  }, [userInfo])
  const context = { userInfo, setUserInfo, modalShow, setModalShow };
  return (
    <AuthContext.Provider value={context}>{children}</AuthContext.Provider>
  );
};

export default AuthContext;<file_sep>/utils/RetrievePost.js
const baseUrl = "public/posts"
const del = '+-+-+-+-'
const getPostContent = (pname)=>{
    const fs = require('fs');
    const str = fs.readFileSync(baseUrl + '/' + pname, 'utf8');
    return str.substring(str.indexOf(del) + del.length)
}
const getPostMeta = (pname)=>{
    const fs = require('fs');
    const str = fs.readFileSync(baseUrl + '/' + pname, 'utf8');
    const meta_raw = str.substring(0, str.indexOf(del))
    const meta = meta_raw.split("\n").filter((item)=>(item !== '')).map((item)=> {
        const obj = {}
        //console.log("+"+ item.substr(0,item.indexOf(':')).trim()+ "+", "-" + item.substr(item.indexOf(':')+1).trim() + "-")
        const key = item.substr(0,item.indexOf(':')).trim()
        if(key === 'tags'){
        const val = item.substr(item.indexOf(':')+1)
        //console.log('val', val)
        obj[key] = (val.split(',')).map((a)=>(a.trim()))
        //console.log(obj[key])
        }
        else{
        obj[key] = item.substr(item.indexOf(':')+1).trim()
        }
        return obj
    })
    meta.push({'url': 'post/' + pname.replace('.md', '')})
    return meta.reduce((a, b)=> ({...a, ...b}))
}

const getPostsName = () =>{
    const fs = require('fs');
    //console.log(fs)
    return fs.readdirSync(baseUrl)
}

export {getPostContent, getPostsName, getPostMeta}<file_sep>/components/search/functions/SearchByDepartment.js
import { Form, TreeSelect } from "antd";
import { useState } from "react";

const treeData = [
  {
    title: "Node1",
    value: "0-0",
    key: "0-0",
    children: [
      {
        title: "Child Node1",
        value: "0-0-0",
        key: "0-0-0",
      },
    ],
  },
  {
    title: "Node2",
    value: "0-1",
    key: "0-1",
    children: [
      {
        title: "Child Node3",
        value: "0-1-0",
        key: "0-1-0",
      },
      {
        title: "Child Node4",
        value: "0-1-1",
        key: "0-1-1",
      },
      {
        title: "Child Node5",
        value: "0-1-2",
        key: "0-1-2",
      },
    ],
  },
];
const { SHOW_PARENT } = TreeSelect;
const SearchByDepartment = () => {
  const [value, setValue] = useState(["0-0-0"]);
  console.log("value", value);
  return (
    <Form.Item label="選擇系所" style={{width:"100%"}}>
      <TreeSelect
        treeData={treeData}
        value={value}
        onChange={(value) => {
          setValue(value);
        }}
        treeCheckable={true}
        showCheckedStrategy={SHOW_PARENT}
        placeholder="Please select"
        style={{ width: "100%" }}
      />
    </Form.Item>
  );
};
export default SearchByDepartment;
<file_sep>/components/todolist/functions/HintModal.js
import {Modal} from 'antd'
const emptyWarning = () => {
  Modal.warning({
    title: "錯誤",
    content: "請檢查是否有欄位為空",
  });
};

const noDataWarning = () => {
  Modal.warning({
    title: "錯誤",
    content: "請選擇欲處理的資料",
  });
};

const exportSuccess = () => {
  Modal.success({
    title: "匯出成功",
    content: "可至 google 日曆匯入此檔案",
  });
};

const insertSuccess = () => {
  Modal.success({
    title: "已成功匯入至google 日曆",
  });
};

export {emptyWarning, noDataWarning, exportSuccess, insertSuccess}
<file_sep>/components/search/SelectedTable.js
/* eslint-disable react/display-name */
import React, { useState } from "react";
import "antd/dist/antd.css";
import { MinusSquareOutlined } from "@ant-design/icons";

import { Table, Space } from "antd";

const same = (r1, r2) => {
  if (r1.semester === r2.semester && r1.id === r2.id) return true;
  return false;
};

const SelectedTable = ({ selected, setSelected }) => {
  const columns = [
    {
      title: "年份",
      dataIndex: "semester",
      key: "semester",
    },
    {
      title: "課程名稱",
      dataIndex: "class_name",
      key: "class_name",
    },
    {
      title: "授課教師",
      key: "teacher_name",
      dataIndex: "teacher_name",
    },
    {
      title: "",
      key: "delete",
      width: "4%",
      render: (record) => (
        <Space size="middle">
          <MinusSquareOutlined
            onClick={() => {
              const newSelected = selected.filter(
                (data) => !same(data, record)
              );
              setSelected(newSelected);
            }}
            style={{ color: "#08c" }}
          />
        </Space>
      ),
    },
  ];

  return (
    <>
      <div className="Search-table">
        <Table
          columns={columns}
          dataSource={selected}
          bordered
          pagination={false}
          size="small"
        />
      </div>
    </>
  );
};

export default SelectedTable;
<file_sep>/components/Sidebar.js
// import * as React from 'react';
// import PropTypes from 'prop-types';
// import Grid from '@material-ui/core/Grid';
// //import Stack from '@material-ui/core/Stack';
// import Paper from '@material-ui/core/Paper';
// import Typography from '@material-ui/core/Typography';
// import Link from '@material-ui/core/Link';
// import GitHubIcon from '@material-ui/icons/GitHub';
// import FacebookIcon from '@material-ui/icons/Facebook';
// import InstagramIcon from '@material-ui/icons/Instagram';
// import {sidebar} from '../components/Attributes'
// const social = [
//   { name: 'GitHub', icon: GitHubIcon, href: 'https://github.com/UT0903' },
//   { name: 'Twitter', icon: InstagramIcon, href: '#' },
//   { name: 'Facebook', icon: FacebookIcon, href: 'https://www.facebook.com/profile.php?id=100000599048771' },
// ];
// function Sidebar() {

//   const { archives, description, title } = sidebar;
//   return (
//     <>
//       <Paper elevation={0} sx={{ p: 2, bgcolor: 'grey.200' }}>
//         <Typography variant="h6" gutterBottom>
//           {title}
//         </Typography>
//         <Typography>{description}</Typography>
//       </Paper>
//       <Typography variant="h6" gutterBottom sx={{ mt: 3 }}>
//         Archives
//       </Typography>
//       {archives?.map((archive) => (
//         <Link display="block" variant="body1" href={archive.url} key={archive.title}>
//           {archive.title}
//         </Link>
//       ))}

//       <Typography variant="h6" gutterBottom sx={{ mt: 3 }}>
//         Social
//       </Typography>

//       {social.map((network) => (
//         <Link
//           display="block"
//           variant="body1"
//           href={network.href}
//           key={network.name}
//           sx={{ mb: 0.5 }}
//         >
//           <network.icon />
//           <span>{network.name}</span>
//         </Link>
//       ))}
//     </>
//   );
// }

// // Sidebar.propTypes = {
// //   archives: PropTypes.arrayOf(
// //     PropTypes.shape({
// //       title: PropTypes.string.isRequired,
// //       url: PropTypes.string.isRequired,
// //     }),
// //   ).isRequired,
// //   description: PropTypes.string.isRequired,
// //   social: PropTypes.arrayOf(
// //     PropTypes.shape({
// //       icon: PropTypes.elementType.isRequired,
// //       name: PropTypes.string.isRequired,
// //     }),
// //   ).isRequired,
// //   title: PropTypes.string.isRequired,
// // };

// export default Sidebar;
<file_sep>/components/todolist/functions/ExportCalendar.js
//import { utilGoogle } from "./util";
import { noDataWarning, insertSuccess, emptyWarning } from "./HintModal";
import {Button} from 'antd'
const insertToCalendar = (data, success) => {
    const events = [];
    for (let i = 0; i < data.length; i++) {
      const row = data[i];
      let event = {};
      if (row.due === "" || row.hw === "" || row.course === "") return false;
      event.summary = row.hw;
      event.description = row.course;
      const tok = row.due.split(" ");
      const tokDate = tok[0].split("/"); // yyyy/mm/dd
      event.start = {
        dateTime: tokDate.join("-") + "T" + tok[1] + ":00+08:00", // Taiwan time zone
      };
      event.end = {
        dateTime: tokDate.join("-") + "T" + tok[1] + ":00+08:00", // Taiwan time zone
      };
      events.push(event);
    }
    //const { login } = utilGoogle();
    //login(events, success, () => {});
    return true;
  };
const ExportCalendar = ({selectedRowKeys, tableData})=>{
  const onClick = () =>{
    if (selectedRowKeys.length === 0) {
      noDataWarning();
      return;
    }
    const selectedData = tableData.filter((row) =>
      selectedRowKeys.includes(row.key)
    );
    const res = insertToCalendar(selectedData, insertSuccess);
    if (!res) emptyWarning();
  }
  return (
    <Button type="primary" htmlType="button" onClick={onClick}>
      Export to Calendar
    </Button>
  )
}
export default ExportCalendar<file_sep>/pages/search.js
import { useEffect, useState, useContext } from "react";

// import axios from "axios";
import "antd/dist/antd.css";
import SearchTable from "../components/search/NewSearchTable";
//import SelectedTable from "../components/search/SelectedTable";
import SearchForm from "../components/search/SearchForm";
import { Modal, message, Button } from "antd";
//import { CONCAT_SERVER_URL } from "../utils";
import { useQuery } from "react-query";
import { useRouter } from "next/router";
import Header from "../components/Header";
import AuthContext from "../contexts/AuthContext";
import axio from "../contexts/axios";
const MAX_TABLE_NUM = 10;
const BASEROWNUM = 50;
const SearchCourse = () => {
  const { userInfo } = useContext(AuthContext);
  const [searchKey, setSearchKey] = useState(null);
  // const { data = { courses: [] }, refetch } = useQuery(
  //   "getCourses",
  //   async () => {
  //     return await axio.post("/api/getCourseNum", {
  //       searchKey,
  //       startIdx,
  //       endIdx,
  //     });
  //   }
  //   // {
  //   //   onSuccess: (data) => {
  //   //     setTotal(data.total);
  //   //   },
  //   // }
  // );
  const onSearch = async(sk)=>{
    setSearchKey(sk)
    const {res, err} = await axio.post("/api/getCourseNum", {
      searchKey:sk,
      startIdx:0,
      rowNum:BASEROWNUM
    });
  }
  const [rowItems, setRowItems] = useState([
    {
      _id: "60ccc97a70861ee41d99e799",
      id: "97008",
      class_name: "土風舞",
      class_id: "00250260",
      class_num: "H9",
      semester: "109-2",
      reg_student_num: "199",
      study_student_num: "52",
      credit: "1.0",
      teacher_name: "魏豐閔",
      reg_method: "2",
      limit_student_num: "44",
    },
    {
      _id: "60ccc97a70861ee41d99e79a",
      id: "97009",
      class_name: "標準舞",
      class_id: "00250310",
      class_num: "J3",
      semester: "109-2",
      reg_student_num: "269",
      study_student_num: "57",
      credit: "1.0",
      teacher_name: "王學富",
      reg_method: "2",
      limit_student_num: "48",
    },
  ]);
  
  const loadMore = () => {
    // simulate a request
    return fetch("/api/getCourse")
      .then((res) => {
        return res.json();
      })
      .then((result) => {
        console.log("fetch", result);
        setRowItems(rowItems.concat(result.courses));
      });
  };
  const onRowClick = (e) => {
    console.log(e);
  }
  const onRowRightClick = (e) => {
    console.log(e);
  }
  // const displayStatus = (payload) => {
  //   if (payload.msg) {
  //     const { type, msg } = payload;
  //     const content = { content: msg, duration: 1.5 };
  //     switch (type) {
  //       case "success":
  //         message.success(content);
  //         break;
  //       case "error":
  //       default:
  //         message.error(content);
  //         break;
  //     }
  //   }
  // };
  if (!userInfo) return <h1>Please Login First</h1>;
  return (
    <>
      <SearchForm onSearch={onSearch}/>
      <SearchTable rowCount={100000} rowItems={rowItems} setRowItems={setRowItems} loadMore={loadMore} onRowClick={onRowClick} onRowRightClick={onRowRightClick}/>
    </>
  );
};

export default SearchCourse;
<file_sep>/pages/api/getCourse.js
export default async function handler(req, res) {
  const packet = {
    courses: [
      {
        _id: "60ccc97a70861ee41d99e799",
        id: "97008",
        class_name: "土風舞",
        class_id: "00250260",
        class_num: "H9",
        semester: "109-2",
        reg_student_num: "199",
        study_student_num: "52",
        credit: "1.0",
        teacher_name: "魏豐閔",
        reg_method: "2",
        limit_student_num: "44",
      },
      {
        _id: "60ccc97a70861ee41d99e79a",
        id: "97009",
        class_name: "標準舞",
        class_id: "00250310",
        class_num: "J3",
        semester: "109-2",
        reg_student_num: "269",
        study_student_num: "57",
        credit: "1.0",
        teacher_name: "王學富",
        reg_method: "2",
        limit_student_num: "48",
      },
      {
        _id: "60ccc97a70861ee41d99e79b",
        id: "97010",
        class_name: "標準舞",
        class_id: "00250310",
        class_num: "J4",
        semester: "109-2",
        reg_student_num: "255",
        study_student_num: "40",
        credit: "1.0",
        teacher_name: "王學富",
        reg_method: "2",
        limit_student_num: "48",
      },
      {
        _id: "60ccc97a70861ee41d99e79c",
        id: "97011",
        class_name: "太極劍",
        class_id: "00250370",
        class_num: "23",
        semester: "109-2",
        reg_student_num: "369",
        study_student_num: "52",
        credit: "1.0",
        teacher_name: "游添燈",
        reg_method: "2",
        limit_student_num: "50",
      },
      {
        _id: "60ccc97a70861ee41d99e79d",
        id: "97004",
        class_name: "體適能(一)",
        class_id: "00250020",
        class_num: "P3",
        semester: "109-2",
        reg_student_num: "110",
        study_student_num: "42",
        credit: "1.0",
        teacher_name: "王翊澤",
        reg_method: "2",
        limit_student_num: "40",
      },
      {
        _id: "60ccc97a70861ee41d99e79e",
        id: "9701300250390002503900025039000250390002503900025039000250390002503900025039000250390",
        class_name: "太極拳初級",
        class_id: "00250390",
        class_num: "20",
        semester: "109-2",
        reg_student_num: "492",
        study_student_num: "57",
        credit: "1.0",
        teacher_name: "游添燈",
        reg_method: "2",
        limit_student_num: "50",
      },
      {
        _id: "60ccc97a70861ee41d99e79f",
        id: "97012",
        class_name: "太極拳初級",
        class_id: "00250390",
        class_num: "19",
        semester: "109-2",
        reg_student_num: "453",
        study_student_num: "57",
        credit: "1.0",
        teacher_name: "游添燈",
        reg_method: "2",
        limit_student_num: "50",
      },
      {
        _id: "60ccc97a70861ee41d99e7a0",
        id: "97014",
        class_name: "太極拳初級太極拳初級太極拳初級太極拳初級太極拳初級",
        class_id: "00250390",
        class_num: "21",
        semester: "109-2",
        reg_student_num: "427",
        study_student_num: "49",
        credit: "1.0",
        teacher_name: "游添燈",
        reg_method: "2",
        limit_student_num: "50",
      },
      {
        _id: "60ccc97a70861ee41d99e7a1",
        id: "970151",
        class_name: "太極拳初級",
        class_id: "00250390",
        class_num: "F9",
        semester: "109-2",
        reg_student_num: "389",
        study_student_num: "61",
        credit: "1.0",
        teacher_name: "施登堯",
        reg_method: "2",
        limit_student_num: "50",
      },
      {
        _id: "60ccc97a70861ee41d99e7a2",
        id: "97016",
        class_name: "太極拳中級",
        class_id: "00250400",
        class_num: "22",
        semester: "109-2",
        reg_student_num: "54",
        study_student_num: "25",
        credit: "1.0",
        teacher_name: "游添燈",
        reg_method: "2",
        limit_student_num: "50",
      },
    ],
    total: 104564,
  };
  res.status(200).send(packet);
}
<file_sep>/pages/_app.js
import "../styles/App.css";
import { QueryClientProvider, QueryClient } from "react-query";
import Header from "../components/Header";
import { AuthContextProvider } from "../contexts/AuthContext";
import Footer from "../components/Footer";
import { useState, useEffect } from "react";
import "bootstrap/dist/css/bootstrap.css";
import 'react-virtualized/styles.css';
import '../styles/SearchTable.css'
import "antd/dist/antd.css";
const queryClient = new QueryClient();

function MyApp({ Component, pageProps, navigation }) {
  //console.log(navigation);
  useEffect(() => {
    document.title = "台大課程小工具 | UT's blog";
  }, []);
  return (
    <>
      <AuthContextProvider navigation={navigation}>
        <Header />
        <QueryClientProvider client={queryClient}>
          <Component {...pageProps} />
        </QueryClientProvider>
      </AuthContextProvider>
    </>
  );
}

export default MyApp;
<file_sep>/components/todolist/util.js
import { useEffect } from "react";
import { gapi } from "gapi-script";

const utilGoogle = () => {
  const CLIENT_ID =
    "863727614400-ncmg6aoq5qkmme3nr5as964d7kon77iu.apps.googleusercontent.com";
  const API_KEY = "<KEY>";
  const SCOPE = "https://www.googleapis.com/auth/calendar";

  const login = (events, successCallback, failedCallback) => {
    const authParams = {
      response_type: "permission", // Retrieves an access token only
      client_id: CLIENT_ID, // Client ID from Cloud Console
      immediate: false, // For the demo, force the auth window every time
      scope: [
        "profile",
        "https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/calendar.events",
      ], // Array of scopes
    };

    gapi.load("client:auth2", () => {
      gapi.auth2.init({ client_id: CLIENT_ID });
      gapi.auth.authorize(authParams, (res) => {
        gapi.client.setApiKey(API_KEY);
        gapi.client
          .load(
            "https://content.googleapis.com/discovery/v1/apis/calendar/v3/rest"
          )
          .then(
            () => {
              const batch = gapi.client.newBatch();
              events.map((r, j) => {
                batch.add(
                  gapi.client.calendar.events.insert({
                    calendarId: "primary",
                    resource: events[j],
                  })
                );
              });
              batch.then(() => {
                console.log("all jobs now dynamically done!!!");
                successCallback();
              });
            },
            (err) => {
              console.log("auth failed", err);
              failedCallback();
            }
          );
      });
    });
  };
  return { login };
};

export { utilGoogle };
<file_sep>/components/Header.js
import * as React from "react";
import {
  Toolbar,
  Button,
  IconButton,
  Typography,
  Grid,
} from "@material-ui/core";

import MyLink from "../components/MyLink";
import { useRouter } from "next/router";
import AuthContext from "../contexts/AuthContext";
import { useContext, useState, useEffect } from "react";
import LoginForm from "../components/login/LoginForm";
import HomeRoundedIcon from "@material-ui/icons/HomeRounded";

export default function Header() {
  const { setModalShow, userInfo, setUserInfo } = useContext(AuthContext);
  const siteTitle = "UT的部落格 | UT's Blog";
  return (
    <React.Fragment >
      <Toolbar
        style={{
          justifyContent: "center",
          borderBottom: "1px solid",
          borderColor: "rgba(200, 200, 200, 100)",
          position: "sticky",
          top: 0,
          background: "rgba(240, 240, 240, 255)",
          zIndex: 1000,
        }}
      >
        <MyLink
          // display="block"
          // variant="body1"
          href={"."}
          // key={network.name}
          // sx={{ mb: 0.5 }}
        >
          <HomeRoundedIcon fontSize="large" />
          {/* <span>{network.name}</span> */}
        </MyLink>
        <Typography
          variant="h5"
          color="inherit"
          align="center"
          noWrap
          style={{ flex: 1 }}
        >
          {siteTitle}
        </Typography>
        {!userInfo? (
          <>
            <Typography
              style={{
                fontSize: "13px",
                backgroundColor: "rgba(0, 0, 0, 0)",
                marginRight: "10px",
                color: "red",
              }}
              gutterBottom
            >
              尚未登入
            </Typography>
            <Button
              size="small"
              style={{ border: "2px solid", color: "blue" }}
              onClick={() => {
                setModalShow(true);
              }}
            >
              登入
            </Button>
          </>
        ) : (
          <>
            <Typography
              style={{
                fontSize: "13px",
                backgroundColor: "rgba(0, 0, 0, 0)",
                marginRight: "10px",
              }}
              gutterBottom
            >
              {userInfo.acc} 已登入
            </Typography>
            <Button
              size="small"
              style={{ border: "2px solid", color: "green" }}
              onClick={() => {
                setUserInfo(null)
              }}
            >
              登出
            </Button>
          </>
        )}
      </Toolbar>
      {/* <Toolbar
          component="nav"
          variant="dense"
          style={{ justifyContent: 'space-around', flexWrap:"wrap" }}
        >
          {sections.map((section) => (
            <MyLink
              href={section.url}
              key={section.title}
              color='textPrimary'
              style={{fontSize:"15px", flexShrink:"0"}}
              xs={12}
              md={2}
            >
              {section.title}
            </MyLink>
          ))}
        </Toolbar> */}
      <LoginForm />
    </React.Fragment>
  );
}
<file_sep>/public/posts/router-install.md
title: 台大宿舍網路路由器零基礎安裝詳解（全圖解）
date: 2020-09-22
img: router-install/main.jpeg
description: 這篇文章手把手教你怎麼在宿舍裝上自己的無線Wifi
tags: 台大, 網管
+-+-+-+-

## 前言

不想每次回宿舍上網都要插插拔拔網路線怎麼辦？手機沒有吃到飽，在宿舍也想要跟電腦一起用宿網網路怎麼辦？這篇文章可能可以幫助到你！

## ※所需工具

一台路由器（本篇使用tp-link-WR840N a.k.a網路上最便宜的路由器做示範）、一條網路線（通常隨著購買路由器會附送一條）  

## 1. 進入台大宿網管理系統平台 / 在台大宿網管理系統平台註冊

### 1-0. 確認電腦用正確的方式連上台大校園網路

* 方法1: 直接用網路線連至宿舍網路孔(記得要聽到「喀」一聲才有插緊)
Mac或沒有網路孔的電腦可以選擇用「網路孔轉USB的轉接頭」，或是使用其他方法
* 方法2: 將路由器連至宿舍網路孔，再將電腦用無線的方式連線到路由器  
透過路由器連到網路孔一樣是連到台大內網  
* 方法3: 將電腦連線至台大校園網路(NTU, ntupeap等等)
* 方法4: 使用台大VPN連線  
[台大VPN安裝及使用說明](https://ccnet.ntu.edu.tw/vpn/)  

### 1-1. 在網址列輸入172.16.17.32

### 1-2. 點選「宿舍網路註冊系統」

![](https://i.imgur.com/qYmvkmd.jpg)

### 1-3. 確認有出現紅色區域且網段欄為目前所住的宿舍，上方基本資料的部分沒有亂碼，新生或換宿生記得要在此頁面註冊或更新資料

![](https://i.imgur.com/7R6zdC1.png)

## 2. 路由器設定

(此部份依各家廠牌不同，可能需要輸入不同網址與輸入不同的預設帳密，請參考該廠牌說明書)

### 2-1. 將網路線連結於路由器上的WAN孔與宿舍網路孔之間，並將路由器接上電源

### 2-2. 用電腦連wifi到路由器，在電腦網址欄輸入「192.168.0.1」進入路由器管理頁面，輸入帳號和密碼(預設為帳號:admin 密碼:admin)

### 2-3. 點擊網路設定>網際網路(WAN)

### 2-4. 修改紅框3中內容

#### 2-4-1. 連線類型：改成固定IP

#### 2-4-2. IP位址：改成宿網註冊平台中的註冊IP(不是登入IP，不是登入IP，不是登入IP，很重要所以說三次)

#### 2-4-3. 子網路遮罩：輸入「255.255.255.0」。如果他要你輸入遮罩長度，請輸入「24」

#### 2-4-4. 預設閘道：IP位址的最後一碼改成254。如192.168.3.11的閘道就是192.168.3.11

#### 2-4-5. 主要DNS：192.168.3.11

#### 2-4-6. 次要DNS：172.16.58.3或是8.8.8.8

#### 2-4-7. 修改完成後請再三確認沒有打錯，記得按儲存

![](https://i.imgur.com/K7105uB.png)

## 3. 修改WAN MAC位址或修改註冊MAC位址

* 方法1: 修改路由器的MAC
* 方法2: 修改宿網管理系統的註冊MAC(適用於有些路由器不能改MAC a.k.a.爛路由器)  
這邊有兩種方法，兩種挑一種來做就好，基本上就是把兩個東西的MAC位址改成一樣的(如圖，把1跟2的MAC位址改成一樣的)  

![](https://i.imgur.com/KULRrzl.png)  
![](https://i.imgur.com/XPooX1V.png)

### 3-1. 另外，如果找不到路由器初始的MAC位址，路由器本體的下面通常會寫這台機器的詳細資訊

![](https://i.imgur.com/XNkbCCG.png)

## 4. 最後，把路由器接上網路線到宿舍網路孔

**注意，要接在有寫WAN的網路孔上，不要插錯了**  
![](https://i.imgur.com/7x8qgxF.jpg)

## 5. 大功告成，可以用電腦或手機試試看能不能連上網路

有時候需要等待5-10分鐘的學校資料庫更新  

還是不行？了解觀念後才會更清楚自己在幹嘛!  
[台大學生都應該了解的網路基礎知識](post/web-knowledge)
<file_sep>/components/login/useCountDown.js
import { useState, useEffect } from "react";

export default function useCounter() {
  const [remain, setRemain] = useState(0);
  useEffect(() => {
    if (remain > 0) {
      const countDown = () => setRemain((n) => n - 1);
      const timeout = setTimeout(countDown, 1000);
      return () => clearTimeout(timeout);
    }
    return () => {};
  }, [remain]);
  return [remain, setRemain];
}
<file_sep>/components/search/functions/TableColAttr.js
const attr = [
    { label: "流水號", dataKey: "id", width: 38, priority: 2 },
    {
      label: "授課對象",
      dataKey: "class_name",
      width: 42,
      priority: 2,
    },
    {
      label: "班次",
      dataKey: "class_num",
      width: 23,
      priority: 1,
    },
    {
      label: "課程名稱",
      dataKey: "class_name",
      width: 140,
      priority: 1,
    },
    { label: "學分", dataKey: "credit", width: 24, priority: 2 },
    {
      label: "必/選修",
      dataKey: "teacher_name",
      width: 36,
      priority: 3,
    },
    {
      label: "授課老師",
      dataKey: "teacher_name",
      width: 42,
      priority: 1,
    },
    {
      label: "加選方式",
      dataKey: "reg_method",
      width: 22,
      priority: 2,
    },
    {
      label: "時間教室",
      dataKey: "teacher_name",
      width: 155,
      priority: 1,
    },
    {
      label: "總人數",
      dataKey: "limit_student_num",
      width: 34,
      priority: 2,
    },
    {
      label: "限制條件與備註",
      dataKey: "teacher_name",
      width: 350,
      priority: 2,
    },
  ];
export default attr<file_sep>/pages/api/getTodos.js
export default async function handler(req, res) {
    const packet = {
        failed: [],
        info: [
          {
            course: "計算機網路實驗 Computer Network Laboratory",
            hw: "Lab 1 Trace route concept and implementation",
            due: "2021:03:25:23:59:59",
            src_from: "cool",
          },
          {
            course: "計算機網路實驗 Computer Network Laboratory",
            hw: "Lab2 Security Wireless LANs",
            due: "2021:04:22:23:59:59",
            src_from: "cool",
          },
          {
            course: "計算機網路實驗 Computer Network Laboratory",
            hw: "Lab 3 SIMULATING SDN WITH MININET",
            due: "2021:06:03:23:59:59",
            src_from: "cool",
          },
          {
            course: "計算機網路實驗 Computer Network Laboratory",
            hw: "The Presentation of demonstration",
            src_from: "cool",
          },
          {
            course: "計算機網路實驗 ComputerNetwork Laboratory",
            hw: "The Presentation of proposal",
            due: "2021:05:20:11:59:59",
            src_from: "cool",
          },
          {
            course: "普通心理學 General Psychology",
            hw: "心理學實驗參與加分證明上傳",
            due: "2021:06:21:23:59:59",
            src_from: "cool",
          },
          {
            course: "普通心理學 General Psychology",
            hw: "期中報告",
            due: "2021:04:25:23:59:59",
            src_from: "cool",
          },
          {
            course: "普通心理學 General Psychology",
            hw: "期末報告",
            due: "2021:06:21:23:59:59",
            src_from: "cool",
          },
          {
            course: "普通心理學 General Psychology",
            hw: "第三週討論：注意力何時介入",
            src_from: "cool",
          },
          {
            course: "普通心理學 General Psychology",
            hw: "第二週討論：心靈的本質",
            due: "2021:03:07:23:59:00",
            src_from: "cool",
          },
          {
            course: "普通心理學 General Psychology",
            hw: "第八週討論：化學的腦切除術",
            due: "2021:04:18:23:59:59",
            src_from: "cool",
          },
          {
            course: "普通心理學 General Psychology",
            hw: "第六週討論：陳敬鎧案",
            due: "2021:04:04:23:59:59",
            src_from: "cool",
          },
          {
            course: "普通心理學 General Psychology",
            hw: "第十一週討論：記憶理論提問與回答",
            due: "2021:05:09:23:59:59",
            src_from: "cool",
          },
          {
            course: "普通心理學 General Psychology",
            hw: "第十三週討論：錯放罪犯 vs 錯抓無辜",
            due: "2021:05:23:23:59:59",
            src_from: "cool",
          },
          {
            course: "普通心理學 General Psychology",
            hw: "第十二週討論：情緒性事件的記憶",
            due: "2021:05:16:23:59:59",
            src_from: "cool",
          },
          {
            course: "普通心理學 General Psychology",
            hw: "第十五週討論：鏡子測驗",
            due: "2021:06:06:23:59:59",
            src_from: "cool",
          },
          {
            course: "普通心理學 General Psychology",
            hw: "第十六週討論：社會心理學問答",
            due: "2021:06:13:23:59:59",
            src_from: "cool",
          },
          {
            course: "普通心理學 General Psychology",
            hw: "第十四週討論：學習舉例",
            due: "2021:05:30:23:59:59",
            src_from: "cool",
          },
          {
            course: "普通心理學 General Psychology",
            hw: "第十週討論：心靈與大腦",
            due: "2021:05:02:23:59:59",
            src_from: "cool",
          },
          {
            course: "普通心理學 General Psychology",
            hw: "第四週作業：object-based attention",
            src_from: "cool",
          },
          {
            course: "普通心理學 General Psychology",
            hw: "第四週討論：半邊忽略症",
            src_from: "cool",
          },
          {
            course: "普通心理學 General Psychology",
            hw: "1-1-Tolman",
            src_from: "cool",
          },
          {
            course: "普通心理學 General Psychology",
            hw: "1-2-Tolman",
            src_from: "cool",
          },
          {
            course: "普通心理學 General Psychology",
            hw: "2-1-Donders",
            src_from: "cool",
          },
          {
            course: "機器學習 Machine Learning",
            hw: "HW13 - Model Description",
            src_from: "cool",
          },
          {
            course: "機器學習 Machine Learning",
            hw: "HW14",
            due: "2021:07:02:23:59:00",
            src_from: "cool",
          },
          {
            course: "機器學習 Machine Learning",
            hw: "HW15",
            due: "2021:07:09:23:59:00",
            src_from: "cool",
          },
          {
            course: "機器學習 Machine Learning",
            hw: "HW01 total score",
            src_from: "cool",
          },
          {
            course: "機器學習 Machine Learning",
            hw: "HW02 total score",
            src_from: "cool",
          },
          {
            course: "機器學習 Machine Learning",
            hw: "HW03 total score",
            src_from: "cool",
          },
          {
            course: "機器學習 Machine Learning",
            hw: "HW04 total score",
            due: "2021:05:02:23:59:59",
            src_from: "cool",
          },
          {
            course: "機器學習 Machine Learning",
            hw: "HW05 total score",
            src_from: "cool",
          },
          {
            course: "機器學習 Machine Learning",
            hw: "HW06 total score",
            src_from: "cool",
          },
          {
            course: "機器學習 Machine Learning",
            hw: "HW07 total score",
            src_from: "cool",
          },
          {
            course: "機器學習 Machine Learning",
            hw: "HW08 total score",
            src_from: "cool",
          },
          {
            course: "機器學習 Machine Learning",
            hw: "HW09",
            due: "2021:05:28:23:59:00",
            src_from: "cool",
          },
          {
            course: "機器學習 Machine Learning",
            hw: "HW10 total score",
            src_from: "cool",
          },
          {
            course: "機器學習 Machine Learning",
            hw: "GitHub account survey for JudgeBoi",
            due: "2021:04:04:23:59:00",
            src_from: "cool",
          },
          {
            course: "機器學習 Machine Learning",
            hw: "HW02-1 total score",
            src_from: "cool",
          },
          {
            course: "機器學習 Machine Learning",
            hw: "HW02-2 total score",
            src_from: "cool",
          },
          {
            course: "機器學習 Machine Learning",
            hw: "HW01 Code Submission",
            due: "2021:03:28:23:59:59",
            src_from: "cool",
          },
          {
            course: "機器學習 Machine Learning",
            hw: "HW02 Code Submission",
            due: "2021:04:04:23:59:59",
            src_from: "cool",
          },
          {
            course: "機器學習 Machine Learning",
            hw: "HW03 Code Submission",
            due: "2021:04:18:23:59:59",
            src_from: "cool",
          },
          {
            course: "機器學習 MachineLearning",
            hw: "HW04 Code Submission",
            due: "2021:04:18:23:59:59",
            src_from: "cool",
          },
          {
            course: "機器學習 MachineLearning",
            hw: "HW05 Code Submission",
            due: "2021:05:02:23:59:59",
            src_from: "cool",
          },
          {
            course: "機器學習 MachineLearning",
            hw: "HW06 Code Submission",
            due: "2021:05:16:23:59:59",
            src_from: "cool",
          },
          {
            course: "機器學習 MachineLearning",
            hw: "HW07 Code Submission",
            due: "2021:05:23:23:59:59",
            src_from: "cool",
          },
          {
            course: "機器學習 MachineLearning",
            hw: "HW08 Code Submission",
            due: "2021:05:23:23:59:59",
            src_from: "cool",
          },
          {
            course: "機器學習 MachineLearning",
            hw: "HW10 Code Submission",
            due: "2021:05:30:23:59:59",
            src_from: "cool",
          },
          {
            course: "機器學習 MachineLearning",
            hw: "HW11 Code Submission",
            due: "2021:06:13:23:59:59",
            src_from: "cool",
          },
          {
            course: "機器學習 MachineLearning",
            hw: "HW12 Code Submission",
            due: "2021:06:27:23:59:59",
            src_from: "cool",
          },
          {
            course: "機器學習 MachineLearning",
            hw: "HW13 Code Submission",
            due: "2021:07:04:23:59:59",
            src_from: "cool",
          },
          {
            course: "籃球中級",
            hw: "影片欣賞",
            due: "2021:06:02:17:00:00",
            src_from: "ceiba",
          },
          {
            course: "籃球中級",
            hw: "影片欣賞2",
            due: "2021:06:18:17:00:00",
            src_from: "ceiba",
          },
          {
            course: "一般醫學保健",
            hw: "期末報告(一)",
            due: "2021:06:03:00:00:00",
            src_from: "ceiba",
          },
          {
            course: "一般醫學保健",
            hw: "期末報告(二)",
            due: "2021:06:03:00:00:00",
            src_from: "ceiba",
          },
          {
            course: "一般醫學保健",
            hw: "期末報告(三)",
            due: "2021:06:03:00:00:00",
            src_from: "ceiba",
          },
          {
            course: "網路服務程式設計",
            hw: "HW#1",
            due: "2021:03:08:21:00:00",
            src_from: "ceiba",
          },
          {
            course: "網路服務程式設計",
            hw: "HW#2",
            due: "2021:03:15:21:00:00",
            src_from: "ceiba",
          },
          {
            course: "網路服務程式設計",
            hw: "HW#3",
            due: "2021:04:07:21:00:00",
            src_from: "ceiba",
          },
          {
            course: "網路服務程式設計",
            hw: "HW#4",
            due: "2021:04:22:21:00:00",
            src_from: "ceiba",
          },
          {
            course: "網路服務程式設計",
            hw: "HW#5",
            due: "2021:05:10:21:00:00",
            src_from: "ceiba",
          },
          {
            course: "網路服務程式設計",
            hw: "HW#6",
            due: "2021:05:20:21:00:00",
            src_from: "ceiba",
          },
          {
            course: "網路服務程式設計",
            hw: "HW#7",
            due: "2021:06:21:21:00:00",
            src_from: "ceiba",
          },
          {
            course: "資料庫系統-從SQL到NoSQL",
            hw: "hw1",
            due: "2021:03:16:14:00:00",
            src_from: "ceiba",
          },
          {
            course: "資料庫系統-從SQL到NoSQL",
            hw: "hw2",
            due: "2021:03:31:00:00:00",
            src_from: "ceiba",
          },
          {
            course: "資料庫系統-從SQL到NoSQL",
            hw: "hw3",
            due: "2021:04:20:14:00:00",
            src_from: "ceiba",
          },
          {
            course: "資料庫系統-從SQL到NoSQL",
            hw: "hw4",
            due: "2021:05:07:14:00:00",
            src_from: "ceiba",
          },
          {
            course: "資料庫系統-從SQL到NoSQL",
            hw: "hw5",
            due: "2021:05:18:14:00:00",
            src_from: "ceiba",
          },
          {
            course: "資料庫系統-從SQL到NoSQL",
            hw: "hw6",
            due: "2021:06:16:14:00:00",
            src_from: "ceiba",
          },
          {
            course: "資料庫系統-從SQL到NoSQL",
            hw: "Final project PPT + Video",
            due: "2021:06:13:00:00:00",
            src_from: "ceiba",
          },
          {
            course: "資料庫系統-從SQL到NoSQL",
            hw: "Final project Report + Code",
            due: "2021:06:30:00:00:00",
            src_from: "ceiba",
          },
        ],
      };
    res.status(200).json(packet);
}<file_sep>/pages/post/[pid].js
import { useRouter } from "next/router";
import Grid from "@material-ui/core/Grid";
import Markdown from "markdown-to-jsx";
import { useState, useEffect } from "react";
import Typography from "@material-ui/core/Typography";
import Link from "@material-ui/core/Link";
import { makeStyles } from "@material-ui/core/styles";
import axio from "../../contexts/axios";
import {
  getPostContent,
  getPostMeta,
  getPostsName,
} from "../../utils/RetrievePost";
// import { getPostContent, getPostMeta, getPostsName } from '../../utils/RetrievePost';
const useStyles = makeStyles((theme) => ({
  listItem: {
    marginTop: theme.spacing(1),
  },
}));
function MarkdownListItem(props) {
  const classes = useStyles();
  return (
    <li className={classes.listItem}>
      <Typography component="span" {...props} />
    </li>
  );
}

export function getStaticPaths() {
  // Call an external API endpoint to get posts
  //console.log('getPost', posts)
  const pnames = getPostsName();
  const paths = pnames.map((fname) => ({
    params: { pid: fname.replace('.md', '') },
  }));
  // We'll pre-render only these paths at build time.
  // { fallback: false } means other routes should 404.
  return { paths, fallback: false };
}

// This also gets called at build time
export async function getStaticProps({ params }) {
  const fname = params.pid + '.md'
  const meta = getPostMeta(fname);
  const content = getPostContent(fname);
  return { props: { meta, content } };
}
const Post = ({ meta, content }) => {
  const options = {
    forceBlock: false,
    overrides: {
      h1: {
        component: Typography,
        props: {
          gutterBottom: true,
          variant: "h4",
          style: { padding: "0.5rem 0rem 0.5rem 0rem" },
        },
      },
      h2: {
        component: Typography,
        props: {
          gutterBottom: true,
          variant: "h5",
          style: { padding: "0.3rem 0rem 0.3rem 0rem" },
        },
      },
      h3: {
        component: Typography,
        props: {
          gutterBottom: true,
          variant: "h6",
          style: { padding: "0.1rem 0rem 0.1rem 0rem" },
        },
      },
      h4: {
        component: Typography,
        props: {
          gutterBottom: true,
          variant: "h6",
          paragraph: true,
          style: { padding: "0rem 0rem 0rem 0rem" },
        },
      },
      p: {
        component: Typography,
        props: {
          paragraph: true,
          // style:{padding:"0.5rem 0rem 0.5rem 0rem"}
        },
      },
      a: {
        component: Link,
        props: {
          style: { padding: "0.5rem 0rem 0.5rem 0rem" },
        },
      },
      li: {
        component: MarkdownListItem,
        props: {
          style: { padding: "0.5rem 0rem 0.5rem 0rem" },
        },
      },
      img: {
        props: {
          style: {
            width: "100%",
            // left: "50%",
            // marginLeft: "-47vw",
            // marginRight: "-47vw",
            // maxWidth: "94vw",
            // position: "relative",
            // right: "50%",
            // width: "94vw"
          },
        },
      },
      table: {
        props: {
          style: { padding: "0.5rem 0rem 0.5rem 0rem" },
        },
      },
    },
  };
  return (
    <div style={{ display: "flex", justifyContent: "center" }}>
      <div style={{ margin: "64px", maxWidth: "680px", width: "100%" }}>
        <Typography variant="h3" style={{ fontWeight: "bold" }}>
          {meta.title}
        </Typography>
        <Typography variant="subtitle1" gutterBottom>
          {meta.description}
        </Typography>
        <div></div>
        <Markdown options={options}>{content}</Markdown>
      </div>
    </div>
  );
};

export default Post;
<file_sep>/components/login/LoginFormInfo.js
import React, { useState, useEffect, useContext } from "react";
// import { useHistory } from "react-router-dom";
import { makeStyles } from "@material-ui/core/styles";
// import { useDispatch } from "react-redux";
import TextField from "@material-ui/core/TextField";
import Button from "@material-ui/core/Button";
// import { setVerified, setId } from "../redux/userSlice";
import Loading from "../Loading";
// import { setCookie } from "../cookieHelper";
//import { CONCAT_SERVER_URL } from "../utils";
//import { tidyUpData } from "../component/Data";
import axio from "../../contexts/axios";
import Router from "next/router";
import AuthContext from "../../contexts/AuthContext";
const config = {
  headers: {
    "content-type": "application/json",
    "Access-Control-Allow-Origin": "*",
  },
};

const useStyles = makeStyles((theme) => ({
  root: {
    '"& > *"': {
      margin: theme.spacing(1),
      width: '"25ch"',
    },
  },
  centerMargin: {
    margin: "0px auto 0px",
  },
  controlSpace: {
    marginTop: "10px",
    width: "300px",
    [`@media (max-width: 400px)`]: {
      width: "80%",
    },
  },
  controlButton: {
    marginTop: "30px",
    width: "200px",
    [`@media (max-width: 400px)`]: {
      width: "170px",
    },
  },
  visitorHref: {
    display: "block",
    marginTop: "80px",
    color: "gray",
  },
}));

export default function LoginFormInfo() {
  const classes = useStyles();
  const {setUserInfo, setModalShow} = useContext(AuthContext);
  const [info, setInfo] = useState({
    email: "",
    password: "",
  });

  const [state, setState] = useState({
    isError: false,
    nowLoading: false,
    errorMes: ["", ""],
  });

  const handleChangeEmail = (e) => {
    setInfo({
      ...info,
      email: e.target.value,
    });
  };

  const handleChangePassword = (e) => {
    setInfo({
      ...info,
      password: e.target.value,
    });
  };

  const handleSubmit = async () => {
    setState({
      isError: state.isError,
      nowLoading: true,
      errorMes: state.errorMes,
    });
    const acc = info.email
    const pwd = info.password
    const {err, res} = await axio.post('/api/login', {acc, pwd})
    if (err) {
      setState({
        isError: true,
        nowLoading: false,
        errorMes: ["", res.data],
      });
    } else {
      setUserInfo({acc, pwd, JWT:res.data})
      setState({ isError: false, nowLoading: true, errorMes: "" });
      setModalShow(false)
    }
  };

  const handleSearch = (e) => {
    if (e.key === "Enter") {
      handleSubmit();
    }
  };

  return (
    <div className={classes.centerMargin}>
      <form className={classes.root} noValidate autoComplete="off">
        <TextField
          value={info.email}
          label="帳號"
          variant="outlined"
          required
          error={state.isError}
          helperText={state.errorMes[0]}
          placeholder="enter your email"
          color="primary"
          className={classes.controlSpace}
          InputProps={{ style: { borderRadius: "50px" } }}
          onChange={handleChangeEmail}
        />

        <TextField
          type="password"
          value={info.password}
          label="密碼"
          variant="outlined"
          required
          error={state.isError}
          helperText={state.errorMes[1]}
          placeholder="enter your password"
          color="primary"
          className={classes.controlSpace}
          InputProps={{ style: { borderRadius: "50px" } }}
          onChange={handleChangePassword}
          onKeyUp={handleSearch}
        />

        {state.nowLoading ? (
          <Loading />
        ) : (
          <Button
            variant="contained"
            component="span"
            className={classes.controlButton}
            onClick={handleSubmit}
          >
            登入
          </Button>
        )}
      </form>
    </div>
  );
}
<file_sep>/pages/todolist.js
import styles from "../styles/Todolist.module.css";
import { useEffect, useState } from "react";
import EditableTable from "../components/todolist/EditableTable";
import { Button } from "antd";

import { useContext } from "react";
import AuthContext from '../contexts/AuthContext'
const TodoList = () => {
  const {userInfo} = useContext(AuthContext)
  if(!userInfo){
    return (<h1>please login first</h1>)
  }
  return (
    <>
      <div className={styles.Todo}>
        <div className={styles.TodoTable}>
          <EditableTable/>
        </div>
      </div>
    </>
  );
};

export default TodoList;
<file_sep>/contexts/axios.js
import axios from "axios";
import { useContext } from "react";
import AuthContext from "./AuthContext";
const instance = axios.create({
  baseURL: process.env.SERVER_URL,
  //timeout: 1000,
  headers: { "Content-Type": "application/json" },
});
//export default instance;
const myaxios = {
  get: async (...args) => {
    try {
      return { err: false, res: await instance.get(...args) };
    } catch (err) {
      return { err: true, res: err };
    }
  },
  post: async (...args) => {
    try {
      return { err: false, res: await instance.post(...args) };
    } catch (err) {
      return { err: true, res: err };
    }
  },
};
export default myaxios;
<file_sep>/components/CustomModal.js
import React from "react";
import Modal from "react-bootstrap/Modal";
import AuthContext from "../contexts/AuthContext";
import { useContext, useState } from "react";
export default function CustomModal({ jumpFrame, children }) {
  const { setModalShow, modalShow } = useContext(AuthContext);
  return (
    <Modal
      show={modalShow}
      onHide={() => {
        setModalShow(false);
      }}
      size="lg"
      aria-labelledby="contained-modal-title-vcenter"
      centered
      backdrop="static"
      dialogClassName={jumpFrame}
    >
      {children}
    </Modal>
  );
}
