diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..a6d42278 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# 디폴트 무시된 파일 +/shelf/ +/workspace.xml +# 에디터 기반 HTTP 클라이언트 요청 +/httpRequests/ diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..03d9549e --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/jsLinters/eslint.xml b/.idea/jsLinters/eslint.xml new file mode 100644 index 00000000..66d9454f --- /dev/null +++ b/.idea/jsLinters/eslint.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..90a5036a --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/prettier.xml b/.idea/prettier.xml new file mode 100644 index 00000000..5968543a --- /dev/null +++ b/.idea/prettier.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/wiki.iml b/.idea/wiki.iml new file mode 100644 index 00000000..24643cc3 --- /dev/null +++ b/.idea/wiki.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index cfacdb0b..d15f7f80 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "wiki", "version": "0.1.0", "dependencies": { + "@ant-design/icons": "^5.2.6", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -20,9 +21,11 @@ "antd": "^5.9.0", "dotenv": "^16.3.1", "firebase": "^10.3.1", + "framer-motion": "^10.16.4", "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", "react-dom": "^18.2.0", + "react-modal": "^3.16.1", "react-router-dom": "^6.15.0", "react-scripts": "5.0.1", "recoil": "^0.7.7", @@ -33,6 +36,7 @@ "devDependencies": { "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@types/react-beautiful-dnd": "^13.1.4", + "@types/styled-components": "^5.1.27", "eslint": "^8.49.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", @@ -5122,6 +5126,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-modal": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@types/react-modal/-/react-modal-3.16.0.tgz", + "integrity": "sha512-iphdqXAyUfByLbxJn5j6d+yh93dbMgshqGP0IuBeaKbZXx0aO+OXsvEkt6QctRdxjeM9/bR+Gp3h9F9djVWTQQ==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-redux": { "version": "7.1.26", "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.26.tgz", @@ -5196,6 +5209,17 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" }, + "node_modules/@types/styled-components": { + "version": "5.1.27", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.27.tgz", + "integrity": "sha512-oY9c1SdztRRF0QDQdwXEenfAjGN4WGUkaMpx5hvdTbYYqw01qoY2GrHi+kAR6SVofynzD6KbGoF5ITP0zh5pvg==", + "dev": true, + "dependencies": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, "node_modules/@types/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz", @@ -9148,6 +9172,11 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/exenv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", + "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==" + }, "node_modules/exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -9707,6 +9736,44 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "10.16.4", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.16.4.tgz", + "integrity": "sha512-p9V9nGomS3m6/CALXqv6nFGMuFOxbWsmaOrdmhyQimMIlLl3LC7h7l86wge/Js/8cRu5ktutS/zlzgR7eBOtFA==", + "dependencies": { + "tslib": "^2.4.0" + }, + "optionalDependencies": { + "@emotion/is-prop-valid": "^0.8.2" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/framer-motion/node_modules/@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "optional": true, + "dependencies": { + "@emotion/memoize": "0.7.4" + } + }, + "node_modules/framer-motion/node_modules/@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "optional": true + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -16730,6 +16797,29 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "node_modules/react-modal": { + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.16.1.tgz", + "integrity": "sha512-VStHgI3BVcGo7OXczvnJN7yT2TWHJPDXZWyI/a0ssFNhGZWsPmB8cF0z33ewDXq4VfYMO1vXgiv/g8Nj9NDyWg==", + "dependencies": { + "exenv": "^1.2.0", + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.0", + "warning": "^4.0.3" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18", + "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18" + } + }, "node_modules/react-redux": { "version": "7.2.9", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", @@ -19181,6 +19271,14 @@ "makeerror": "1.0.12" } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", diff --git a/package.json b/package.json index 53c9657a..d7f5ac07 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@ant-design/icons": "^5.2.6", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -15,9 +16,11 @@ "antd": "^5.9.0", "dotenv": "^16.3.1", "firebase": "^10.3.1", + "framer-motion": "^10.16.4", "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", "react-dom": "^18.2.0", + "react-modal": "^3.16.1", "react-router-dom": "^6.15.0", "react-scripts": "5.0.1", "recoil": "^0.7.7", @@ -52,6 +55,7 @@ "devDependencies": { "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@types/react-beautiful-dnd": "^13.1.4", + "@types/styled-components": "^5.1.27", "eslint": "^8.49.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", diff --git a/src/components/Employee/CardAddMember.tsx b/src/components/Employee/CardAddMember.tsx new file mode 100644 index 00000000..4b9cfd2a --- /dev/null +++ b/src/components/Employee/CardAddMember.tsx @@ -0,0 +1,28 @@ +import { PlusOutlined } from "@ant-design/icons"; +import React from "react"; +import { Button, Dropdown } from "antd"; +import type { MenuProps } from "antd"; + +const items: MenuProps["items"] = [ + { + label: "Add Member", + key: "addMember", + }, + { + label: "Add Team", + key: "addTeam", + }, +]; + +export default function TableAddMember() { + return ( + + + ); +} diff --git a/src/components/Employee/CardFilter.tsx b/src/components/Employee/CardFilter.tsx new file mode 100644 index 00000000..01cf7356 --- /dev/null +++ b/src/components/Employee/CardFilter.tsx @@ -0,0 +1,59 @@ +import { FilterFilled } from "@ant-design/icons"; +import React from "react"; +import { Button, Dropdown } from "antd"; +import type { MenuProps } from "antd"; + +const items: MenuProps["items"] = [ + { + type: "group", + label: "SORT BY:", + children: [ + { + label: "default", + key: "sortDefault", + }, + { + label: "Name", + key: "sortName", + }, + { + label: "Team", + key: "sortTeam", + }, + ], + }, + { + type: "group", + label: "MEMBERS:", + children: [ + { + label: "All", + key: "membersAll", + }, + { + label: "Manager", + key: "membersManager", + }, + { + label: "Member", + key: "membersMember", + }, + ], + }, +]; + +export default function TableFilter() { + return ( + + + + ); +} diff --git a/src/components/Employee/CardHeading.tsx b/src/components/Employee/CardHeading.tsx new file mode 100644 index 00000000..afedbcba --- /dev/null +++ b/src/components/Employee/CardHeading.tsx @@ -0,0 +1,45 @@ +import React from "react"; +import { Button, Space } from "antd"; +import styled from "styled-components"; +import TableFilter from "./CardFilter"; +import TableSearch from "./CardSearch"; +import TableAddMember from "./CardAddMember"; +import TableExportBtn from "./CardExportBtn"; + +const CardHeader = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + position: relative; + padding: 1.5rem; +`; + +const ToggleWrap = styled.div` + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + align-items: center; +`; + +export default function CardHeading() { + return ( + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/components/Employee/CardSearch.tsx b/src/components/Employee/CardSearch.tsx new file mode 100644 index 00000000..7786882d --- /dev/null +++ b/src/components/Employee/CardSearch.tsx @@ -0,0 +1,16 @@ +import React from "react"; + +import { Input } from "antd"; + +const { Search } = Input; + +export default function TableSearch() { + return ( + + ); +} diff --git a/src/components/Employee/CardTable.tsx b/src/components/Employee/CardTable.tsx new file mode 100644 index 00000000..46546a6f --- /dev/null +++ b/src/components/Employee/CardTable.tsx @@ -0,0 +1,186 @@ +import { EllipsisOutlined } from "@ant-design/icons"; +import React, { useState } from "react"; +import { Col, Row, Table, Dropdown } from "antd"; +import type { ColumnsType } from "antd/es/table"; +import type { MenuProps } from "antd"; + +import styled from "styled-components"; + +interface DataType { + key: React.Key; + name: string; + department: string; + position: string; + phone: string; + address: string; + team: string; + profile: string; + accessLevel: string; +} + +const Image = styled.span` + display: block; + width: 50px; + height: 50px; + border-radius: 50%; + overflow: hidden; + & > img { + width: 100%; + } +`; +const Btn = styled.a` + display: block; + padding: 0.3rem; +`; + +const items: MenuProps["items"] = [ + { + key: "1", + label: ( + + Edit + + ), + }, + { + key: "2", + label: ( + + View Profile + + ), + }, + { + key: "3", + label: ( + + Delete + + ), + danger: true, + }, +]; + +export default function CardTable() { + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + + const columns: ColumnsType = [ + { + title: "Name", + render: (record) => ( + + + {" "} + + {record.name} + {" "} + + + {" "} +
{record.name}
+
{record.address}
+ +
+ ), + }, + { + title: "Department", + render: (record) => ( + + +
{record.department}
+
{record.position}
+ +
+ ), + }, + { + title: "Team", + dataIndex: "team", + }, + { + title: "Phone", + dataIndex: "phone", + }, + { + title: "AccessLevel", + dataIndex: "accessLevel", + }, + { + title: , + dataIndex: "", + render: () => ( + + + e.preventDefault()}> + + + + + ), + }, + ]; + + const dataSource: DataType[] = [ + { + key: "1", + name: "박나영", + department: "Information Technology", + position: "Front-end Developer", + phone: "010-5555-5555", + address: "myemail@example.com", + team: "이정도면 껌이조", + profile: + "https://deploy-preview-66--effulgent-axolotl-ab38e8.netlify.app/asset/member1.png", + accessLevel: "Member", + }, + { + key: "2", + name: "김땡땡", + department: "Information Technology", + position: "Back-end Developer", + phone: "010-5555-5555", + address: "myemail@example.com", + team: "가보자고", + profile: + "https://deploy-preview-66--effulgent-axolotl-ab38e8.netlify.app/asset/member2.png", + accessLevel: "Member", + }, + { + key: "3", + name: "이땡땡", + department: "Information Technology", + position: "Front-end Developer", + phone: "010-5555-5555", + address: "myemail@example.com", + team: "가보자고", + profile: + "https://deploy-preview-66--effulgent-axolotl-ab38e8.netlify.app/asset/member3.png", + accessLevel: "Manager", + }, + ]; + const rowSelection = { + onChange: (selectedRowKeys: React.Key[], selectedRows: DataType[]) => { + console.log( + `selectedRowKeys: ${selectedRowKeys}`, + "selectedRows: ", + selectedRows, + ); + }, + }; + + return ( + <> + + + ); +} diff --git a/src/components/SignUp/Navigation.tsx b/src/components/SignUp/Navigation.tsx new file mode 100644 index 00000000..fc6dd22e --- /dev/null +++ b/src/components/SignUp/Navigation.tsx @@ -0,0 +1,32 @@ +import { useNavigate } from "react-router-dom"; + +export const useNavigation = () => { + const navigate = useNavigate(); + + const moveUserRegister = () => { + navigate("/user-register"); + }; + + const moveStartRegister = () => { + navigate("/start-register"); + }; + + const moveEndRegister = () => { + navigate("/end-register"); + }; + + const moveMain = () => { + navigate("/"); + }; + + const moveMyTeam = () => { + navigate("/wiki"); + }; + return { + moveUserRegister, + moveStartRegister, + moveEndRegister, + moveMain, + moveMyTeam, + }; +}; diff --git a/src/components/SignUp/Pagination.tsx b/src/components/SignUp/Pagination.tsx new file mode 100644 index 00000000..ae9e5d18 --- /dev/null +++ b/src/components/SignUp/Pagination.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import { styled } from "styled-components"; + +export const SlideCounter = styled.div` + display: flex; + justify-content: center; + margin-top: 20px; + gap: 10px; +`; +export const ActiveDot = styled.div` + background-color: #5b617c; + width: 10px; + height: 10px; + border-radius: 50px; +`; +export const Dot = styled.div` + background-color: #5b617c19; + width: 10px; + height: 10px; + border-radius: 50px; +`; diff --git a/src/components/SignUp/Register/EndRegister.tsx b/src/components/SignUp/Register/EndRegister.tsx new file mode 100644 index 00000000..a7081b75 --- /dev/null +++ b/src/components/SignUp/Register/EndRegister.tsx @@ -0,0 +1,68 @@ +import React from "react"; +import { styled } from "styled-components"; +import { ActiveDot, Dot, SlideCounter } from "../Pagination"; +import { useNavigation } from "../Navigation"; +import { EndSubTitle, EndTitle } from "../Title"; +import { motion } from "framer-motion"; +const Container = styled.div` + margin: 0; + padding: 0; + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; +`; +const EndContainer = styled.div` + width: 50%; + margin: 100px auto; + text-align: center; +`; +const EndBtnContainer = styled.div` + display: flex; + justify-content: center; + margin-top: 50px; + gap: 20px; +`; +const EndBtn = styled.button` + width: 200px; + height: 50px; + border-radius: 10px; + text-align: center; + font-size: 16px; + font-weight: bold; + cursor: pointer; + &:hover { + color: #fff; + background-color: #000; + } +`; +export default function EndRegister() { + const { moveMain, moveMyTeam } = useNavigation(); + return ( + + + + + 이제 Wiki를 통해 +
+ 일의 능률을 향상시켜보세요! +
+ 아래 버튼을 누르시면 이동됩니다! + + Home + My Team + +
+ + + + + +
+
+ ); +} diff --git a/src/components/SignUp/Register/StartRegister.tsx b/src/components/SignUp/Register/StartRegister.tsx new file mode 100644 index 00000000..1abd132b --- /dev/null +++ b/src/components/SignUp/Register/StartRegister.tsx @@ -0,0 +1,56 @@ +import React from "react"; +import { styled } from "styled-components"; +import { ActiveDot, Dot, SlideCounter } from "../Pagination"; +import { useNavigation } from "../Navigation"; +import { StartSubTitle, StartTitle } from "../Title"; +import { motion } from "framer-motion"; +const Container = styled.div` + margin: 0; + padding: 0; + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; +`; +const StartContainer = styled.div` + width: 50%; + margin: 100px auto; + text-align: center; +`; +const StartBtn = styled.button` + width: 400px; + height: 50px; + margin: 50px auto; + border-radius: 10px; + text-align: center; + font-size: 16px; + font-weight: bold; + cursor: pointer; + &:hover { + color: #fff; + background-color: #000; + } +`; +export default function StartRegister() { + const { moveUserRegister } = useNavigation(); + return ( + + + + Wiki에 오신 것을 환영합니다! + 아래 버튼을 통해 정보를 입력해주세요! + 시작하기 + + + + + + + + + ); +} diff --git a/src/components/SignUp/Register/UserRegister.tsx b/src/components/SignUp/Register/UserRegister.tsx new file mode 100644 index 00000000..ab9d1e00 --- /dev/null +++ b/src/components/SignUp/Register/UserRegister.tsx @@ -0,0 +1,171 @@ +import React, { useState } from "react"; +import { styled } from "styled-components"; +import { PlusOutlined } from "@ant-design/icons"; +import { Button, Input, Select, Upload, message } from "antd"; +import { UploadOutlined, ArrowLeftOutlined } from "@ant-design/icons"; +import type { UploadProps } from "antd"; +import { MainTitle } from "../Title"; +import { SlideCounter, Dot, ActiveDot } from "../Pagination"; +import { useNavigation } from "../Navigation"; +import { motion } from "framer-motion"; + +const props: UploadProps = { + name: "file", + action: "https://www.mocky.io/v2/5cc8019d300000980a055e76", + headers: { + authorization: "authorization-text", + }, + onChange(info) { + if (info.file.status !== "uploading") { + console.log(info.file, info.fileList); + } + if (info.file.status === "done") { + message.success(`${info.file.name} file uploaded successfully`); + } else if (info.file.status === "error") { + message.error(`${info.file.name} file upload failed.`); + } + }, +}; + +const Container = styled.div` + margin: 0; + padding: 0; + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; +`; +const UserInfoContainer = styled.div` + border: 1px solid black; + margin: 20px auto; + display: flex; + flex-direction: column; + justify-content: center; + text-align: start; + span { + font-weight: 500; + } +`; +const UserNameCategory = styled.div` + margin: 20px 20px; + display: flex; + flex-direction: column; +`; +const UserCompanyCategory = styled(UserNameCategory)``; +const UserRankCategory = styled(UserNameCategory)``; +const UserImageCategory = styled(UserNameCategory)``; +const BtnContainer = styled.div` + display: flex; + justify-content: center; + gap: 10px; + margin: 20px; + height: 30px; +`; +const BackBtn = styled.button` + border: 1px solid black; + width: 60px; + font-size: 16px; + cursor: pointer; + transition: transform 0.3s ease-in-out; + &:hover { + background-color: #000; + color: #fff; + transform: translateX(-5px); + } +`; +const SubmitBtn = styled.button` + border: 1px solid black; + width: 240px; + font-size: 16px; + cursor: pointer; + &:hover { + background-color: #000; + color: #fff; + } +`; +export default function UserRegister() { + const { moveStartRegister, moveEndRegister } = useNavigation(); + return ( + + + 회원님의 정보를 입력해주세요! + + + 이름 + + + + 소속 팀 + + + + 프로필 사진 + + + + + + + + + 완료 + + + + + + + + + + ); +} diff --git a/src/components/SignUp/SignUpEmail.tsx b/src/components/SignUp/SignUpEmail.tsx new file mode 100644 index 00000000..330b689b --- /dev/null +++ b/src/components/SignUp/SignUpEmail.tsx @@ -0,0 +1,113 @@ +import React, { useState } from "react"; +import { Button, Modal, Checkbox, Form, Input } from "antd"; +import { styled } from "styled-components"; +import { auth } from "../../libs/firebase"; +import { signInWithEmailAndPassword } from "firebase/auth"; +import { createUserWithEmailAndPassword } from "firebase/auth"; + +interface FieldType { + userEmail?: string; + password?: string; + remember?: string; +} +const Container = styled.div` + margin: 0; + padding: 0; +`; +const ModalContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + border-radius: 20px; + text-align: start; +`; +const ModalTitle = styled.p` + font-size: 20px; + margin-top: 50px; + margin-bottom: 0; + text-align: center; +`; +const onFinish = (values: any) => { + console.log("Success: ", values); +}; +const onFinishFailed = (errorInfo: any) => { + console.log("Failed: ", errorInfo); +}; + +const SignUpEmailModal = () => { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + + const handleEmailChange = (e: React.ChangeEvent) => { + setEmail(e.target.value); + }; + + const handlePasswordChange = (e: React.ChangeEvent) => { + setPassword(e.target.value); + }; + + const handleSignUp = async () => { + try { + const userCredential = await createUserWithEmailAndPassword( + auth, + email, + password, + ); + const user = userCredential.user; + console.log("로그인 성공:", user); + alert("회원가입 완료되었습니다!"); + } catch (error) { + console.error("로그인 실패:", error); + } + }; + return ( + + + Wiki에서 사용하실 이메일을 적어주세요! +
+ + label="이메일주소" + name="userEmail" + rules={[ + { + required: true, + message: "사용하실 이메일을 입력해주세요!", + }, + ]} + > + + + + + label="비밀번호" + name="password" + rules={[{ required: true, message: "비밀번호를 입력해주세요!" }]} + > + + + + name="remember" + valuePropName="checked" + wrapperCol={{ offset: 8, span: 16 }} + > + + + + + +
+
+ ); +}; +export default SignUpEmailModal; diff --git a/src/components/SignUp/Title.tsx b/src/components/SignUp/Title.tsx new file mode 100644 index 00000000..33e2461d --- /dev/null +++ b/src/components/SignUp/Title.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import { styled } from "styled-components"; + +export const MainTitle = styled.p` + font-size: 20px; + font-weight: 500; + text-align: start; + margin: 0 auto; +`; + +export const StartTitle = styled.p` + margin-top: 30px; + font-size: 32px; + font-weight: bold; + text-align: center; +`; +export const StartSubTitle = styled.p` + font-size: 20px; + color: #d9d9d9; + text-align: center; +`; +export const EndTitle = styled(StartTitle)``; +export const EndSubTitle = styled(StartSubTitle)``; + +export const ModalTitle = styled.p` + font-size: 20px; + margin-top: 50px; + margin-bottom: 0; + text-align: center; +`; diff --git a/src/components/Timer/TimerApp.tsx b/src/components/Timer/TimerApp.tsx new file mode 100644 index 00000000..2f89cabf --- /dev/null +++ b/src/components/Timer/TimerApp.tsx @@ -0,0 +1,219 @@ +import React from "react"; +import { useState, useEffect } from "react"; +import { styled } from "styled-components"; +import { Button, Alert } from "antd"; +import { + ClockCircleOutlined, + CheckOutlined, + PoweroffOutlined, +} from "@ant-design/icons"; + +// 타이머 스타일링 +interface TimerProps { + fontSize?: string; +} +const TimerText = styled.div` + font-size: ${(props) => props.fontSize || "1.5rem"}; +`; + +const TimerAlign = styled.div` + style={ + display: "flex", + flexDirection: "column", + justifyContent: "right", + alignItems: "center"}`; + +const GreetingText = styled.div` + font-size: "1.5rem"; +`; + +const TimerApp = () => { + const nowDate = new Date().toLocaleDateString("ko-KR", { + year: "2-digit", + month: "2-digit", + day: "2-digit", + weekday: "narrow", + }); + + const [nowTime, setNowTime] = useState( + new Date().toLocaleTimeString(), + ); // 현재 시간 표시 + const [startWorkTime, setStartWorkTime] = useState(null); // 출근 시간 기록 + const [finishWorkTime, setFinishWorkTime] = useState(null); // 퇴근 시간 기록 + const [startWorkBtnClicked, setStartWorkBtnClicked] = + useState(false); // 출근 버튼 클릭 가능 상태로 시작 + const [finishWorkBtnClicked, setFinishWorkBtnClicked] = + useState(false); // 퇴근 버튼 클릭 가능 상태로 시작 + const [clickedStartBtnText, setClickedStartBtnText] = useState(""); // 출근 버튼이 클릭됐을 때 해당 시각을 버튼에 표시 + const [clickedFinishBtnText, setClickedFinishBtnText] = useState(""); // 퇴근 버튼이 클릭됐을 때 해당 시각을 버튼에 표시 + const [userName, setUserName] = useState(""); + + const UpdateTime = () => { + let nowTime = new Date().toLocaleTimeString(); + setNowTime(nowTime); + }; + + useEffect(() => { + const interval = setInterval(UpdateTime, 1000); + + return () => { + clearInterval(interval); + }; + }, []); + + const recordStartWork = (e: React.MouseEvent) => { + e.preventDefault(); + // 현재 시간을 출근 시간으로 기록 + const startWorkTime = new Date(); + const hours = startWorkTime.getHours().toString().padStart(2, "0"); + const minutes = startWorkTime.getMinutes().toString().padStart(2, "0"); + const seconds = startWorkTime.getSeconds().toString().padStart(2, "0"); + setStartWorkTime(`${hours}:${minutes}:${seconds}`); + setStartWorkBtnClicked(true); // 출근 시간 기록 후 버튼 비활성화 + setClickedStartBtnText(nowTime); + }; + + const recordFinishWork = (e: React.MouseEvent) => { + e.preventDefault(); + // 현재 시간을 퇴근 시간으로 기록 + + if (!startWorkBtnClicked) { + return alert("출근한 상태일 때만 퇴근 기록이 가능합니다!"); + } + + const finishWorkTime = new Date(); + const hours = finishWorkTime.getHours().toString().padStart(2, "0"); + const minutes = finishWorkTime.getMinutes().toString().padStart(2, "0"); + const seconds = finishWorkTime.getSeconds().toString().padStart(2, "0"); + setFinishWorkTime(`${hours}:${minutes}:${seconds}`); + setFinishWorkBtnClicked(true); // 퇴근 시간 기록 후 버튼 비활성화 + setClickedFinishBtnText(nowTime); + }; + + const calcWorkTime = () => { + if (startWorkTime && finishWorkTime) { + const startTime = startWorkTime.split(":"); + const finishTime = finishWorkTime.split(":"); + const startHours = parseInt(startTime[0], 10); + const startMinutes = parseInt(startTime[1], 10); + const finishHours = parseInt(finishTime[0], 10); + const finishMinutes = parseInt(finishTime[1], 10); + + let hours = finishHours - startHours; + let minutes = finishMinutes - startMinutes; + + if (minutes < 0) { + hours -= 1; + minutes += 60; + } + + return `오늘 총 근무 시간은 ${hours}시간 ${minutes}분 입니다.`; + } + }; + + return ( +
+ +
+
+ TODAY {nowDate} +
+
+ + +   + {nowTime} + +
+
+
+
+
+ +  |  + +
+ {startWorkBtnClicked && !finishWorkBtnClicked && ( + +
좋은 하루 보내세요😊
+
+ )} + {startWorkBtnClicked && finishWorkBtnClicked && ( + +
오늘도 수고하셨습니다!👍
+
+ )} + {startWorkTime && finishWorkTime &&
{calcWorkTime()}
} + + ); +}; + +export default TimerApp; diff --git a/src/components/signin/SignInEmail.tsx b/src/components/signin/SignInEmail.tsx new file mode 100644 index 00000000..d040db74 --- /dev/null +++ b/src/components/signin/SignInEmail.tsx @@ -0,0 +1,113 @@ +import React, { useState } from "react"; +import { Button, Modal, Checkbox, Form, Input } from "antd"; +import { styled } from "styled-components"; +import { auth } from "../../libs/firebase"; +import { signInWithEmailAndPassword } from "firebase/auth"; +import { createUserWithEmailAndPassword } from "firebase/auth"; + +interface FieldType { + userEmail?: string; + password?: string; + remember?: string; +} +const Container = styled.div` + margin: 0; + padding: 0; +`; +const ModalContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + border-radius: 20px; + text-align: start; +`; +const ModalTitle = styled.p` + font-size: 20px; + margin-top: 50px; + margin-bottom: 0; + text-align: center; +`; +const onFinish = (values: any) => { + console.log("Success: ", values); +}; +const onFinishFailed = (errorInfo: any) => { + console.log("Failed: ", errorInfo); +}; + +const SignInEmailModal = () => { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + + const handleEmailChange = (e: React.ChangeEvent) => { + setEmail(e.target.value); + }; + + const handlePasswordChange = (e: React.ChangeEvent) => { + setPassword(e.target.value); + }; + + const handleSignIn = async () => { + try { + const userCredential = await signInWithEmailAndPassword( + auth, + email, + password, + ); + const user = userCredential.user; + console.log("로그인 성공:", user); + } catch (error) { + alert("회원가입부터 진행해주세요"); + console.error("로그인 실패:", error); + } + }; + return ( + + + Wiki에서 사용하고 있는 이메일을 적어주세요! +
+ + label="이메일주소" + name="userEmail" + rules={[ + { + required: true, + message: "사용하고 계신 이메일을 입력해주세요!", + }, + ]} + > + + + + + label="비밀번호" + name="password" + rules={[{ required: true, message: "비밀번호를 입력해주세요!" }]} + > + + + + name="remember" + valuePropName="checked" + wrapperCol={{ offset: 8, span: 16 }} + > + + + + + +
+
+ ); +}; +export default SignInEmailModal; diff --git a/src/components/signin/SignInGoogle.tsx b/src/components/signin/SignInGoogle.tsx new file mode 100644 index 00000000..53c3082c --- /dev/null +++ b/src/components/signin/SignInGoogle.tsx @@ -0,0 +1,11 @@ +import { auth } from "../../libs/firebase"; +import { signInWithPopup, GoogleAuthProvider } from "firebase/auth"; + +const provider = new GoogleAuthProvider(); + +const signInGoogle = async () => { + const { user } = await signInWithPopup(auth, provider); + console.log(user); +}; + +export default signInGoogle; diff --git a/src/libs/firebase.ts b/src/libs/firebase.ts index 615b9227..20508c5b 100644 --- a/src/libs/firebase.ts +++ b/src/libs/firebase.ts @@ -1,7 +1,7 @@ // Import the functions you need from the SDKs you need import { initializeApp } from "firebase/app"; import { getFirestore } from "firebase/firestore"; - +import { getAuth } from "firebase/auth"; // Your web app's Firebase configuration const firebaseConfig = { apiKey: process.env.REACT_APP_API_KEY, @@ -16,3 +16,4 @@ const firebaseConfig = { const app = initializeApp(firebaseConfig); // Initialize Cloud Firestore and get a reference to the service export const db = getFirestore(app); +export const auth = getAuth(app); diff --git a/src/pages/Employee.tsx b/src/pages/Employee.tsx index 71d0e905..f2b33125 100644 --- a/src/pages/Employee.tsx +++ b/src/pages/Employee.tsx @@ -1,7 +1,60 @@ import React from "react"; +import styled from "styled-components"; + +import CardTable from "../components/Employee/CardTable"; +import CardHeading from "../components/Employee/CardHeading"; + +export const Container = styled.div` + max-width: 100%; + margin: 0 auto; + padding: 0 1rem; + + @media (min-width: 576px) { + max-width: 576px; + } + + @media (min-width: 768px) { + max-width: 768px; + } + + @media (min-width: 992px) { + max-width: 992px; + } + + @media (min-width: 1200px) { + max-width: 1200px; + } +`; +const Header = styled.div` + font-size: 1.5rem; + font-weight: bold; +`; + +const CarContainer = styled.div` + background-color: #fff; + border-radius: 8px; + color: #526484; + box-shadow: rgba(99, 99, 99, 0.2) 0px 0px 5px 0px; + word-wrap: break-word; +`; const Employee = () => { - return
Employee
; + return ( + +
+

임직원

+
+ +
+ +
+
+ +
+
+
+
+ ); }; export default Employee; diff --git a/src/pages/SignIn.tsx b/src/pages/SignIn.tsx index e12af16f..d6654575 100644 --- a/src/pages/SignIn.tsx +++ b/src/pages/SignIn.tsx @@ -1,7 +1,121 @@ -import React from "react"; +import { GoogleOutlined, MailOutlined } from "@ant-design/icons"; +import React, { useState } from "react"; +import { styled } from "styled-components"; +import SignInEmailModal from "../components/SignIn/SignInEmail"; +import { Modal } from "antd"; +import signInGoogle from "../components/SignIn/SignInGoogle"; +import { Link } from "react-router-dom"; +import { MainTitle } from "../components/SignUp/Title"; +const Container = styled.div` + margin: 0; + padding: 0; + width: 100vw; +`; +const SignInContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + border: 1px solid black; + border-radius: 20px; + width: 50vw; + margin: 100px auto; + text-align: center; +`; +const Logo = styled.div` + border: 1px solid black; + border-radius: 20px; + width: 150px; + height: 50px; + line-height: 50px; + margin: 60px auto; + text-align: center; +`; +const LoginBtnContainer = styled.div` + margin-top: 50px; + display: flex; + flex-direction: column; + text-align: center; +`; +const GoogleLogin = styled.button` + border: 1px solid black; + border-radius: 10px; + width: 330px; + height: 40px; + text-align: center; + display: flex; + align-items: center; + padding-left: 5px; + margin: 30px auto; + cursor: pointer; + &:hover { + background-color: grey; + } + span { + padding-left: 35px; + } +`; +const EmailLogin = styled(GoogleLogin)` + margin-bottom: 20px; +`; +const IconContainer = styled.div` + margin-right: 20px; +`; +const MoveSingUp = styled(Link)` + text-align: center; + margin-bottom: 20px; + font-size: 12px; + font-weight: bold; + color: #1c49ff; + span { + color: #909090; + font-weight: normal; + } +`; const SignIn = () => { - return
SignIn
; + const [isEmailModalOpen, setEmailModalOpen] = useState(false); + const showModal = () => { + setEmailModalOpen(true); + }; + const handleCancel = () => { + setEmailModalOpen(false); + }; + const handleOk = () => { + setEmailModalOpen(false); + }; + return ( + + + LOGO + + + Wiki에 오신 것을 환영합니다! +
+ 시작하시기 전에 로그인을 해주세요! +
+ + + + + Google로 로그인 + + OR + + + + + 직접 이메일 입력 + +
+ + 아직 계정이 없으신가요? 회원가입하기 + +
+ + + +
+ ); }; export default SignIn; diff --git a/src/pages/SignUp.tsx b/src/pages/SignUp.tsx index b466e403..40e5143d 100644 --- a/src/pages/SignUp.tsx +++ b/src/pages/SignUp.tsx @@ -1,7 +1,121 @@ -import React from "react"; +import { GoogleOutlined, MailOutlined } from "@ant-design/icons"; +import React, { useState } from "react"; +import { styled } from "styled-components"; +import { Modal } from "antd"; +import signInGoogle from "../components/SignIn/SignInGoogle"; +import { Link } from "react-router-dom"; +import SignUpEmailModal from "../components/SignUp/SignUpEmail"; +import { MainTitle } from "../components/SignUp/Title"; +const Container = styled.div` + margin: 0; + padding: 0; + width: 100vw; +`; +const SignUpContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + border: 1px solid black; + border-radius: 20px; + width: 50vw; + margin: 100px auto; + text-align: center; +`; +const Logo = styled.div` + border: 1px solid black; + border-radius: 20px; + width: 150px; + height: 50px; + line-height: 50px; + margin: 60px auto; + text-align: center; +`; +const LoginBtnContainer = styled.div` + margin-top: 50px; + display: flex; + flex-direction: column; + text-align: center; +`; +const GoogleLogin = styled.button` + border: 1px solid black; + border-radius: 10px; + width: 330px; + height: 40px; + text-align: center; + display: flex; + align-items: center; + padding-left: 5px; + margin: 30px auto; + cursor: pointer; + &:hover { + background-color: grey; + } + span { + padding-left: 35px; + } +`; +const EmailLogin = styled(GoogleLogin)` + margin-bottom: 20px; +`; +const IconContainer = styled.div` + margin-right: 20px; +`; +const MoveSingIn = styled(Link)` + text-align: center; + margin-bottom: 20px; + font-size: 12px; + font-weight: bold; + color: #1c49ff; + span { + color: #909090; + font-weight: normal; + } +`; const SignUp = () => { - return
SignUp
; + const [isEmailModalOpen, setEmailModalOpen] = useState(false); + const showModal = () => { + setEmailModalOpen(true); + }; + const handleCancel = () => { + setEmailModalOpen(false); + }; + const handleOk = () => { + setEmailModalOpen(false); + }; + return ( + + + LOGO + + + Wiki에 오신 것을 환영합니다! +
+ 시작하시기 전에 회원가입을 해주세요! +
+ + + + + Google로 가입 + + OR + + + + + 직접 이메일 입력 + +
+ + 이미 계정이 있으신가요? 로그인하기 + +
+ + + +
+ ); }; export default SignUp; diff --git a/src/pages/Timer.tsx b/src/pages/Timer.tsx index 9941ca8d..031ddbca 100644 --- a/src/pages/Timer.tsx +++ b/src/pages/Timer.tsx @@ -1,7 +1,51 @@ -import React from "react"; +import React, { useState } from "react"; +import { styled } from "styled-components"; +import TimerApp from "../components/Timer/TimerApp"; + +const TimerContainer = styled.div` + width: 320px; + height: 260px; + border: 2px solid #3956a3; + border-radius: 40px; + padding: 15px 25px; + box-sizing: border-box; + white-space: pre-line; + display: flex; + justify-content: flex-end; + font-size: 1rem; + z-index: 999; + position: absolute; + top: 10%; + right: 5%; +`; + +const TextAlign = styled.div` + text-align: right; +`; + +const CloseModalBtn = styled.div` + width: 5px; + height: 5px; + cursor: pointer; + color: #535353; +`; +// const [closeModal, setCloseModal] = useState(false); const Timer = () => { - return
Timer
; + // const closeModal = () => { + // setCloseModal(true); + // }; + + return ( + + X + +
+ 환영합니다. OOO 님! + +
+
+ ); }; export default Timer; diff --git a/src/router/Router.tsx b/src/router/Router.tsx index bd5dbc78..a8983fbf 100644 --- a/src/router/Router.tsx +++ b/src/router/Router.tsx @@ -1,6 +1,5 @@ import React from "react"; import { Route, Routes } from "react-router-dom"; -// import Layout from "../layouts/Layout"; import SubLayout from "../layouts/SubLayout"; import Main from "../pages/Main"; import Wiki from "../pages/Wiki"; @@ -12,7 +11,9 @@ import Timer from "../pages/Timer"; import SignIn from "../pages/SignIn"; import SignUp from "../pages/SignUp"; import Employee from "../pages/Employee"; - +import StartRegister from "../components/SignUp/Register/StartRegister"; +import UserRegister from "../components/SignUp/Register/UserRegister"; +import EndRegister from "../components/SignUp/Register/EndRegister"; const Router = () => { return ( @@ -40,6 +41,9 @@ const Router = () => { }> }> }> + }> + }> + }> );