= ({
43 | elemId,
44 | show,
45 | scrollSize = 622,
46 | left = true,
47 | right = true,
48 | }) => {
49 | const scroll = useCallback(
50 | (offset: number) => {
51 | const elem = document.getElementById(elemId);
52 | if (elem) elem.scrollLeft += offset;
53 | },
54 | [elemId],
55 | );
56 |
57 | const leftScrollClick = useCallback(() => scroll(-scrollSize), [scroll, scrollSize]);
58 | const rightScrollClick = useCallback(() => scroll(scrollSize), [scroll, scrollSize]);
59 | return (
60 |
61 | {left &&
}
62 | {right &&
}
63 |
64 | );
65 | };
66 |
67 | export default ScrollBtn;
68 |
--------------------------------------------------------------------------------
/src/components/molecules/ScrollBtn/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./ScrollBtn";
2 |
--------------------------------------------------------------------------------
/src/components/molecules/Select/Select.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import ReactSelect from "react-select";
4 | import ReactAsyncSelect from "react-select/async";
5 |
6 | const Select = ({ async, ...props }) => {
7 | let Component = ReactSelect;
8 | if (async) Component = ReactAsyncSelect;
9 | return ;
10 | };
11 |
12 | Select.propTypes = {
13 | ...ReactSelect.propTypes,
14 | ...ReactAsyncSelect.propTypes,
15 | async: PropTypes.bool,
16 | };
17 |
18 | Select.defaultProps = {
19 | async: false,
20 | };
21 |
22 | export default Select;
23 |
--------------------------------------------------------------------------------
/src/components/molecules/Select/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./Select";
2 |
--------------------------------------------------------------------------------
/src/components/molecules/SimpleSelect/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./SimpleSelect";
2 |
--------------------------------------------------------------------------------
/src/components/molecules/StatBox/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./StatBox";
2 |
--------------------------------------------------------------------------------
/src/components/organisms/AuthCallback/AuthCallback.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useDispatch } from "react-redux";
3 | import queryString from "query-string";
4 | import type { RouteComponentProps } from "react-router-dom";
5 |
6 | import { loginCallback } from "store/reducers/auth";
7 | import storage from "lib/storage";
8 | import type { Action } from "redux-actions";
9 |
10 | // TODO
11 | // This is a temporary fix to avoid type errors
12 | // We should find a way to infer types from redux middlewares
13 | interface Dispatch {
14 | (action: Action
): P;
15 | }
16 |
17 | const AuthCallback: React.FC = ({ location, history }) => {
18 | const dispatch = useDispatch();
19 | useEffect(() => {
20 | const { code, state } = queryString.parse(location.search);
21 | if (typeof code === "string" && typeof state === "string") {
22 | dispatch(loginCallback(code, state))
23 | .then((res) => {
24 | const referrer = storage.getItem("referrer");
25 | if (referrer) {
26 | storage.removeItem("referrer");
27 | history.replace(referrer);
28 | return;
29 | }
30 | history.replace(`/${res.user.username}`);
31 | })
32 | .catch((error) => {
33 | alert(error.message);
34 | history.replace("/");
35 | });
36 | }
37 | }, [dispatch, history, location.search]);
38 | return null;
39 | };
40 |
41 | export default AuthCallback;
42 |
--------------------------------------------------------------------------------
/src/components/organisms/AuthCallback/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./AuthCallback";
2 |
--------------------------------------------------------------------------------
/src/components/organisms/GroupBox/GroupBox.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import GroupBoxA from "./GroupBoxA";
4 | import GroupBoxP from "./GroupBoxP";
5 | import GroupBoxS from "./GroupBoxS";
6 | import type { Group } from "lib/interface";
7 |
8 | interface Props {
9 | type?: "profile" | "simple" | "apply";
10 | group?: Group;
11 | }
12 |
13 | const GroupBox: React.FC = ({ type = "profile", group = null, ...props }) => {
14 | switch (type) {
15 | case "profile":
16 | return group && ;
17 | case "simple":
18 | return group && ;
19 | case "apply":
20 | return ;
21 | default:
22 | return null;
23 | }
24 | };
25 |
26 | export default GroupBox;
27 |
--------------------------------------------------------------------------------
/src/components/organisms/GroupBox/GroupBoxA.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import addGray from "static/images/add_gray.svg";
4 |
5 | import { GroupAW } from "./GroupBox.styled";
6 |
7 | const GroupBox: React.FC = () => (
8 |
9 |
10 | 새로운 그룹 신청하기
11 |
12 | );
13 |
14 | export default GroupBox;
15 |
--------------------------------------------------------------------------------
/src/components/organisms/GroupBox/GroupBoxP.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Tooltip from "@material-ui/core/Tooltip";
3 |
4 | import { getLabeledTimeDiff } from "lib/utils";
5 |
6 | import groupDefaultProfile from "static/images/groupDefaultProfile.png";
7 |
8 | import { GroupW, NameW, ProfileStatsW, WritingsW } from "./GroupBox.styled";
9 | import type { Group } from "lib/interface";
10 |
11 | interface Props {
12 | group: Group;
13 | }
14 |
15 | const GroupBox: React.FC = ({ group, ...props }) => {
16 | const timePast = group.recentUpload
17 | ? getLabeledTimeDiff(group.recentUpload, 60, 60, 24, 7, 5, 12)
18 | : "없음";
19 | const stats = [
20 | {
21 | name: "올린 자보",
22 | value: group.zabosCount,
23 | },
24 | {
25 | name: "팔로워",
26 | value: group.followersCount,
27 | },
28 | {
29 | name: "최근 업로드",
30 | value: timePast,
31 | },
32 | ];
33 |
34 | return (
35 | {
40 | if (group.isPending) e.preventDefault();
41 | }}
42 | {...props}
43 | >
44 | {group.profilePhoto ? (
45 |
46 | ) : (
47 |
48 | )}
49 |
50 |
51 | {group.name}
52 |
53 |
54 |
55 |
56 | );
57 | };
58 |
59 | export default GroupBox;
60 |
--------------------------------------------------------------------------------
/src/components/organisms/GroupBox/GroupBoxS.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from "react";
2 | import { useSelector } from "react-redux";
3 | import get from "lodash.get";
4 |
5 | import SuperTooltip from "components/atoms/SuperTooltip";
6 |
7 | import { isElemWidthOverflown } from "lib/utils";
8 |
9 | import groupDefaultProfile from "static/images/groupDefaultProfile.png";
10 |
11 | import { GroupSW, NameW, SubtitleW, WritingsW } from "./GroupBox.styled";
12 | import { Group } from "lib/interface";
13 |
14 | interface Props {
15 | group: Group;
16 | }
17 |
18 | const GroupBoxS: React.FC = ({ group, ...props }) => {
19 | const width = useSelector((state) => get(state, ["app", "windowSize", "width"]));
20 | const nameRef = useRef(null);
21 | const [showTooltip, setShowTooltip] = useState(false);
22 | useEffect(() => {
23 | nameRef.current && setShowTooltip(isElemWidthOverflown(nameRef.current));
24 | }, [nameRef, width]);
25 |
26 | return (
27 |
28 |
29 | {group.profilePhoto ? (
30 |

31 | ) : (
32 |

33 | )}
34 |
35 |
36 |
37 | {group.name}
38 |
39 | {group.subtitle || "한 줄 소개 없음"}
40 |
41 |
42 | );
43 | };
44 |
45 | export default GroupBoxS;
46 |
--------------------------------------------------------------------------------
/src/components/organisms/GroupBox/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./GroupBox";
2 |
--------------------------------------------------------------------------------
/src/components/organisms/GroupList/GroupList.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | import ScrollBtn from "../../molecules/ScrollBtn";
5 | import GroupBox from "../GroupBox";
6 | import type { Group } from "lib/interface/schemas";
7 |
8 | const GroupsComponent = styled.section`
9 | width: 1032px;
10 |
11 | h1 {
12 | display: inline-block;
13 | font-size: 22px;
14 | color: #363636;
15 | margin: 0 0 16px;
16 | font-weight: 800;
17 | }
18 |
19 | @media (max-width: 640px) {
20 | width: 100%;
21 | /* padding: 0 16px; */
22 | h1 {
23 | padding: 0 16px;
24 | font-size: 18px;
25 | margin-bottom: 12px;
26 | }
27 | }
28 | `;
29 |
30 | const GroupsListComponent = styled.div`
31 | display: flex;
32 | scroll-behavior: smooth;
33 | width: 100%;
34 | padding: 3px;
35 | margin-top: 16px;
36 | overflow-x: scroll;
37 | /* overflow-y: visible; */
38 | white-space: nowrap;
39 |
40 | /* hide scroll bar */
41 | /* -webkit- (Chrome, Safari, newer versions of Opera) */
42 |
43 | &::-webkit-scrollbar {
44 | width: 0 !important;
45 | }
46 |
47 | /* Firefox */
48 | scrollbar-width: none;
49 | /* -ms- (Internet Explorer +10) */
50 | -ms-overflow-style: none;
51 | @media (max-width: 640px) {
52 | margin-top: 12px;
53 | padding: 3px 16px;
54 | }
55 | `;
56 |
57 | export const Groups = Object.assign(GroupsComponent, { List: GroupsListComponent });
58 |
59 | const text = {
60 | profile: "소속 그룹",
61 | search: "그룹 검색 결과",
62 | } as const;
63 |
64 | interface Props {
65 | type: keyof typeof text;
66 | groups: Group[];
67 | hasApplyBox?: boolean;
68 | isMyProfile?: boolean;
69 | }
70 |
71 | const GroupList: React.FC = ({ type, groups, isMyProfile }) => (
72 |
73 | {text[type]}
74 | 3} />
75 |
76 | {groups.map((group) => (
77 |
78 | ))}
79 | {isMyProfile && }
80 | <> >
81 |
82 |
83 | );
84 |
85 | export default GroupList;
86 |
--------------------------------------------------------------------------------
/src/components/organisms/GroupList/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./GroupList";
2 |
--------------------------------------------------------------------------------
/src/components/organisms/List/List.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import styled from "styled-components";
4 |
5 | const StyledList = styled.div`
6 | width: 100%;
7 | `;
8 |
9 | const List = ({ dataSource, renderItem }) => {dataSource.map(renderItem)};
10 |
11 | List.propTypes = {
12 | dataSource: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
13 | renderItem: PropTypes.func,
14 | };
15 |
16 | List.defaultProps = {
17 | renderItem: (item) => {item}
,
18 | };
19 |
20 | export default List;
21 |
--------------------------------------------------------------------------------
/src/components/organisms/List/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./List";
2 |
--------------------------------------------------------------------------------
/src/components/organisms/MemberItem/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./MemberItem";
2 |
--------------------------------------------------------------------------------
/src/components/organisms/ProfileStats/ProfileStats.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import { Stats } from "./ProfileStats.styled";
5 |
6 | const ProfileStats = ({ stats, smallV, ...props }) => (
7 |
8 | {stats.map(({ name, value }) => (
9 |
10 | {value || 0}
11 | {name}
12 |
13 | ))}
14 |
15 | );
16 |
17 | ProfileStats.propTypes = {
18 | stats: PropTypes.arrayOf(
19 | PropTypes.shape({
20 | name: PropTypes.string.isRequired,
21 | value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
22 | }),
23 | ).isRequired,
24 | smallV: PropTypes.bool,
25 | };
26 |
27 | ProfileStats.defaultProps = {
28 | smallV: false,
29 | };
30 |
31 | export default ProfileStats;
32 |
--------------------------------------------------------------------------------
/src/components/organisms/ProfileStats/ProfileStats.styled.js:
--------------------------------------------------------------------------------
1 | import styled, { css } from "styled-components";
2 |
3 | export const Stats = styled.section`
4 | display: inline-block;
5 | `;
6 |
7 | Stats.elem = styled.div`
8 | display: inline-block;
9 | border-right: 1px solid #e9e9e9;
10 | padding: 0 18px;
11 |
12 | h3 {
13 | font-size: 22px;
14 | font-weight: 800;
15 | color: #143441;
16 | text-align: center;
17 | margin: 0 0 6px 0;
18 | }
19 | div {
20 | font-size: 14px;
21 | color: #8f8f8f;
22 | text-align: center;
23 | }
24 |
25 | ${(props) =>
26 | props.small
27 | ? css`
28 | padding: 0 14px;
29 | h3 {
30 | font-size: 16px;
31 | margin-bottom: 4px;
32 | font-weight: bold;
33 | }
34 | div {
35 | font-size: 12px;
36 | }
37 | @media (max-width: 640px) {
38 | padding: 0 11px;
39 | h3 {
40 | font-size: 14px;
41 | margin-bottom: 3px;
42 | }
43 | div {
44 | font-size: 10px;
45 | }
46 | }
47 | `
48 | : css``};
49 |
50 | &:nth-child(1) {
51 | padding-left: 0;
52 | }
53 | &:nth-child(3) {
54 | padding-right: 0;
55 | border-right: none;
56 | }
57 | `;
58 |
--------------------------------------------------------------------------------
/src/components/organisms/ProfileStats/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./ProfileStats";
2 |
--------------------------------------------------------------------------------
/src/components/organisms/SearchSelect/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./SearchSelect";
2 |
--------------------------------------------------------------------------------
/src/components/organisms/ZaboCard/ZaboCard.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import withZabo from "hoc/withZabo";
5 | import { ZaboType } from "lib/propTypes";
6 |
7 | import ZaboCardL from "./ZaboCardL";
8 | import ZaboCardM from "./ZaboCardM";
9 |
10 | const ZaboCard = ({ zabo, size }) => {
11 | if (size === "large") return ;
12 | return ;
13 | };
14 |
15 | ZaboCard.propTypes = {
16 | zabo: ZaboType.isRequired,
17 | size: PropTypes.oneOf(["medium", "large"]),
18 | };
19 |
20 | ZaboCard.defaultProps = {
21 | size: "medium",
22 | };
23 |
24 | export default withZabo(ZaboCard, false, false);
25 |
--------------------------------------------------------------------------------
/src/components/organisms/ZaboCard/ZaboCard.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { storiesOf } from "@storybook/react";
3 |
4 | import ZaboCard from "./index";
5 |
6 | storiesOf("organisms/ZaboCard", module).add("Default", () => , {
7 | notes: "",
8 | });
9 |
--------------------------------------------------------------------------------
/src/components/organisms/ZaboCard/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./ZaboCard";
2 |
--------------------------------------------------------------------------------
/src/components/pages/ApiPage/ApiPage.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { Switch, useRouteMatch } from "react-router-dom";
3 |
4 | import { PublicRoute } from "hoc/AuthRoutes";
5 | import axios from "lib/axios";
6 |
7 | const Login = () => {
8 | useEffect(() => {
9 | axios
10 | .get("/auth/loginApi")
11 | .then((data) => {
12 | window.location.href = data.url;
13 | })
14 | .catch((error) => {
15 | console.error(error);
16 | alert("로그인에 실패하였습니다.");
17 | });
18 | }, []);
19 | return null;
20 | };
21 |
22 | const ApiPage = () => {
23 | const { path } = useRouteMatch();
24 | return (
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | ApiPage.propTypes = {};
32 |
33 | ApiPage.defaultProps = {};
34 |
35 | export default ApiPage;
36 |
--------------------------------------------------------------------------------
/src/components/pages/ApiPage/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./ApiPage";
2 |
--------------------------------------------------------------------------------
/src/components/pages/AuthPage/AuthPage.container.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react";
2 | import { connect } from "react-redux";
3 |
4 | import AuthPage from "./AuthPage";
5 |
6 | class AuthPageContainer extends PureComponent {
7 | render() {
8 | return ;
9 | }
10 | }
11 |
12 | const mapStateToProps = (state) => ({});
13 |
14 | const mapDispatchToProps = (dispatch) => ({});
15 |
16 | export default connect(mapStateToProps, mapDispatchToProps)(AuthPageContainer);
17 |
--------------------------------------------------------------------------------
/src/components/pages/AuthPage/AuthPage.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react";
2 | import { Redirect, Route, Switch } from "react-router-dom";
3 |
4 | import { LoginPage } from "components/pages";
5 |
6 | import AuthPageWrapper from "./AuthPage.styled";
7 |
8 | class AuthPage extends PureComponent {
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 | }
19 | }
20 |
21 | AuthPage.propTypes = {};
22 |
23 | AuthPage.defaultProps = {};
24 |
25 | export default AuthPage;
26 |
--------------------------------------------------------------------------------
/src/components/pages/AuthPage/AuthPage.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { storiesOf } from "@storybook/react";
3 |
4 | import AuthPage from "./index";
5 |
6 | storiesOf("pages/AuthPage", module).add("Default", () => , {
7 | notes: "",
8 | });
9 |
--------------------------------------------------------------------------------
/src/components/pages/AuthPage/AuthPage.styled.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const AuthPageWrapper = styled.div``;
4 |
5 | export default AuthPageWrapper;
6 |
--------------------------------------------------------------------------------
/src/components/pages/AuthPage/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./AuthPage.container";
2 |
--------------------------------------------------------------------------------
/src/components/pages/HomePage/HomePage.container.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react";
2 | import { connect } from "react-redux";
3 | import get from "lodash.get";
4 |
5 | import { getZaboList } from "store/reducers/zabo";
6 |
7 | import HomePage from "./HomePage";
8 |
9 | // deliver states(Redux) as props to the HomePage component
10 | class HomePageContainer extends PureComponent {
11 | render() {
12 | return ;
13 | }
14 | }
15 |
16 | // Subscribe to the Redux "state"
17 | const mapStateToProps = (state) => ({
18 | zaboList: get(state, ["zabo", "zaboList"]),
19 | });
20 |
21 | // HomePage 에서 변경 사항이 생긴다면 널리 알려라.
22 | const mapDispatchToProps = {
23 | getZaboList,
24 | };
25 |
26 | // index.js 가 HomePage 를 import 하는 것이 아닌, HomePage
27 | export default connect(mapStateToProps, mapDispatchToProps)(HomePageContainer);
28 |
--------------------------------------------------------------------------------
/src/components/pages/HomePage/HomePage.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import Header from "components/templates/Header";
4 | import ZaboList from "components/templates/ZaboList";
5 |
6 | import HomePageWrapper from "./HomePage.styled";
7 |
8 | const HomePage = () => (
9 |
10 |
11 |
12 |
13 |
14 |
15 | );
16 |
17 | HomePage.propTypes = {};
18 |
19 | HomePage.defaultProps = {};
20 |
21 | export default HomePage;
22 |
--------------------------------------------------------------------------------
/src/components/pages/HomePage/HomePage.stories.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import React from "react";
3 | import { storiesOf } from "@storybook/react";
4 |
5 | import HomePage from "./index";
6 |
7 | storiesOf("pages/HomePage", module).add("Default", () => , {
8 | notes: "",
9 | });
10 |
--------------------------------------------------------------------------------
/src/components/pages/HomePage/HomePage.styled.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | /* ============ Wrapper ============ */
4 | const HomePageWrapper = styled.div`
5 | transition: 0.4s;
6 | animation-duration: 0.3s;
7 |
8 | /*
9 | * Poster Layout
10 | * poster = 240px, column space = 10px
11 | * .container padding = 0px 20px
12 | * num of posters: total width
13 | * 1: 240px
14 | * 2: 490px => 530 ~
15 | * 3: 760px => 800 ~
16 | * 4: 1020px => 1060 ~
17 | */
18 |
19 | /* @media (min-width: 0px) and (max-width: 530px) {
20 | !* .container width auto with padding *!
21 | }
22 | @media (min-width: 530px) and (max-width: 800px) {
23 | .container {
24 | width: 530px;
25 | }
26 | }
27 | @media (min-width: 800px) and (max-width: 1060px) {
28 | .container {
29 | width: 800px;
30 | }
31 | }
32 | @media (min-width: 1060px) {
33 | .container {
34 | width: 1060px;
35 | }
36 | }*/
37 | `;
38 |
39 | export default HomePageWrapper;
40 |
41 | /* ============ Header ============ */
42 | export const Header = styled.div`
43 | display: flex;
44 | align-items: center;
45 |
46 | .blur {
47 | transition: 0.5s;
48 | position: absolute;
49 | top: 0;
50 | left: 0;
51 | width: 100%;
52 | height: 0;
53 | z-index: 1;
54 | &.show {
55 | border-top: 6px solid rgb(27, 50, 65);
56 | height: 100%;
57 | background-color: rgba(255, 255, 255);
58 | }
59 | }
60 | `;
61 | Header.Search = styled.div`
62 | flex: 1;
63 | margin-right: 12px;
64 | transition: 1s;
65 | z-index: 2;
66 | `;
67 | Header.AddButton = styled.button`
68 | width: 60px;
69 | height: 40px;
70 | background-color: rgb(27, 50, 65);
71 | `;
72 |
--------------------------------------------------------------------------------
/src/components/pages/HomePage/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./HomePage.container";
2 |
--------------------------------------------------------------------------------
/src/components/pages/LandingPage/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./LandingPage";
2 |
--------------------------------------------------------------------------------
/src/components/pages/LoginPage/LoginPage.container.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react";
2 | import { connect } from "react-redux";
3 |
4 | import LoginPage from "./LoginPage";
5 |
6 | class LoginPageContainer extends PureComponent {
7 | render() {
8 | return ;
9 | }
10 | }
11 |
12 | const mapStateToProps = (state) => ({});
13 |
14 | const mapDispatchToProps = (dispatch) => ({});
15 |
16 | export default connect(mapStateToProps, mapDispatchToProps)(LoginPageContainer);
17 |
--------------------------------------------------------------------------------
/src/components/pages/LoginPage/LoginPage.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 |
3 | import axios from "lib/axios";
4 | import storage from "lib/storage";
5 |
6 | const LoginPage = ({ history }) => {
7 | useEffect(() => {
8 | const { state } = history.location;
9 | if (state && state.referrer) {
10 | storage.setItem("referrer", state.referrer);
11 | }
12 | axios
13 | .get("/auth/loginApi")
14 | .then((data) => {
15 | window.location.replace(data.url);
16 | })
17 | .catch((error) => {
18 | console.error(error);
19 | alert("로그인에 실패하였습니다.");
20 | });
21 | }, [history]);
22 | return null;
23 | };
24 | LoginPage.propTypes = {};
25 |
26 | LoginPage.defaultProps = {};
27 |
28 | export default LoginPage;
29 |
--------------------------------------------------------------------------------
/src/components/pages/LoginPage/LoginPage.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { storiesOf } from "@storybook/react";
3 |
4 | import LoginPage from "./index";
5 |
6 | storiesOf("pages/LoginPage", module).add("Default", () => , {
7 | notes: "",
8 | });
9 |
--------------------------------------------------------------------------------
/src/components/pages/LoginPage/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./LoginPage.container";
2 |
--------------------------------------------------------------------------------
/src/components/pages/NotFound/NotFound.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { NavLink } from "react-router-dom";
3 |
4 | import notFoundImage from "static/images/notFoundImage.jpg";
5 | import logo from "static/logo/logo.svg";
6 |
7 | import { Page } from "./NotFound.styled";
8 |
9 | const NotFound = () => (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | 요청하신 페이지를 찾을 수 없습니다.
20 |
21 | 메인페이지로 이동하기
22 |
23 |
24 |
25 | );
26 |
27 | export default NotFound;
28 |
--------------------------------------------------------------------------------
/src/components/pages/NotFound/NotFound.styled.js:
--------------------------------------------------------------------------------
1 | import styled, { css } from "styled-components";
2 |
3 | import { media } from "lib/utils/style";
4 |
5 | export const Page = styled.section`
6 | width: 100%;
7 | height: 100%;
8 | `;
9 |
10 | Page.Header = styled.div`
11 | position: fixed;
12 | top: 0;
13 | width: 100%;
14 | height: 55px;
15 | border-top: 5px solid ${(props) => props.theme.main};
16 | padding: 10px 18px;
17 | `;
18 |
19 | Page.Body = styled.div`
20 | width: 90vw;
21 | margin: 240px 5vw 0;
22 | ${media.tablet(css`
23 | width: 34vw;
24 | margin: 280px 33vw 0;
25 | `)};
26 | `;
27 |
28 | Page.Title = styled.img`
29 | width: 60vw;
30 | margin: 0 15vw;
31 | ${media.tablet(css`
32 | width: 34vw;
33 | margin: 0;
34 | `)};
35 | `;
36 |
37 | Page.Description = styled.p`
38 | text-align: center;
39 | margin-top: 28px;
40 | line-height: 1.6;
41 | font-size: 16px;
42 | color: ${(props) => props.theme.gray30};
43 | ${media.tablet(css`
44 | margin-top: 48px;
45 | font-size: 18px;
46 | `)};
47 | `;
48 |
49 | Page.Description.Link = styled.a`
50 | color: ${(props) => props.theme.main} !important;
51 | font-weight: bold;
52 | text-decoration: underline;
53 | `;
54 |
55 | export default Page;
56 |
--------------------------------------------------------------------------------
/src/components/pages/NotFound/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./NotFound";
2 |
--------------------------------------------------------------------------------
/src/components/pages/ProfilePage/Main.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Route, Switch, useRouteMatch } from "react-router-dom";
3 | import styled from "styled-components";
4 |
5 | import { NotFound } from "components/pages";
6 |
7 | import ProfilePage from "./ProfilePage";
8 |
9 | const ProfilePageWrapper = styled.section``;
10 |
11 | const Main = () => {
12 | const match = useRouteMatch();
13 |
14 | return (
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | };
23 |
24 | Main.propTypes = {};
25 |
26 | export default Main;
27 |
--------------------------------------------------------------------------------
/src/components/pages/ProfilePage/ProfilePage.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useMemo } from "react";
2 | import { useParams } from "react-router-dom";
3 | import { shallowEqual, useDispatch, useSelector } from "react-redux";
4 | import get from "lodash.get";
5 |
6 | import { NotFound } from "components/pages";
7 |
8 | import { getProfile } from "store/reducers/profile";
9 |
10 | import GroupProfile from "./GroupProfilePage";
11 | import UserProfile from "./UserProfilePage";
12 |
13 | const ProfilePage = () => {
14 | const { name } = useParams();
15 | const dispatch = useDispatch();
16 | useEffect(() => {
17 | dispatch(getProfile(name)).catch((error) => undefined);
18 | }, [name]);
19 | const profile = useSelector((state) => get(state, ["profile", "profiles", name]));
20 | if (!profile) return null;
21 | if (profile.error) return ;
22 | if (profile.username === name) return ;
23 | if (profile.name === name) return ;
24 | return null;
25 | };
26 |
27 | ProfilePage.propTypes = {};
28 |
29 | ProfilePage.defaultProps = {};
30 |
31 | export default ProfilePage;
32 |
--------------------------------------------------------------------------------
/src/components/pages/ProfilePage/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./Main";
2 |
--------------------------------------------------------------------------------
/src/components/pages/Reload.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 |
3 | const Reload: React.FC = () => {
4 | useEffect(() => {
5 | location.reload();
6 | }, []);
7 |
8 | return <>>;
9 | };
10 |
11 | export default Reload;
12 |
--------------------------------------------------------------------------------
/src/components/pages/SearchPage/SearchPage.styled.js:
--------------------------------------------------------------------------------
1 | import styled, { css } from "styled-components";
2 |
3 | import { media } from "lib/utils/style";
4 |
5 | export const Page = styled.div`
6 | min-width: 1072px;
7 | padding: 48px 0;
8 | @media (max-width: 640px) {
9 | min-width: 100%;
10 | padding: 28px 0;
11 | }
12 |
13 | display: flex;
14 | flex-direction: column;
15 | align-items: center;
16 | `;
17 |
18 | Page.Body = styled.div`
19 | width: 1032px;
20 | h1 {
21 | display: inline-block;
22 | font-size: 22px;
23 | font-weight: 800;
24 | color: #363636;
25 | margin: 0;
26 | }
27 | .emptySpace {
28 | margin-top: 8px;
29 | }
30 |
31 | @media (max-width: 640px) {
32 | width: 100%;
33 | margin-top: 12px;
34 | padding: 0 16px;
35 | h1 {
36 | font-size: 18px;
37 | }
38 | .emptySpace {
39 | margin-top: 0;
40 | }
41 | }
42 | `;
43 |
44 | export const EmptyResultW = styled.section`
45 | text-align: center;
46 | font-size: 16px;
47 | margin-top: ${(props) => (props.isZaboEmpty ? "90px" : "225px")};
48 | img {
49 | width: 29.15px;
50 | height: 29.15px;
51 | }
52 | div.empty-text {
53 | color: #202020;
54 | margin-bottom: 40px;
55 | .empty-query {
56 | display: inline-block;
57 | font-weight: 800;
58 | }
59 | }
60 | .search-icon {
61 | margin-bottom: 22px;
62 | }
63 | .empty-text {
64 | margin-bottom: 44px;
65 | }
66 | p {
67 | color: #bcbcbc;
68 | line-height: 28px;
69 | margin: 0;
70 | }
71 |
72 | @media (max-width: 640px) {
73 | margin-top: 97px;
74 | }
75 | ${media.tablet(css`
76 | img {
77 | margin-bottom: 34px;
78 | }
79 | `)};
80 | `;
81 |
82 | // TODO: Refactor dups
83 | export const ZaboResultW = styled.section`
84 | width: 100%;
85 | margin-top: ${(props) => (props.isGroupEmpty ? "0" : "68px")};
86 | ${media.tablet(css`
87 | width: 1032px;
88 | `)};
89 | `;
90 |
91 | export const GroupResultW = styled.section`
92 | @media (max-width: 640px) {
93 | margin: 0 -16px;
94 | }
95 | `;
96 |
--------------------------------------------------------------------------------
/src/components/pages/SearchPage/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./SearchPage";
2 |
--------------------------------------------------------------------------------
/src/components/pages/SettingsPage/Main.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Redirect, Route, Switch, useRouteMatch } from "react-router-dom";
3 | import styled from "styled-components";
4 |
5 | import { NotFound } from "components/pages";
6 |
7 | import pToP from "../../../hoc/paramsToProps";
8 | import GroupApply from "./GroupApply";
9 | import GroupMembersSetting from "./GroupMembersSetting";
10 | import GroupProfileSetting from "./GroupProfileSetting";
11 | import ProfileSetting from "./ProfileSetting";
12 |
13 | const SettingsWrapper = styled.section``;
14 |
15 | const Main = () => {
16 | const { path } = useRouteMatch();
17 | return (
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | export default Main;
32 |
--------------------------------------------------------------------------------
/src/components/pages/SettingsPage/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./Main";
2 |
--------------------------------------------------------------------------------
/src/components/pages/SettingsPage/withGroupProfile.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import get from "lodash.get";
4 |
5 | import { NotFound } from "components/pages";
6 |
7 | import { getProfile } from "store/reducers/profile";
8 |
9 | const withGroupProfile = (
10 | // TODO: add profile types
11 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
12 | WrappedComponent: React.FC
,
13 | isPrivate = false,
14 | ) => {
15 | const ComponentWithGroupProfile: React.FC
= (props) => {
16 | const dispatch = useDispatch();
17 |
18 | useEffect(() => {
19 | dispatch(getProfile(props.groupName));
20 | }, [props.groupName, dispatch]);
21 |
22 | const profile = useSelector((state) => get(state, ["profile", "profiles", props.groupName]));
23 | if (!profile) return null;
24 | if (profile.error) return ;
25 | if (isPrivate && !profile.myRole) return ;
26 | return ;
27 | };
28 |
29 | return ComponentWithGroupProfile;
30 | };
31 |
32 | export default withGroupProfile;
33 |
--------------------------------------------------------------------------------
/src/components/pages/Zabo/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/components/pages/Zabo/index.js
--------------------------------------------------------------------------------
/src/components/pages/ZaboPage/Main.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Route, Switch, useRouteMatch } from "react-router-dom";
3 |
4 | import { NotFound, ZaboDetailPage, ZaboEditPage } from "components/pages";
5 | import Header from "components/templates/Header";
6 |
7 | import paramsToProps from "../../../hoc/paramsToProps";
8 | import { ZaboPageWrapper } from "./ZaboPage.styled";
9 |
10 | const Main = () => {
11 | const { path } = useRouteMatch();
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | };
23 |
24 | export default Main;
25 |
--------------------------------------------------------------------------------
/src/components/pages/ZaboPage/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./Main";
2 |
--------------------------------------------------------------------------------
/src/components/pages/ZaboUploadPage/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./ZaboUploadPage";
2 |
--------------------------------------------------------------------------------
/src/components/pages/index.ts:
--------------------------------------------------------------------------------
1 | import loadable from "@loadable/component";
2 | /*
3 | Migrated from react-loadable to loadable-component.
4 | Check out these to find out more
5 | https://loadable-components.com/docs/loadable-vs-react-lazy/#note-about-react-loadable
6 | https://velog.io/@velopert/nomore-react-loadable
7 | */
8 |
9 | export const HomePage = loadable(/* webpackPrefetch: true */ () => import("./HomePage"));
10 | export const LandingPage = loadable(/* webpackPrefetch: true */ () => import("./LandingPage"));
11 | export const ZaboPage = loadable(/* webpackPrefetch: true */ () => import("./ZaboPage"));
12 | export const ZaboDetailPage = loadable(
13 | /* webpackPrefetch: true */ () => import("./ZaboPage/ZaboDetailPage"),
14 | );
15 | export const ZaboEditPage = loadable(
16 | /* webpackPrefetch: true */ () => import("./ZaboPage/ZaboEditPage"),
17 | );
18 | export const ZaboUploadPage = loadable(
19 | /* webpackPrefetch: true */ () => import("./ZaboUploadPage"),
20 | );
21 | export const SettingsPage = loadable(/* webpackPrefetch: true */ () => import("./SettingsPage"));
22 | export const AuthPage = loadable(/* webpackPrefetch: true */ () => import("./AuthPage"));
23 | export const LoginPage = loadable(/* webpackPrefetch: true */ () => import("./LoginPage"));
24 | export const ProfilePage = loadable(/* webpackPrefetch: true */ () => import("./ProfilePage"));
25 | export const NotFound = loadable(/* webpackPrefetch: true */ () => import("./NotFound"));
26 | // export const AdminPage = loadable(/* webpackPrefetch: true */ () => import("./AdminPage"));
27 | export const SearchPage = loadable(/* webpackPrefetch: true */ () => import("./SearchPage"));
28 | export const ApiPage = loadable(/* webpackPrefetch: true */ () => import("./ApiPage"));
29 | export const ReloadPage = loadable(/* webpackPrefetch: true */ () => import("./Reload"));
30 |
--------------------------------------------------------------------------------
/src/components/templates/FloatingNavigator/FloatingNavigator.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react";
2 |
3 | import add from "static/images/add.svg";
4 | import calendar from "static/images/calendar.svg";
5 | import search from "static/images/search-icon-navy.png";
6 | import user from "static/images/user.svg";
7 |
8 | import FloatingNavigatorWrapper, { NavItem } from "./FloatingNavigator.styled";
9 |
10 | class FloatingNavigator extends PureComponent {
11 | state = { scrollY: window.scrollY, show: true };
12 |
13 | componentDidMount() {
14 | window.addEventListener("optimizedScroll", this.handleScroll);
15 | }
16 |
17 | componentWillUnmount() {
18 | window.removeEventListener("optimizedScroll", this.handleScroll);
19 | }
20 |
21 | handleScroll = () => {
22 | this.setState((prevState) => ({
23 | scrollY: window.scrollY,
24 | show: prevState.scrollY > window.scrollY,
25 | }));
26 | };
27 |
28 | render() {
29 | const { show } = this.state;
30 |
31 | return (
32 |
33 |
34 |
홈
35 |
36 |
37 |
38 | 검색
39 |
40 |
41 |
42 | 업로드
43 |
44 |
45 |
46 | 프로필
47 |
48 |
49 | );
50 | }
51 | }
52 |
53 | FloatingNavigator.propTypes = {};
54 |
55 | FloatingNavigator.defaultProps = {};
56 |
57 | export default FloatingNavigator;
58 |
--------------------------------------------------------------------------------
/src/components/templates/FloatingNavigator/FloatingNavigator.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { storiesOf } from "@storybook/react";
3 |
4 | import FloatingNavigator from "./index";
5 |
6 | storiesOf("templates/FloatingNavigator", module).add("Default", () => , {
7 | notes: "",
8 | });
9 |
--------------------------------------------------------------------------------
/src/components/templates/FloatingNavigator/FloatingNavigator.styled.js:
--------------------------------------------------------------------------------
1 | import { NavLink } from "react-router-dom";
2 | import styled, { css } from "styled-components";
3 |
4 | export const NavItem = styled(NavLink)`
5 | flex: 1 0 20%;
6 | margin: 0 2.5%;
7 | display: flex;
8 | align-items: center;
9 | justify-content: center;
10 | flex-direction: column;
11 | transition: all 0.4s ease;
12 | img {
13 | transition: all 0.4s ease;
14 | width: 20px;
15 | height: 20px;
16 | margin-bottom: 6px;
17 | }
18 | &:hover {
19 | cursor: pointer;
20 | }
21 | &.active {
22 | border-bottom: 1px solid black;
23 | }
24 | `;
25 |
26 | const FloatingNavigatorWrapper = styled.div`
27 | position: fixed;
28 | transition: max-height 0.4s;
29 | bottom: 0;
30 | left: 0;
31 | width: 100%;
32 | height: 60px;
33 | max-height: ${(props) => (props.show ? "60px" : 0)};
34 | overflow: hidden;
35 | border-top: 1px solid black;
36 | background-color: white;
37 | z-index: 1000;
38 | display: flex;
39 | justify-content: space-between;
40 |
41 | ${NavItem} {
42 | ${(props) =>
43 | props.show
44 | ? css``
45 | : css`
46 | img {
47 | width: 0;
48 | height: 0;
49 | }
50 | font-size: 0;
51 | `};
52 | @media (min-width: 560px) {
53 | display: none;
54 | }
55 | }
56 | `;
57 |
58 | export default FloatingNavigatorWrapper;
59 |
--------------------------------------------------------------------------------
/src/components/templates/FloatingNavigator/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./FloatingNavigator";
2 |
--------------------------------------------------------------------------------
/src/components/templates/Footer/Footer.styled.tsx:
--------------------------------------------------------------------------------
1 | import styled, { type css } from "styled-components";
2 |
3 | const FooterWrapper = styled.footer<{
4 | ownStyle?: ReturnType;
5 | }>`
6 | position: fixed;
7 | bottom: 0;
8 | left: 0;
9 | width: 100%;
10 | height: 74px;
11 | background: white;
12 | z-index: 1000;
13 |
14 | display: flex;
15 | flex-direction: column;
16 | justify-content: center;
17 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.4);
18 | @media (max-width: 640px) {
19 | height: 60px;
20 | }
21 | ${(props) => props.ownStyle || ""};
22 | `;
23 |
24 | export default FooterWrapper;
25 |
--------------------------------------------------------------------------------
/src/components/templates/Footer/Footer.tsx:
--------------------------------------------------------------------------------
1 | import React, { type PropsWithChildren, useEffect, useState } from "react";
2 | import { css } from "styled-components";
3 |
4 | import Container from "components/atoms/Container";
5 |
6 | import FooterWrapper from "./Footer.styled";
7 |
8 | const containerStyle = (props: { horizontalScroll?: boolean }) => css`
9 | position: absolute;
10 | justify-content: space-between;
11 | align-items: center;
12 |
13 | ${props.horizontalScroll &&
14 | css`
15 | @media (min-width: 640px) {
16 | min-width: 1072px;
17 | }
18 | `}
19 | `;
20 |
21 | interface Props extends PropsWithChildren {
22 | ownStyle?: ReturnType;
23 | scrollFooter?: boolean;
24 | }
25 |
26 | const Footer: React.FC = ({ ownStyle, scrollFooter, children }) => {
27 | const [left, setLeft] = useState(0);
28 | useEffect(() => {
29 | const listener = () => setLeft(-window.pageXOffset);
30 | window.addEventListener("optimizedScroll", listener);
31 | return () => window.removeEventListener("optimizedScroll", listener);
32 | }, []);
33 | const style = { left };
34 |
35 | return (
36 |
37 |
38 | {children}
39 |
40 |
41 | );
42 | };
43 |
44 | export default Footer;
45 |
--------------------------------------------------------------------------------
/src/components/templates/Footer/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./Footer";
2 |
--------------------------------------------------------------------------------
/src/components/templates/Header/Header.stories.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import React from "react";
3 | import { storiesOf } from "@storybook/react";
4 |
5 | import Header from "./index";
6 |
7 | storiesOf("templates/Header", module).add("Default", () => , {
8 | notes: "",
9 | });
10 |
--------------------------------------------------------------------------------
/src/components/templates/Header/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./Header";
2 |
--------------------------------------------------------------------------------
/src/components/templates/Loading/Loading.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import LoadingWrapper from "./Loading.styled";
5 |
6 | class Loading extends PureComponent {
7 | render() {
8 | return (
9 |
10 |
11 | - za
12 | -
13 | b
14 | o
15 |
16 |
17 |
18 | );
19 | }
20 | }
21 |
22 | Loading.propTypes = {
23 | height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
24 | };
25 |
26 | Loading.defaultProps = {
27 | height: "100vh",
28 | };
29 |
30 | export default Loading;
31 |
--------------------------------------------------------------------------------
/src/components/templates/Loading/Loading.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { storiesOf } from "@storybook/react";
3 |
4 | import Loading from "./index";
5 |
6 | storiesOf("templates/Loading", module).add("Default", () => , {
7 | notes: "",
8 | });
9 |
--------------------------------------------------------------------------------
/src/components/templates/Loading/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./Loading";
2 |
--------------------------------------------------------------------------------
/src/components/templates/Modal/Modal.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { storiesOf } from "@storybook/react";
3 |
4 | import Modal from "./index";
5 |
6 | storiesOf("templates/Modal", module).add("Default", () => , {
7 | notes: "",
8 | });
9 |
--------------------------------------------------------------------------------
/src/components/templates/Modal/index.js:
--------------------------------------------------------------------------------
1 | import Modal from "./Modal";
2 |
3 | export * from "./modalMethods";
4 | export default Modal;
5 |
--------------------------------------------------------------------------------
/src/components/templates/Modal/modalMethods.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | import Modal from "./Modal";
5 |
6 | const { body } = document;
7 |
8 | export const showInstanceModal = ({ content, onCancel, ...props }) => {
9 | const container = document.createElement("div");
10 | body.appendChild(container);
11 |
12 | const render = (show) => {
13 | ReactDOM.render(
14 | // eslint-disable-next-line no-use-before-define
15 |
16 | {content}
17 | ,
18 | container,
19 | );
20 | };
21 |
22 | const show = () => render(true);
23 |
24 | const hide = () => render(false);
25 |
26 | const unmount = () => {
27 | ReactDOM.unmountComponentAtNode(container);
28 | body.removeChild(container);
29 | };
30 |
31 | const _onCancel = () => {
32 | if (typeof onCancel === "function") onCancel();
33 | hide();
34 | };
35 |
36 | show();
37 |
38 | return hide;
39 | };
40 |
--------------------------------------------------------------------------------
/src/components/templates/PWAPrompt/PWAPrompt.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react";
2 |
3 | import appIcon from "static/logo/sparcs.svg";
4 |
5 | import PWAPromptWrapper from "./PWAPrompt.styled";
6 |
7 | class PWAPrompt extends PureComponent {
8 | state = { active: false };
9 |
10 | handleScroll = () => {
11 | if (window.scrollY < 10) {
12 | document.body.classList.add("pwa-prompt-active");
13 | this.setState({ active: true });
14 | } else {
15 | document.body.classList.remove("pwa-prompt-active");
16 | setTimeout(() => this.setState({ active: false }));
17 | }
18 | };
19 |
20 | addListener = () => {
21 | this.setState({ active: true });
22 | window.addEventListener("optimizedScroll", this.handleScroll);
23 | };
24 |
25 | deleteListener = () => {
26 | this.setState({ active: false });
27 | window.removeEventListener("optimizedScroll", this.handleScroll);
28 | };
29 |
30 | componentDidMount() {
31 | if (window.pwaPromptActive) {
32 | this.addListener();
33 | } else {
34 | window.onPWAPromptActive = this.addListener;
35 | }
36 | }
37 |
38 | handleOpenClick = () => {
39 | this.deleteListener();
40 | document.body.classList.remove("pwa-prompt-active");
41 |
42 | window.deferredPrompt.prompt();
43 | // Wait for the user to respond to the prompt
44 | window.deferredPrompt.userChoice.then((choiceResult) => {
45 | if (choiceResult.outcome === "accepted") {
46 | console.log("User accepted the A2HS prompt"); // TODO: Statistics
47 | } else {
48 | console.log("User dismissed the A2HS prompt");
49 | }
50 | window.deferredPrompt = null;
51 | });
52 | };
53 |
54 | render() {
55 | const { active } = this.state;
56 | if (!active) return null;
57 | return (
58 |
59 |
60 |

61 |
62 |
ZABO (자보) : 모든 포스터를 한 곳에서 모아보세요
63 |
ZABO 어플리케이션 설치하기 (데스크탑, 안드로이드 ,iOS)
64 |
65 |
66 |
67 |
68 | );
69 | }
70 | }
71 |
72 | PWAPrompt.propTypes = {};
73 |
74 | PWAPrompt.defaultProps = {};
75 |
76 | export default PWAPrompt;
77 |
--------------------------------------------------------------------------------
/src/components/templates/PWAPrompt/PWAPrompt.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { storiesOf } from "@storybook/react";
3 |
4 | import PWAPrompt from "./index";
5 |
6 | storiesOf("templates/PWAPrompt", module).add("Default", () => , {
7 | notes: "",
8 | });
9 |
--------------------------------------------------------------------------------
/src/components/templates/PWAPrompt/PWAPrompt.styled.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const PWAPromptWrapper = styled.div`
4 | position: fixed;
5 | top: 0;
6 | width: 100%;
7 | height: 40px;
8 | background-color: #62666a;
9 | z-index: 1000;
10 | transition: max-height 0.4s;
11 | .container {
12 | height: 100%;
13 | flex-direction: row;
14 | align-items: center;
15 | }
16 |
17 | img {
18 | height: 25px;
19 | margin-right: 20px;
20 | }
21 |
22 | .texts {
23 | flex: 1;
24 | overflow: hidden;
25 |
26 | * {
27 | overflow: hidden;
28 | text-overflow: ellipsis;
29 | white-space: nowrap;
30 | color: white;
31 | }
32 | .title {
33 | font-size: 12px;
34 | }
35 | .desc {
36 | font-size: 10px;
37 | }
38 | }
39 |
40 | button {
41 | background-color: transparent;
42 | border: none;
43 | margin-left: 20px;
44 | height: 25px;
45 | padding: 0;
46 | color: cornflowerblue;
47 | font-size: 14px;
48 | }
49 | `;
50 |
51 | export default PWAPromptWrapper;
52 |
--------------------------------------------------------------------------------
/src/components/templates/PWAPrompt/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./PWAPrompt";
2 |
--------------------------------------------------------------------------------
/src/components/templates/SavedPosters/SavedPosters.container.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react";
2 | import { connect } from "react-redux";
3 |
4 | import SavedPosters from "./SavedPosters";
5 |
6 | class SavedPostersContainer extends PureComponent {
7 | render() {
8 | return ;
9 | }
10 | }
11 |
12 | const mapStateToProps = (state) => ({});
13 |
14 | const mapDispatchToProps = (dispatch) => ({});
15 |
16 | export default connect(mapStateToProps, mapDispatchToProps)(SavedPostersContainer);
17 |
--------------------------------------------------------------------------------
/src/components/templates/SavedPosters/SavedPosters.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import SavedPostersWrapper from "./SavedPosters.styled";
5 |
6 | class SavedPosters extends PureComponent {
7 | render() {
8 | return (
9 |
10 | {this.props.children}
11 | poster
12 |
13 | );
14 | }
15 | }
16 |
17 | SavedPosters.propTypes = {};
18 |
19 | SavedPosters.defaultProps = {};
20 |
21 | export default SavedPosters;
22 |
--------------------------------------------------------------------------------
/src/components/templates/SavedPosters/SavedPosters.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { storiesOf } from "@storybook/react";
3 |
4 | import SavedPosters from "./SavedPosters";
5 |
6 | storiesOf("templates/SavedPosters", module).add("Default", () => , {
7 | notes: "",
8 | });
9 |
--------------------------------------------------------------------------------
/src/components/templates/SavedPosters/SavedPosters.styled.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const SavedPostersWrapper = styled.div``;
4 |
5 | export default SavedPostersWrapper;
6 |
--------------------------------------------------------------------------------
/src/components/templates/SavedPosters/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./SavedPosters.container";
2 |
--------------------------------------------------------------------------------
/src/components/templates/SearchBar/SearchBar.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { storiesOf } from "@storybook/react";
3 |
4 | import SearchBar from "./index";
5 |
6 | storiesOf("templates/SearchBar", module).add("Default", () => , {
7 | notes: "",
8 | });
9 |
--------------------------------------------------------------------------------
/src/components/templates/SearchBar/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./SearchBar";
2 |
--------------------------------------------------------------------------------
/src/components/templates/ZaboList/ZaboList.container.js:
--------------------------------------------------------------------------------
1 | import { connect } from "react-redux";
2 | import { List } from "immutable";
3 | import get from "lodash.get";
4 |
5 | import { getGroupZaboList, getPins, getSearchZaboList, getZaboList } from "store/reducers/zabo";
6 |
7 | import ZaboList from "./ZaboList";
8 |
9 | const reduxKey = {
10 | main: () => ["zabo", "lists", "main"],
11 | related: (id) => ["zabo", "lists", id],
12 | pins: () => ["zabo", "lists", "pins"],
13 | group: (name) => ["zabo", "lists", name],
14 | search: () => ["zabo", "lists", "search"],
15 | };
16 | const emptyList = List([]);
17 | const mapStateToProps = (state, ownProps) => {
18 | const { type, query } = ownProps;
19 | const zaboIdList = get(state, reduxKey[type](query)) || emptyList;
20 | return {
21 | zaboIdList,
22 | width: get(state, ["app", "windowSize", "width"]),
23 | };
24 | };
25 |
26 | const mapDispatchToProps = {
27 | getPins,
28 | getZaboList,
29 | getGroupZaboList,
30 | getSearchZaboList,
31 | };
32 |
33 | export default connect(mapStateToProps, mapDispatchToProps)(ZaboList);
34 |
--------------------------------------------------------------------------------
/src/components/templates/ZaboList/ZaboList.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { storiesOf } from "@storybook/react";
3 |
4 | import ZaboList from "./index";
5 |
6 | storiesOf("templates/ZaboList", module).add("Default", () => , {
7 | notes: "",
8 | });
9 |
--------------------------------------------------------------------------------
/src/components/templates/ZaboList/ZaboList.styled.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const ZaboListWrapper = styled.div`
4 | width: 100%;
5 | .masonry.masonry-main {
6 | margin: 20px auto 0 auto;
7 | width: 100%;
8 | }
9 | `;
10 |
11 | export default ZaboListWrapper;
12 |
--------------------------------------------------------------------------------
/src/components/templates/ZaboList/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./ZaboList.container";
2 |
--------------------------------------------------------------------------------
/src/components/templates/ZaboList/withStackMaster.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import React from "react";
3 | import PropTypes from "prop-types";
4 | import storage from "lib/storage";
5 |
6 | export default (Component) => {
7 | class StackMaster extends React.Component {
8 | state = { stack: [] };
9 |
10 | componentDidMount() {
11 | const { zaboId } = this.props;
12 | if (!zaboId) {
13 | storage.setItem("zaboStack", []);
14 | } else {
15 | let zaboStack = storage.getItem("zaboStack") || [];
16 | zaboStack = Array.isArray(zaboStack) ? zaboStack : [];
17 | const prev = zaboStack.pop();
18 | if (!!prev && prev !== zaboId) zaboStack.push(prev);
19 | zaboStack.push(zaboId);
20 | storage.setItem("zaboStack", zaboStack);
21 | this.setState({ stack: zaboStack });
22 | }
23 | }
24 |
25 | componentDidUpdate(prevProps, prevState, snapshot) {
26 | const { zaboId } = this.props;
27 | if (prevProps.zaboId === zaboId) return;
28 |
29 | if (!zaboId) {
30 | this.setState({ stack: [] });
31 | storage.setItem("zaboStack");
32 | return;
33 | }
34 |
35 | this.setState((prevState) => {
36 | const { stack } = prevState;
37 | stack.push(zaboId);
38 | storage.setItem("zaboStack", stack);
39 | return { stack };
40 | });
41 | }
42 |
43 | render() {
44 | const { stack } = this.state;
45 | const { zaboId, ...props } = this.props;
46 |
47 | return ;
48 | }
49 | }
50 |
51 | StackMaster.propTypes = {
52 | ...Component.propTypes,
53 | zaboId: PropTypes.string,
54 | };
55 |
56 | return StackMaster;
57 | };
58 |
--------------------------------------------------------------------------------
/src/components/templates/ZaboUpload/LeavingAlert.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import get from "lodash.get";
4 |
5 | import { reset, setModal } from "store/reducers/upload";
6 |
7 | const LeavingAlert = () => {
8 | const dispatch = useDispatch();
9 | const showModal = useSelector((state) => get(state, ["upload", "showModal"]));
10 | useEffect(() => {
11 | if (showModal) {
12 | alert("데이터를 저장하시겠습니까?");
13 | dispatch(reset());
14 | dispatch(setModal(false));
15 | }
16 | }, [showModal]);
17 | return null;
18 | };
19 |
20 | export default LeavingAlert;
21 |
--------------------------------------------------------------------------------
/src/components/templates/ZaboUpload/Loading.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | const Style = styled.div`
5 | position: fixed;
6 | top: 0;
7 | left: 0;
8 | align-items: center;
9 | background-color: rgba(29, 31, 32, 0.5);
10 | display: flex;
11 | justify-content: center;
12 | width: 100%;
13 | height: 100%;
14 |
15 | .loader {
16 | position: fixed;
17 | left: 50%;
18 | top: 50%;
19 | animation: rotate 1s infinite;
20 | height: 50px;
21 | width: 50px;
22 | }
23 |
24 | .loader:before,
25 | .loader:after {
26 | border-radius: 50%;
27 | content: "";
28 | display: block;
29 | height: 20px;
30 | width: 20px;
31 | }
32 | .loader:before {
33 | animation: ball1 1s infinite;
34 | background-color: #cb2025;
35 | box-shadow: 30px 0 0 #f8b334;
36 | margin-bottom: 10px;
37 | }
38 | .loader:after {
39 | animation: ball2 1s infinite;
40 | background-color: #00a096;
41 | box-shadow: 30px 0 0 #97bf0d;
42 | }
43 |
44 | @keyframes rotate {
45 | 0% {
46 | transform: rotate(0deg) scale(0.8);
47 | }
48 | 50% {
49 | transform: rotate(360deg) scale(1.2);
50 | }
51 | 100% {
52 | transform: rotate(720deg) scale(0.8);
53 | }
54 | }
55 |
56 | @keyframes ball1 {
57 | 0% {
58 | box-shadow: 30px 0 0 #f8b334;
59 | }
60 | 50% {
61 | box-shadow: 0 0 0 #f8b334;
62 | margin-bottom: 0;
63 | transform: translate(15px, 15px);
64 | }
65 | 100% {
66 | box-shadow: 30px 0 0 #f8b334;
67 | margin-bottom: 10px;
68 | }
69 | }
70 |
71 | @keyframes ball2 {
72 | 0% {
73 | box-shadow: 30px 0 0 #97bf0d;
74 | }
75 | 50% {
76 | box-shadow: 0 0 0 #97bf0d;
77 | margin-top: -20px;
78 | transform: translate(15px, 15px);
79 | }
80 | 100% {
81 | box-shadow: 30px 0 0 #97bf0d;
82 | margin-top: 0;
83 | }
84 | }
85 | `;
86 |
87 | const Loading = () => (
88 |
91 | );
92 |
93 | export default Loading;
94 |
--------------------------------------------------------------------------------
/src/components/templates/ZaboUpload/ZaboUpload.styled.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/components/templates/ZaboUpload/ZaboUpload.styled.js
--------------------------------------------------------------------------------
/src/components/templates/ZaboUpload/index.js:
--------------------------------------------------------------------------------
1 | import InfoForm from "./InfoForm";
2 | import SelectGroup from "./SelectGroup";
3 | import UploadImage from "./UploadImages";
4 | import UploadProcess from "./UploadProcess";
5 |
6 | const ZaboUpload = {
7 | SelectGroup,
8 | UploadImage,
9 | InfoForm,
10 | UploadProcess,
11 | };
12 |
13 | export default ZaboUpload;
14 |
--------------------------------------------------------------------------------
/src/hoc/paramsToProps.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import get from "lodash.get";
3 |
4 | // eslint-disable-next-line react/display-name
5 | export default (WrappedComponent) => (props) => {
6 | const params = get(props, ["match", "params"]) || {};
7 | const downProps = {
8 | ...props,
9 | ...params,
10 | };
11 | return ;
12 | };
13 |
--------------------------------------------------------------------------------
/src/hoc/withLog.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const withLog = (WrappedComponent) => {
4 | const displayName = WrappedComponent.displayName || WrappedComponent.name;
5 | return class extends React.PureComponent {
6 | constructor(props) {
7 | super(props);
8 | console.log(`%c[${displayName}] : constructor`, "color: #4CAF50;");
9 | }
10 |
11 | componentDidMount() {
12 | console.log(`%c[${displayName}] : componentDidMount`, "color: #2196F3;", this.props);
13 | }
14 |
15 | componentDidUpdate(prevProps, state) {
16 | console.log(
17 | `%c[${displayName}] : componentDidUpdate`,
18 | "color: #2196F3;",
19 | prevProps,
20 | this.props,
21 | );
22 | }
23 |
24 | componentWillUnmount() {
25 | console.log(`%c[${displayName}] : componentWillUnmount`, "color: #F44336;");
26 | }
27 |
28 | render() {
29 | return ;
30 | }
31 | };
32 | };
33 |
34 | export default withLog;
35 |
--------------------------------------------------------------------------------
/src/hoc/withZabo.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useMemo } from "react";
2 | import PropTypes from "prop-types";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import get from "lodash.get";
5 |
6 | import { getZabo } from "store/reducers/zabo";
7 |
8 | const NotFound = () => Zabo Not Found
;
9 |
10 | const withZabo = (WrappedComponent, isPrivate = false, fetch = true) => {
11 | const Sub = (props) => {
12 | const dispatch = useDispatch();
13 | const { zaboId } = props;
14 |
15 | useEffect(() => {
16 | if (fetch) dispatch(getZabo(zaboId));
17 | }, [zaboId]);
18 |
19 | const zabo = useSelector((state) => get(state, ["zabo", "zabos", zaboId]));
20 | if (!zabo) return null;
21 | if (zabo.error) return ;
22 | if (isPrivate && !zabo.isMyZabo) return ;
23 | return ;
24 | };
25 | Sub.propTypes = {
26 | zaboId: PropTypes.string.isRequired,
27 | };
28 | return Sub;
29 | };
30 |
31 | export default withZabo;
32 |
--------------------------------------------------------------------------------
/src/hooks/useInputs.js:
--------------------------------------------------------------------------------
1 | import { useReducer } from "react";
2 |
3 | function reducer(state, action) {
4 | return {
5 | ...state,
6 | [action.name]: action.value,
7 | };
8 | }
9 |
10 | export default function useInputs(initialForm) {
11 | const [state, dispatch] = useReducer(reducer, initialForm);
12 | const onChange = (e) => {
13 | dispatch(e.target);
14 | };
15 | return [state, onChange];
16 | }
17 |
--------------------------------------------------------------------------------
/src/hooks/useSetState.js:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from "react";
2 |
3 | export default function useSetState(initialForm) {
4 | const [state, setStateInternal] = useState(initialForm);
5 | const clone = { ...state };
6 | const setState = (param) => {
7 | if (typeof param === "function") {
8 | setStateInternal((prevState) => ({ ...prevState, ...param(prevState) }));
9 | return;
10 | }
11 | setStateInternal(Object.assign(clone, param));
12 | };
13 | const onChange = (e) => {
14 | setState({
15 | [e.target.name]: e.target.value,
16 | });
17 | };
18 | return [state, setState, onChange];
19 | }
20 |
--------------------------------------------------------------------------------
/src/index.scss:
--------------------------------------------------------------------------------
1 | @import "lib/fonts/stylesheet.css";
2 |
3 | body {
4 | margin: 50px 0 0 0;
5 | padding: 0;
6 | font-family: "NanumSquare", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
7 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
8 | transition: margin-top 0.4s;
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 | /*IE9*/
12 | a::selection {
13 | background-color: transparent;
14 | }
15 | a::-moz-selection {
16 | background-color: transparent;
17 | }
18 | a {
19 | -webkit-user-select: none;
20 | -moz-user-select: -moz-none;
21 | /*IE10*/
22 | -ms-user-select: none;
23 | user-select: none;
24 |
25 | /*You just need this if you are only concerned with android and not desktop browsers.*/
26 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
27 | }
28 | input[type="text"],
29 | textarea,
30 | [contenteditable] {
31 | -webkit-user-select: text;
32 | -moz-user-select: text;
33 | -ms-user-select: text;
34 | user-select: text;
35 | }
36 | .pwa-prompt {
37 | z-index: -1;
38 | max-height: 0;
39 | }
40 | &.pwa-prompt-active {
41 | margin-top: 95px;
42 | header {
43 | top: 40px;
44 | }
45 | .pwa-prompt {
46 | display: unset;
47 | max-height: 40px;
48 | }
49 | }
50 | }
51 |
52 | code {
53 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
54 | }
55 |
56 | * {
57 | box-sizing: border-box;
58 | }
59 |
60 | ::selection {
61 | background: rgba(184, 203, 236, 0.41);
62 | }
63 |
64 | a,
65 | a:hover,
66 | a:visited,
67 | a:focus {
68 | border: none;
69 | outline: none;
70 | text-decoration: none;
71 | color: inherit;
72 | cursor: pointer;
73 | }
74 |
75 | button,
76 | button:focus,
77 | button:active {
78 | outline: none;
79 | cursor: pointer;
80 | }
81 |
82 | #root {
83 | }
84 |
85 | .container {
86 | width: 100%;
87 | margin: 0 auto;
88 | padding: 0 16px;
89 | display: flex;
90 | flex-direction: column;
91 | }
92 |
93 | #modal-root {
94 | position: relative;
95 | z-index: 9999;
96 | }
97 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import "animate.css";
2 | import "./index.scss";
3 | import "slick-carousel/slick/slick.css";
4 | import "slick-carousel/slick/slick-theme.css";
5 |
6 | import React from "react";
7 | import { BrowserRouter as Router } from "react-router-dom";
8 | import { Provider } from "react-redux";
9 | import ReactDOM from "react-dom";
10 | import { ThemeProvider } from "styled-components";
11 |
12 | import store from "store";
13 | import { colors } from "lib/theme";
14 |
15 | import App from "./App";
16 | import boot from "./boot";
17 | import * as serviceWorker from "./serviceWorker";
18 |
19 | boot();
20 |
21 | ReactDOM.render(
22 |
23 |
24 |
25 |
26 |
27 |
28 | ,
29 | document.getElementById("root"),
30 | );
31 |
32 | // If you want your app to work offline and load faster, you can change
33 | // unregister() to register() below. Note this comes with some pitfalls.
34 | // Learn more about service workers: https://bit.ly/CRA-PWA
35 | serviceWorker.register();
36 |
--------------------------------------------------------------------------------
/src/lib/api/auth.ts:
--------------------------------------------------------------------------------
1 | import axios from "lib/axios";
2 |
3 | import type { User } from "lib/interface";
4 |
5 | export const loginCallback = (code: string, state: string, update?: boolean) =>
6 | axios.post<{
7 | token: string;
8 | user: User;
9 | }>("/auth/login/callback", {
10 | code,
11 | state,
12 | update,
13 | });
14 |
15 | export const checkAuth = () => axios.get("/auth");
16 |
--------------------------------------------------------------------------------
/src/lib/api/group.js:
--------------------------------------------------------------------------------
1 | import axios from "../axios";
2 |
3 | /* Auth */
4 | export const updateGroupInfo = ({ curName, name, description, subtitle, category }) =>
5 | axios.post(`/group/${curName}`, {
6 | name,
7 | description,
8 | subtitle,
9 | category,
10 | });
11 | export const updateGroupInfoWithImage = ({ curName, formData }) =>
12 | axios.post(`/group/${curName}`, formData);
13 |
14 | /* Profile */
15 | export const addGroupMember = ({ groupName, userId, role }) =>
16 | axios.put(`/group/${groupName}/member`, { userId, role });
17 | export const updateGroupMember = ({ groupName, userId, role }) =>
18 | axios.post(`/group/${groupName}/member`, { userId, role });
19 | export const removeGroupUser = ({ groupName, userId }) =>
20 | axios.delete(`/group/${groupName}/member`, { data: { userId } });
21 | export const applyNewGroup = (data) => axios.post("/group/apply", data);
22 |
23 | /* Main */
24 | export const getRecommendedGroups = () => axios.get("/group/recommends");
25 |
--------------------------------------------------------------------------------
/src/lib/api/profile.js:
--------------------------------------------------------------------------------
1 | import axios from "../axios";
2 |
3 | export const fetchProfile = (name) => axios.get(`/profile/${name}`);
4 | export const validateName = ({ name }) => axios.get(`/profile/${name}/isValid`);
5 | export const followProfile = ({ name }) => axios.post(`/profile/${name}/follow`);
6 |
--------------------------------------------------------------------------------
/src/lib/api/search.js:
--------------------------------------------------------------------------------
1 | import queryString from "query-string";
2 |
3 | import axios from "../axios";
4 |
5 | export const searchSimpleAPI = ({ query, category }) => {
6 | if (!query && (!category || !category.length)) return Promise.resolve({ zabos: [], groups: [] });
7 | return axios.get(`/search/simple?${queryString.stringify({ query, category })}`);
8 | };
9 |
10 | export const searchAPI = ({ query, category, stat }) => {
11 | if (!query && (!category || !category.length)) return Promise.resolve({ zabos: [], groups: [] });
12 | return axios.get(`/search?${queryString.stringify({ query, category, stat })}`);
13 | };
14 |
15 | export const searchUsers = (query) => {
16 | if (!query) return Promise.resolve([]);
17 | return axios.get(`/search/user?query=${encodeURIComponent(query)}`);
18 | };
19 |
--------------------------------------------------------------------------------
/src/lib/api/user.js:
--------------------------------------------------------------------------------
1 | import axios from "../axios";
2 |
3 | /* Auth */
4 | export const updateUserInfo = (data) => axios.post("/user", data);
5 | export const updateUserInfoWithImage = (formData) => axios.post("/user", formData);
6 | export const setCurrentGroup = (groupName) => axios.post(`/user/currentGroup/${groupName}`);
7 |
--------------------------------------------------------------------------------
/src/lib/api/zabo.js:
--------------------------------------------------------------------------------
1 | import axios from "../axios";
2 |
3 | export const uploadZabo = (formData, onUploadProgress = () => undefined) =>
4 | axios.post("/zabo", formData, {
5 | headers: {
6 | "Content-Type": "multipart/form-data",
7 | },
8 | onUploadProgress: (progressEvent) => {
9 | const percentCompleted = Math.floor((progressEvent.loaded * 100) / progressEvent.total);
10 | // do whatever you like with the percentage complete
11 | // maybe dispatch an action that will update a progress bar or something
12 | onUploadProgress(percentCompleted);
13 | },
14 | });
15 | export const patchZabo = ({ zaboId, data }) => axios.patch(`/zabo/${zaboId}`, data);
16 | export const deleteZabo = ({ zaboId }) => axios.delete(`/zabo/${zaboId}`);
17 |
18 | export const getZabo = (id) => axios.get(`/zabo/${id}`);
19 | export const getHotZaboList = () => axios.get("/zabo/list/hot");
20 | export const getDeadlineZaboList = () => axios.get("/zabo/list/deadline");
21 | export const getZaboList = ({ lastSeen, relatedTo }) =>
22 | axios
23 | .get("/zabo/list", { params: { lastSeen, relatedTo } })
24 | .then((data) => data.filter((item) => item.photos[0] !== undefined));
25 | export const getPins = ({ username, lastSeen }) =>
26 | axios
27 | .get(`/user/${username}/pins`, { params: { lastSeen } })
28 | .then((data) => data.filter((item) => item.photos[0] !== undefined));
29 |
30 | export const toggleZaboPin = (zaboId) => axios.post(`/zabo/${zaboId}/pin`);
31 | export const toggleZaboLike = (zaboId) => axios.post(`/zabo/${zaboId}/like`);
32 | export const toggleZaboShare = (zaboId) => axios.post(`/zabo/${zaboId}/share`);
33 |
34 | export const getGroupZaboList = ({ groupName, lastSeen }) =>
35 | axios.get(`/group/${groupName}/zabo/list`, { params: { lastSeen } });
36 | export const getSearchZaboList = ({ text, lastSeen }) => {
37 | if (!text) return Promise.resolve({ zabos: [], groups: [] });
38 | const { query, category } = text;
39 | return axios.get(
40 | `/search/zabo/list?query=${encodeURIComponent(query)}&category=${encodeURIComponent(category)}`,
41 | { params: { lastSeen } },
42 | );
43 | };
44 |
--------------------------------------------------------------------------------
/src/lib/channel_io.js:
--------------------------------------------------------------------------------
1 | class ChannelService {
2 | constructor() {
3 | this.loaded = false;
4 | this.onloaded = () => {};
5 | setTimeout(() => {
6 | this.loadScript();
7 | this.loaded = true;
8 | this.onloaded();
9 | }, 5000);
10 | }
11 |
12 | loadScript() {
13 | let w = window;
14 | if (w.ChannelIO) {
15 | return (window.console.error || window.console.log || function () {})(
16 | "ChannelIO script included twice.",
17 | );
18 | }
19 | let d = window.document;
20 | let ch = function () {
21 | ch.c(arguments);
22 | };
23 | ch.q = [];
24 | ch.c = function (args) {
25 | ch.q.push(args);
26 | };
27 | w.ChannelIO = ch;
28 | function l() {
29 | if (w.ChannelIOInitialized) {
30 | return;
31 | }
32 | w.ChannelIOInitialized = true;
33 | let s = document.createElement("script");
34 | s.type = "text/javascript";
35 | s.async = true;
36 | s.src = "https://cdn.channel.io/plugin/ch-plugin-web.js";
37 | s.charset = "UTF-8";
38 | let x = document.getElementsByTagName("script")[0];
39 | x.parentNode.insertBefore(s, x);
40 | }
41 | if (document.readyState === "complete") {
42 | l();
43 | } else if (window.attachEvent) {
44 | window.attachEvent("onload", l);
45 | } else {
46 | window.addEventListener("DOMContentLoaded", l, false);
47 | window.addEventListener("load", l, false);
48 | }
49 | }
50 |
51 | boot(settings) {
52 | if (!this.loaded) {
53 | this.onloaded = () => this.boot(settings);
54 | return;
55 | }
56 | window.ChannelIO("boot", settings);
57 | }
58 |
59 | shutdown() {
60 | if (!this.loaded) {
61 | this.onloaded = () => this.shutdown();
62 | return;
63 | }
64 | window.ChannelIO("shutdown");
65 | }
66 | }
67 |
68 | export default new ChannelService();
69 |
--------------------------------------------------------------------------------
/src/lib/fonts/NanumSquareBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/lib/fonts/NanumSquareBold.ttf
--------------------------------------------------------------------------------
/src/lib/fonts/NanumSquareBold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/lib/fonts/NanumSquareBold.woff
--------------------------------------------------------------------------------
/src/lib/fonts/NanumSquareBold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/lib/fonts/NanumSquareBold.woff2
--------------------------------------------------------------------------------
/src/lib/fonts/NanumSquareExtraBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/lib/fonts/NanumSquareExtraBold.ttf
--------------------------------------------------------------------------------
/src/lib/fonts/NanumSquareExtraBold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/lib/fonts/NanumSquareExtraBold.woff
--------------------------------------------------------------------------------
/src/lib/fonts/NanumSquareExtraBold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/lib/fonts/NanumSquareExtraBold.woff2
--------------------------------------------------------------------------------
/src/lib/fonts/NanumSquareLight.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/lib/fonts/NanumSquareLight.ttf
--------------------------------------------------------------------------------
/src/lib/fonts/NanumSquareLight.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/lib/fonts/NanumSquareLight.woff
--------------------------------------------------------------------------------
/src/lib/fonts/NanumSquareLight.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/lib/fonts/NanumSquareLight.woff2
--------------------------------------------------------------------------------
/src/lib/fonts/NanumSquareRegular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/lib/fonts/NanumSquareRegular.ttf
--------------------------------------------------------------------------------
/src/lib/fonts/NanumSquareRegular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/lib/fonts/NanumSquareRegular.woff
--------------------------------------------------------------------------------
/src/lib/fonts/NanumSquareRegular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/lib/fonts/NanumSquareRegular.woff2
--------------------------------------------------------------------------------
/src/lib/fonts/stylesheet.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: "NanumSquare Bold";
3 | src: url('NanumSquareBold.eot?#iefix') format('embedded-opentype'),
4 | url('NanumSquareBold.woff') format('woff'),
5 | url('NanumSquareBold.woff2') format('woff2'),
6 | url('NanumSquareBold.svg') format('svg'),
7 | url('NanumSquareBold.ttf') format('truetype');
8 | font-weight: 700;
9 | }
10 |
11 | @font-face {
12 | font-family: "NanumSquare ExtraBold";
13 | src: url('NanumSquareExtraBold.eot?#iefix') format('embedded-opentype'),
14 | url('NanumSquareExtraBold.woff') format('woff'),
15 | url('NanumSquareExtraBold.woff2') format('woff2'),
16 | url('NanumSquareExtraBold.svg') format('svg'),
17 | url('NanumSquareExtraBold.ttf') format('truetype');
18 | font-weight: 800;
19 | }
20 |
21 | @font-face {
22 | font-family: "NanumSquare Light";
23 | src: url('NanumSquareLight.eot?#iefix') format('embedded-opentype'),
24 | url('NanumSquareLight.woff') format('woff'),
25 | url('NanumSquareLight.woff2') format('woff2'),
26 | url('NanumSquareLight.svg') format('svg'),
27 | url('NanumSquareLight.ttf') format('truetype');
28 | font-weight: 300;
29 | }
30 |
31 | @font-face {
32 | font-family: "NanumSquare";
33 | src: url('NanumSquareRegular.eot?#iefix') format('embedded-opentype'),
34 | url('NanumSquareRegular.woff') format('woff'),
35 | url('NanumSquareRegular.woff2') format('woff2'),
36 | url('NanumSquareRegular.svg') format('svg'),
37 | url('NanumSquareRegular.ttf') format('truetype');
38 | font-weight: 400;
39 | }
--------------------------------------------------------------------------------
/src/lib/i18n/i18next-react-li-postprocessor.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { render, splitReg } from "./i18next-react-react-postprocessor";
4 |
5 | class Li {
6 | type = "postProcessor";
7 |
8 | name = "li";
9 |
10 | /* return manipulated value */
11 | process = (value, key, options, translator) =>
12 | value.split("\n").map((item, index) => (
13 |
14 | {render(item.split(splitReg), options, "1")}
15 |
16 | ));
17 | }
18 |
19 | export default Li;
20 |
--------------------------------------------------------------------------------
/src/lib/i18n/index.js:
--------------------------------------------------------------------------------
1 | import i18n from "i18next";
2 | import LanguageDetector from "i18next-browser-languagedetector";
3 | import enTranslation from "locales/en/translation.json";
4 | import koTranslation from "locales/ko/translation.json";
5 |
6 | import Li from "./i18next-react-li-postprocessor";
7 | import ReactPostProcessor from "./i18next-react-react-postprocessor";
8 |
9 | i18n.on("languageChanged", (lng) => {
10 | if (!lng.split("-")) return;
11 | if (lng.split("-")[0] !== lng) i18n.changeLanguage(lng.split("-")[0]);
12 | });
13 |
14 | i18n
15 | .use(LanguageDetector)
16 | .use(new Li())
17 | .use(new ReactPostProcessor())
18 | .init({
19 | fallbackLng: "ko",
20 | debug: true,
21 | resources: {
22 | en: {
23 | translation: enTranslation,
24 | },
25 | ko: {
26 | translation: koTranslation,
27 | },
28 | },
29 | whitelist: ["ko", "en"],
30 | nonExplicitWhitelist: true,
31 | updateMissing: true,
32 | ns: ["translation"],
33 | fallbackNS: "translation",
34 | // react i18next special options (optional)
35 | postProcess: ["React"],
36 | interpolation: {
37 | escapeValue: false, // not needed for react!!
38 | format: (value, format, lng) => {
39 | if (format === "uppercase") return value.toUpperCase();
40 | return value;
41 | },
42 | formatSeparator: "|",
43 | },
44 | react: {
45 | wait: true,
46 | bindI18n: "languageChanged loaded",
47 | bindStore: "added removed",
48 | nsMode: "default",
49 | },
50 | });
51 |
52 | export default i18n;
53 |
--------------------------------------------------------------------------------
/src/lib/immutable.js:
--------------------------------------------------------------------------------
1 | import Immutable from "immutable";
2 | import Serialize from "remotedev-serialize";
3 |
4 | const serializer = Serialize.immutable(Immutable);
5 |
6 | export default serializer;
7 |
--------------------------------------------------------------------------------
/src/lib/interface/group.ts:
--------------------------------------------------------------------------------
1 | import type { Group as GroupSchema } from "./schemas";
2 |
3 | export interface Group extends GroupSchema {
4 | zabosCount?: number;
5 | followersCount?: number;
6 | isPending?: boolean;
7 | }
8 |
--------------------------------------------------------------------------------
/src/lib/interface/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./group";
2 | export * from "./user";
3 | export * from "./zabo";
4 |
--------------------------------------------------------------------------------
/src/lib/interface/schemas/board.ts:
--------------------------------------------------------------------------------
1 | import type { MongoSchema } from "../utils/mongo";
2 |
3 | export interface Board extends MongoSchema {
4 | title: string;
5 | /* For further usage */
6 | description?: string;
7 | category?: string;
8 | isPrivate?: boolean;
9 | pins?: string[];
10 | }
11 |
--------------------------------------------------------------------------------
/src/lib/interface/schemas/group.ts:
--------------------------------------------------------------------------------
1 | import type { MongoSchema } from "../utils/mongo";
2 | import type { User } from ".";
3 |
4 | export interface GroupApply extends MongoSchema {
5 | name: string;
6 | subtitle?: string;
7 | description?: string;
8 | profilePhoto?: string;
9 | backgroundPhoto?: string;
10 | category?: string[];
11 | members: {
12 | user: User;
13 | role?: "admin" | "editor";
14 | }[];
15 | purpose?: string;
16 | isBusiness?: boolean;
17 | }
18 |
19 | export interface Group extends GroupApply {
20 | isPreRegistered?: boolean;
21 | level?: number;
22 | revisionHistory?: RevisionHistory[];
23 | recentUpload?: string;
24 | followers?: User[] | string[];
25 | score: number;
26 | }
27 |
28 | interface RevisionHistory extends MongoSchema {
29 | prev?: string;
30 | next?: string;
31 | }
32 |
--------------------------------------------------------------------------------
/src/lib/interface/schemas/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./board";
2 | export * from "./group";
3 | export * from "./user";
4 | export * from "./zabo";
5 |
--------------------------------------------------------------------------------
/src/lib/interface/schemas/user.ts:
--------------------------------------------------------------------------------
1 | import type { MongoSchema } from "../utils/mongo";
2 | import type { Board, Group, Zabo } from "./index";
3 |
4 | export interface User extends MongoSchema {
5 | sso_uid?: string;
6 | sso_sid: string;
7 | email?: string;
8 | username: string;
9 | description?: string;
10 | profilePhoto?: string;
11 | backgroundPhoto?: string;
12 | isAdmin?: boolean;
13 | /* From SSO */
14 | gender?: string;
15 | birthday?: string;
16 | flags?: string[];
17 | firstName?: string;
18 | lastName?: string;
19 | koreanName?: string;
20 | kaistId?: string;
21 | sparcsId?: string;
22 | facebookId?: string;
23 | // TODO: check if typo
24 | tweeterId?: string;
25 | studentId?: string;
26 | kaistEmail?: string;
27 | kaistPersonType?: string;
28 | kaistInfoTime?: string;
29 | kaistStatus?: string;
30 | /* From SSO */
31 | boards?: Board[] | string[];
32 | groups?: Group[] | string[];
33 | currentGroup: Group | string;
34 | type?: string;
35 | followings?: (
36 | | { followee: string | User; onModel: "User" }
37 | | { followee: string | Group; onModel: "Group" }
38 | )[];
39 |
40 | followers?: User[] | string[];
41 | recommends?: Zabo[] | string[];
42 | interests?: Record;
43 | interestMeta?: {
44 | lastCountedDate?: string;
45 | };
46 | }
47 |
--------------------------------------------------------------------------------
/src/lib/interface/schemas/zabo.ts:
--------------------------------------------------------------------------------
1 | import type { MongoSchema } from "../utils/mongo";
2 | import type { User, Group, Board } from "./index";
3 |
4 | export interface Zabo extends MongoSchema {
5 | createdBy?: User | string;
6 | owner?: Group | string;
7 | photos?: {
8 | url?: string;
9 | width?: number;
10 | height?: number;
11 | }[];
12 | meta?: {
13 | w?: number;
14 | h?: number;
15 | };
16 | title: string;
17 | description: string;
18 | category?: string[];
19 | views?: number;
20 | effectiveViews?: number;
21 | schedules?: {
22 | title?: string;
23 | startAt: string;
24 | endAt?: string;
25 | eventType?: "행사" | "신청";
26 | }[];
27 | showBoard: boolean;
28 |
29 | pins?: Board[] | string[];
30 | likes?: ZaboLike[];
31 | likesWithTime?: ZaboLike[];
32 | score?: number;
33 | scoreMeta: {
34 | lastLikeCount?: number;
35 | lastLikeTimeMA?: string;
36 | lastCountedViewDate?: string;
37 | lastViewTimeMA?: string;
38 | };
39 | }
40 |
41 | export interface ZaboLike extends MongoSchema {
42 | user?: User | string;
43 | }
44 |
--------------------------------------------------------------------------------
/src/lib/interface/user.ts:
--------------------------------------------------------------------------------
1 | import type { GroupApply as GroupApplySchema, User as UserSchema } from "./schemas";
2 |
3 | export interface User extends UserSchema {
4 | pendingGroups?: Pick[];
5 | }
6 |
--------------------------------------------------------------------------------
/src/lib/interface/utils/mongo.ts:
--------------------------------------------------------------------------------
1 | export interface MongoSchema {
2 | _id: string;
3 | createdAt?: string;
4 | updatedAt?: string;
5 | }
6 |
--------------------------------------------------------------------------------
/src/lib/interface/zabo.ts:
--------------------------------------------------------------------------------
1 | import type { Zabo as ZaboSchema } from "./schemas";
2 |
3 | // TODO: Write zabo type
4 | export type Zabo = ZaboSchema;
5 |
--------------------------------------------------------------------------------
/src/lib/mixins.js:
--------------------------------------------------------------------------------
1 | import { css } from "styled-components";
2 |
3 | export const flexCenter = css`
4 | display: flex;
5 | justify-content: center;
6 | align-items: center;
7 | `;
8 |
--------------------------------------------------------------------------------
/src/lib/propTypes.js:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 |
3 | export const ZaboType = PropTypes.shape({
4 | _id: PropTypes.string,
5 | title: PropTypes.string,
6 | owner: PropTypes.shape({
7 | _id: PropTypes.string,
8 | name: PropTypes.string,
9 | profilePhoto: PropTypes.string,
10 | subtitle: PropTypes.string,
11 | }),
12 | description: PropTypes.string,
13 | category: PropTypes.arrayOf(PropTypes.string),
14 | photos: PropTypes.arrayOf(
15 | PropTypes.shape({
16 | height: PropTypes.number,
17 | width: PropTypes.number,
18 | url: PropTypes.string,
19 | }),
20 | ),
21 | views: PropTypes.number,
22 | effectiveViews: PropTypes.number,
23 | createdAt: PropTypes.string,
24 | schedules: PropTypes.arrayOf(
25 | PropTypes.shape({
26 | title: PropTypes.string,
27 | startAt: PropTypes.string,
28 | endAt: PropTypes.string,
29 | type: PropTypes.string,
30 | }),
31 | ),
32 | showBoard: PropTypes.bool,
33 | isLiked: PropTypes.bool,
34 | isPinned: PropTypes.bool,
35 | isMyZabo: PropTypes.bool,
36 | });
37 |
38 | export const GroupType = PropTypes.shape({
39 | _id: PropTypes.string,
40 | name: PropTypes.string,
41 | profilePhoto: PropTypes.string,
42 | stats: PropTypes.shape({
43 | // TODO: Generate stats in server
44 | zaboCount: PropTypes.number,
45 | followerCount: PropTypes.number,
46 | recentUploadDate: PropTypes.string,
47 | }),
48 | myRole: PropTypes.oneOf(["admin", "editor"]),
49 | });
50 |
51 | export const UserType = PropTypes.shape({
52 | _id: PropTypes.string,
53 | email: PropTypes.string,
54 | username: PropTypes.string,
55 | description: PropTypes.string,
56 | profilePhoto: PropTypes.string,
57 | backgroundPhoto: PropTypes.string,
58 | birthday: PropTypes.string,
59 | lastName: PropTypes.string,
60 | firstName: PropTypes.string,
61 | studentId: PropTypes.string,
62 | koreanName: PropTypes.string,
63 | boards: PropTypes.arrayOf(
64 | PropTypes.shape({
65 | pinsCount: PropTypes.number,
66 | pins: PropTypes.array,
67 | }),
68 | ),
69 | currentGroup: PropTypes.oneOfType([GroupType, PropTypes.string]),
70 | groups: PropTypes.arrayOf(PropTypes.oneOfType([GroupType, PropTypes.string])),
71 | stats: PropTypes.shape({
72 | // TODO: Change shape from server
73 | likesCount: PropTypes.number,
74 | followingsCount: PropTypes.number,
75 | }),
76 | });
77 |
--------------------------------------------------------------------------------
/src/lib/storage.js:
--------------------------------------------------------------------------------
1 | export default (function () {
2 | try {
3 | const st = localStorage || {};
4 | return {
5 | setItem: (key, object) => {
6 | st[key] = typeof object === "string" ? object : JSON.stringify(object);
7 | },
8 | getItem: (key, parse = true) => {
9 | if (!st[key]) {
10 | return null;
11 | }
12 | const value = st[key];
13 |
14 | try {
15 | const parsed = parse ? JSON.parse(value) : value;
16 | return parsed;
17 | } catch (e) {
18 | return value;
19 | }
20 | },
21 | removeItem: (key) => {
22 | if (localStorage) {
23 | return localStorage.removeItem(key);
24 | }
25 | return delete st[key];
26 | },
27 | };
28 | } catch (err) {
29 | console.warn(err);
30 | setTimeout(() => alert("Cookie disabled"), 1000);
31 | return {
32 | setItem: (key, object) => "",
33 | getItem: (key) => "",
34 | removeItem: (key) => "",
35 | };
36 | }
37 | })();
38 |
--------------------------------------------------------------------------------
/src/lib/theme.ts:
--------------------------------------------------------------------------------
1 | export const colors = {
2 | main: "#143441",
3 | point: "#ff5d5d",
4 | sub: "#35d48d",
5 | textMain: "#202020",
6 | gray100: "#202020",
7 | gray90: "#363636",
8 | gray80: "#4d4d4d",
9 | gray70: "#636363",
10 | gray60: "#797979",
11 | gray50: "#8f8f8f",
12 | gray40: "#a6a6a6",
13 | gray30: "#bcbcbc",
14 | gray20: "#d2d2d2",
15 | gray10: "#e9e9e9",
16 | gray5: "#f4f4f4",
17 | gray3: "#f8f8f8",
18 | gray2: "#fbfbfb",
19 | gray1: "#fdfdfd",
20 | cobalt: "#194386",
21 | black: "#333333",
22 | red: "#e60909",
23 | red50: "#FA6B6B",
24 | green50: "#35d48d",
25 | green: "#00a97d",
26 | pinkishGrey: "#cccccc",
27 | border: "#cccccc",
28 | pinkishGreyTwo: "#d0d0d0",
29 | deepBlue: "#194386",
30 | white: "white",
31 | teal: "#00a97d",
32 | tealishGreen: "#05d27e",
33 | azure: "#0090ff",
34 | brownishGrey: "#666666",
35 | warmGrey: "#999999",
36 | veryLightBlue: "#dee7f2",
37 | none: "rgba(0,0,0,0)",
38 | } as const;
39 |
--------------------------------------------------------------------------------
/src/lib/utils/selector.ts:
--------------------------------------------------------------------------------
1 | import { get } from "lodash";
2 | import { createSelector, ParametricSelector, Selector } from "reselect";
3 | import { IGroup, IJwt, IZabo, IZaboMap } from "types/index.d";
4 | import { IState } from "types/store.d";
5 |
6 | import { CHECK_AUTH } from "store/reducers/auth";
7 |
8 | const decodedTokenSelector: Selector = (state: IState) =>
9 | get(state, ["auth", "jwt"]);
10 | const isDecodedTokenAlive = (decoded: IJwt | undefined) => {
11 | if (decoded) {
12 | return 1000 * decoded.exp - new Date().getTime() >= 5000;
13 | }
14 | return false;
15 | };
16 | const decodedTokenLifetime = (decoded: IJwt | undefined): number => {
17 | if (decoded) {
18 | const remain = decoded.exp * 1000 - Date.now();
19 | return remain > 0 ? remain : 0;
20 | }
21 | return 0;
22 | };
23 |
24 | export const isAuthedSelector = createSelector(decodedTokenSelector, isDecodedTokenAlive);
25 | export const tokenTimeLeft = createSelector(decodedTokenSelector, decodedTokenLifetime);
26 |
27 | export const isAdminSelector = (state: IState) => get(state, ["auth", "info", "isAdmin"]);
28 | export const authPendingSelector = (state: IState) => state.pender.pending[CHECK_AUTH];
29 | export const isAdminOrPendingSelector = (state: IState) => [
30 | isAdminSelector(state),
31 | authPendingSelector(state),
32 | ];
33 |
34 | const zabosSelector: ParametricSelector = (
35 | state: IState,
36 | zaboIds: string[],
37 | ): [IZaboMap, string[]] => [get(state, ["zabo", "zabos"]), zaboIds];
38 | const zabosComputer = ([zabos, zaboIds]: [IZaboMap, string[]]) =>
39 | zaboIds.map((id: string) => zabos[id]);
40 |
41 | // TODO: Factory function (due to cache size of 1)
42 | export const zaboListFromIdsSelector = createSelector(zabosSelector, zabosComputer);
43 |
44 | const zaboSelector = (state: IState, zaboId: string) => get(state, ["zabo", "zabos", zaboId]);
45 | const ownGroupsSelector = (state: IState) => get(state, ["auth", "info", "groups"]);
46 | const isMyZabo = (zabo: IZabo, groups: IGroup[]) =>
47 | !!groups.find((group: IGroup) => zabo && group._id === get(zabo, ["owner", "_id"]));
48 | export const isMyZaboSelector = createSelector([zaboSelector, ownGroupsSelector], isMyZabo);
49 |
--------------------------------------------------------------------------------
/src/lib/utils/style.ts:
--------------------------------------------------------------------------------
1 | import { css } from "styled-components";
2 |
3 | export const mediaSizes = {
4 | desktop: 992,
5 | tablet: 640,
6 | mobile: 320,
7 | } as const;
8 |
9 | const wrapMedia =
10 | (size: number) =>
11 | (...args: ReturnType | Parameters) =>
12 | css`
13 | @media (min-width: ${size / 16}em) {
14 | ${Array.isArray(args) && args.length > 1 ? css(...args) : args};
15 | }
16 | `;
17 |
18 | export const media = {
19 | desktop: wrapMedia(mediaSizes.desktop),
20 | tablet: wrapMedia(mediaSizes.tablet),
21 | mobile: wrapMedia(mediaSizes.mobile),
22 | } as const;
23 |
24 | export const get =
25 | , V>(prop: keyof P, defaultValue: V) =>
26 | (props: P & { theme: P }) =>
27 | props[prop] || props.theme[prop] || defaultValue;
28 | export const getSize =
29 |
(prop: keyof P) =>
30 | (props: P) =>
31 | typeof props[prop] === "number" ? `${props[prop]}px` : props[prop];
32 |
33 | export const cx = (...classNames: (string | Record)[]) => {
34 | let result = "";
35 | classNames.forEach((className) => {
36 | if (typeof className === "string") result += `${className} `;
37 | if (typeof className === "object") {
38 | for (const key in className) {
39 | // eslint-disable-next-line no-prototype-builtins
40 | if (className.hasOwnProperty(key)) if (className[key]) result += `${key} `;
41 | }
42 | }
43 | });
44 | return result.trim();
45 | };
46 |
--------------------------------------------------------------------------------
/src/lib/variables.js:
--------------------------------------------------------------------------------
1 | export const ZABO_CATEGORIES = [
2 | "행사",
3 | "공연",
4 | "축제",
5 | "세미나",
6 | "교육",
7 | "모임",
8 | "이벤트",
9 | "공모전",
10 | "전시",
11 | "공지",
12 | "모집",
13 | "채용",
14 | "봉사",
15 | "오픈동방",
16 | "데모데이",
17 | ];
18 | export const GROUP_CATEGORIES = ["학생단체", "동아리", "학교 부서", "스타트업", "기업"];
19 | export const GROUP_CATEGORIES_2 = [
20 | "과학생회",
21 | "자치단체",
22 | "총학생회",
23 | "생활문화",
24 | "예술",
25 | "음악",
26 | "종교사회",
27 | "체육",
28 | "학술",
29 | "창업",
30 | ];
31 | /* eslint-disable */
32 | export const GROUP_CATEGORY_HIERARCHIY = {
33 | 학생단체: ["과학생회", "자치단체", "총학생회"],
34 | 동아리: ["생활문화", "예술", "음악", "종교사회", "체육", "학술", "창업"],
35 | };
36 | export const RESERVED_ROUTES_USERNAME_EXCEPTIONS = ["auth", "settings", "admin"];
37 |
38 | export const alerts = {
39 | login: "로그인이 필요합니다. SSO를 사용해 로그인 하시겠습니까?",
40 | upload: "자보를 게시하시겠습니까?",
41 | edit: "자보를 수정하시겠습니까?",
42 | del: "정말로 자보를 삭제하시겠습니까?",
43 | addMember: "멤버를 추가하시겠습니까?",
44 | updateMember: "멤버 권한을 변경하시겠습니까?",
45 | deleteMember: "정말로 멤버를 삭제하시겠습니까?",
46 | };
47 |
--------------------------------------------------------------------------------
/src/locales/en/Home.json:
--------------------------------------------------------------------------------
1 | {
2 | "zabo": "ZABOasdf asd asdfasdf sadfasdf"
3 | }
4 |
--------------------------------------------------------------------------------
/src/locales/en/translation.json:
--------------------------------------------------------------------------------
1 | {
2 | "home": "Home",
3 | "login": "Log In",
4 | "logout": "Log Out",
5 | "signup": "Sign Up"
6 | }
7 |
--------------------------------------------------------------------------------
/src/locales/ko/Home.json:
--------------------------------------------------------------------------------
1 | {
2 | "zabo": "자보"
3 | }
4 |
--------------------------------------------------------------------------------
/src/locales/ko/translation.json:
--------------------------------------------------------------------------------
1 | {
2 | "home": "홈",
3 | "login": "로그인",
4 | "logout": "로그아웃",
5 | "signup": "회원가입"
6 | }
7 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | declare namespace NodeJS {
3 | interface ProcessEnv {
4 | NODE_ENV: "development" | "production" | "test" | "ci";
5 | PUBLIC_URL: string;
6 | EXTEND_ESLINT: string;
7 | NODE_PATH: string;
8 | }
9 | }
10 | interface Window {
11 | Stripe: any;
12 | }
13 |
--------------------------------------------------------------------------------
/src/setupProxy.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/no-var-requires
2 | const proxy = require("http-proxy-middleware");
3 |
4 | module.exports = function (app) {
5 | app.use(
6 | "/api",
7 | proxy({
8 | target: "http://localhost:6001",
9 | changeOrigin: true,
10 | }),
11 | );
12 |
13 | app.use(
14 | "/admin",
15 | proxy({
16 | target: "http://localhost:6001",
17 | changeOrigin: true,
18 | }),
19 | );
20 |
21 | app.use(
22 | "/s",
23 | proxy({
24 | target: "http://localhost:6001",
25 | pathRewrite: {
26 | "^/s": "/api/s",
27 | },
28 | }),
29 | );
30 | };
31 |
--------------------------------------------------------------------------------
/src/static/hd/zhangjiajie-landscape.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/hd/zhangjiajie-landscape.jpg
--------------------------------------------------------------------------------
/src/static/hd/zhangjiajie-portrait.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/hd/zhangjiajie-portrait.jpg
--------------------------------------------------------------------------------
/src/static/hd/zhangjiajie-snow.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/hd/zhangjiajie-snow.jpg
--------------------------------------------------------------------------------
/src/static/icon/arrow.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/static/icon/baseline-expand_more-24px.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/icon/category/all.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/static/icon/category/exhibition.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/static/icon/category/notice.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/static/icon/category/recruit.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/static/icon/help.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/static/icon/person_add-24px.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/icon/remove-24px.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/icon/rightArrow.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/static/images/add.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/images/add_gray.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/static/images/banner_poster.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/images/banner_poster.jpg
--------------------------------------------------------------------------------
/src/static/images/banner_poster.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/images/banner_poster.webp
--------------------------------------------------------------------------------
/src/static/images/banner_sparcs.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/images/banner_sparcs.jpg
--------------------------------------------------------------------------------
/src/static/images/banner_sparcs.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/images/banner_sparcs.webp
--------------------------------------------------------------------------------
/src/static/images/bookmark.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/static/images/bookmarkEmpty.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/static/images/calendar.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/images/cancel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/images/cancel.png
--------------------------------------------------------------------------------
/src/static/images/check_gray.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/images/check_gray.png
--------------------------------------------------------------------------------
/src/static/images/chevron_home.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/images/chevron_left.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/static/images/chevron_right.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/static/images/circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/static/images/cross.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/static/images/defaultProfile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/images/defaultProfile.png
--------------------------------------------------------------------------------
/src/static/images/delete.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/images/group.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/static/images/groupDefaultProfile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/images/groupDefaultProfile.png
--------------------------------------------------------------------------------
/src/static/images/landing_background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/images/landing_background.jpg
--------------------------------------------------------------------------------
/src/static/images/leftArrow-navy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/images/leftArrow-navy.png
--------------------------------------------------------------------------------
/src/static/images/leftArrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/images/leftArrow.png
--------------------------------------------------------------------------------
/src/static/images/leftScroll.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/images/leftScroll.png
--------------------------------------------------------------------------------
/src/static/images/like.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/static/images/likeEmpty.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/static/images/main.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/images/main.jpg
--------------------------------------------------------------------------------
/src/static/images/notFoundImage.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/images/notFoundImage.jpg
--------------------------------------------------------------------------------
/src/static/images/recruitAscii.js:
--------------------------------------------------------------------------------
1 | export default `
2 | ...........................................................................................................
3 | ...........................................................................................................
4 | ................','........................................................................................
5 | ...............,lo;........................................................................................
6 | ......';,.....,lxxc'.......................................................................................
7 | ......'ll;...,lxxxo;.......................................................................................
8 | ......'ldo;',lxxxxxc.......:oddddddo;..oxxdddddl;....,ldxo;...,ldddodddo:'..':looooool:'.;cooooool;........
9 | ......'lddoclxxxxxo;......;dxdc;.......oxd;..;dxo;..,ddddo,...,ldo:,,;lxd:',ldoc;,,;cl:',ldoc,,,;;'........
10 | ......'lddooxxxxd:'.......;oxxdlc;.....oxd;..;dxo;.,ddc:odl'..,ldo:,';ldd:,cddc'........'coooc::;,.........
11 | ......'ldloxxxdc,..........,:coddxxd;..oxxdddddo;.,oxo:;ldd:..,ldddooddoc''cddc'.........';:cloooo:'.......
12 | ......,codxxxxo:;,''............;oxxo;.oxd;......,cxddooodxo;.,lddc:cddo;..;odo:'''';:;'',;,'',codl;.......
13 | ......':oxxxdddddo:,......;dxdooodxdc;.oxo;.....;dxl;'.',:dxl,,ldo;.':odo;..,codollodoc,;loollloooc'.......
14 | .......,;:::ldxdc,..........;ccccc;....oxo:.....'oc:,....';::,';::'...,::;'...,;::::;,'..,;:::::;,'........
15 | ...........;ooc,...........................................................................................
16 | ..........;l:,.............................................................................................
17 | .........,;'...............................................................................................
18 | ...........................................................................................................
19 | ...........................................................................................................
20 | ......................🎂 개발자 도구를 열어보셨군요. 당신은 개발자, 스팍스에 지원하세요! 🎂.............................
21 | ...........................................................................................................
22 | ...........................................................................................................
23 | `;
24 |
--------------------------------------------------------------------------------
/src/static/images/rightArrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/images/rightArrow.png
--------------------------------------------------------------------------------
/src/static/images/rightArrowForward.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/images/rightArrowForward.png
--------------------------------------------------------------------------------
/src/static/images/rightGrayArrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/images/rightGrayArrow.png
--------------------------------------------------------------------------------
/src/static/images/rightScroll.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/images/rightScroll.png
--------------------------------------------------------------------------------
/src/static/images/search-icon-navy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/images/search-icon-navy.png
--------------------------------------------------------------------------------
/src/static/images/search-icon-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/images/search-icon-white.png
--------------------------------------------------------------------------------
/src/static/images/uploadImg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/images/uploadImg.png
--------------------------------------------------------------------------------
/src/static/images/user.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/static/images/whiteBookmakrEmpty.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/static/images/whiteBookmark.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/static/images/whiteLike.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/static/images/whiteLikeEmpty.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/static/images/whiteShare.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/logo/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/logo/logo.png
--------------------------------------------------------------------------------
/src/static/logo/sparcs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparcs-kaist/zabo-front-reactjs/d22a0e40efc5c68eea48790b1dd24438aa7a61fd/src/static/logo/sparcs.png
--------------------------------------------------------------------------------
/src/static/logo/sparcs.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/store/example.ts:
--------------------------------------------------------------------------------
1 | type State = object;
2 | interface IAction {
3 | type: string;
4 | [key: string]: any;
5 | }
6 | interface IReducer {
7 | (state: State, action: IAction): State;
8 | }
9 | interface IListener {
10 | (state: State): void;
11 | }
12 |
13 | const createStore = (reducer: IReducer) => {
14 | let state: State = {};
15 | let listeners: IListener[] = [];
16 |
17 | const getState = () => state;
18 | const dispatch = (action: IAction) => {
19 | state = reducer(state, action);
20 | listeners.forEach((listener) => listener(state));
21 | };
22 | const subscribe = (listener: IListener) => {
23 | listeners.push(listener);
24 | return () => {
25 | listeners = listeners.filter((l) => l !== listener);
26 | };
27 | };
28 | dispatch({ type: "Init" });
29 | return {
30 | getState,
31 | dispatch,
32 | subscribe,
33 | };
34 | };
35 |
36 | const reducer = (
37 | state: State = {
38 | /* Prev State */
39 | },
40 | action: IAction,
41 | ) => ({
42 | // New State
43 | });
44 |
45 | export default createStore;
46 |
47 | /*
48 | {
49 | showVisualText: false,
50 | app: {
51 |
52 | },
53 | todo: {
54 |
55 | }
56 | }
57 |
58 | ADD_TODO : 아이템을 추가
59 | DELETE_TODO : 아이템을 삭제
60 | UPDATE_TODO : 아이템을 업데이트
61 |
62 | SHOW_TEXT: 텍스트를 보이게 한다
63 | HIDE_TEXT: 텍스트를 감춘다.
64 |
65 | */
66 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { applyMiddleware, createStore } from "redux";
2 | import { composeWithDevTools } from "redux-devtools-extension";
3 | import penderMiddleware from "redux-pender";
4 |
5 | import persist from "./persist";
6 | import rootReducer from "./reducers";
7 |
8 | const composeEnhancers = composeWithDevTools({
9 | actionBlacklist: ["@@redux-pender/SUCCESS", "@@redux-pender/FAILURE", "@@redux-pender/PENDING"],
10 | maxAge: 1000,
11 | });
12 |
13 | const store = createStore(
14 | rootReducer,
15 | {}, // Initial state
16 | composeEnhancers(applyMiddleware(penderMiddleware(), persist)),
17 | );
18 |
19 | if (rootReducer.hot) {
20 | rootReducer.hot.accept("./reducers", () => {
21 | const nextRootReducer = import("./reducers");
22 | store.replaceReducer(nextRootReducer);
23 | });
24 | }
25 |
26 | export default store;
27 |
--------------------------------------------------------------------------------
/src/store/persist.ts:
--------------------------------------------------------------------------------
1 | import { Store } from "redux";
2 | import { Action } from "redux-actions";
3 |
4 | import storage from "lib/storage";
5 |
6 | const persistUpload =
7 | (store: Store) => (next: (action: Action) => any) => (action: Action) => {
8 | const result = next(action);
9 |
10 | if (action.type.substring(0, 6) === "upload") {
11 | const { upload } = store.getState();
12 | const { step, imagesSelected, submitted, ...persisted } = upload;
13 | persisted.images = [];
14 | persisted.date = new Date();
15 | storage.setItem("uploadPersist", JSON.stringify(persisted));
16 | }
17 |
18 | return result; // 여기서 반환하는 값은 store.dispatch(ACTION_TYPE) 했을때의 결과로 설정됩니다
19 | };
20 |
21 | export default persistUpload;
22 |
--------------------------------------------------------------------------------
/src/store/reducers/app.ts:
--------------------------------------------------------------------------------
1 | import produce from "immer";
2 | import { Action, createAction, handleActions } from "redux-actions";
3 |
4 | // action types
5 | const UPDATE_WINDOW_SIZE = "app/UPDATE_WINDOW_SIZE";
6 |
7 | // action creators
8 | export const setWindowSize = createAction(UPDATE_WINDOW_SIZE);
9 |
10 | interface IWindowSize {
11 | width: number;
12 | height: number;
13 | }
14 | export interface IAppState {
15 | windowSize: IWindowSize;
16 | }
17 |
18 | // initial state
19 | const initialState: IAppState = {
20 | windowSize: {
21 | width: window.innerWidth,
22 | height: window.innerHeight,
23 | },
24 | };
25 |
26 | // reducer
27 | export default handleActions(
28 | {
29 | [UPDATE_WINDOW_SIZE]: (state, action: Action) =>
30 | produce(state, (draft: IAppState) => {
31 | draft.windowSize = action.payload;
32 | }),
33 | },
34 | initialState,
35 | );
36 |
--------------------------------------------------------------------------------
/src/store/reducers/index.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "redux";
2 | import { penderReducer } from "redux-pender";
3 | // import immutableTransform from 'redux-persist-transform-immutable';
4 | // import storage from 'redux-persist/lib/storage';
5 | // import { persistReducer } from 'redux-persist';
6 |
7 | import admin from "./admin";
8 | import app from "./app";
9 | import auth from "./auth";
10 | import profile from "./profile";
11 | import upload from "./upload";
12 | import zabo from "./zabo";
13 |
14 | const modules = {
15 | admin,
16 | app,
17 | auth,
18 | profile,
19 | upload,
20 | zabo,
21 | pender: penderReducer,
22 | };
23 |
24 | // const persistConfig = {
25 | // transforms: [immutableTransform ({
26 | // blacklist: ['groupSelected', 'imagesSelected', 'infoWritten'],
27 | // })],
28 | // key: 'upload',
29 | // storage,
30 | // };
31 | //
32 | // modules.upload = persistReducer (persistConfig, modules.upload);
33 |
34 | export default combineReducers(modules);
35 |
--------------------------------------------------------------------------------
/src/stories/index.js:
--------------------------------------------------------------------------------
1 | /* eslint import/no-extraneous-dependencies:0 */
2 | import React from "react";
3 | import { action } from "@storybook/addon-actions";
4 | import { linkTo } from "@storybook/addon-links";
5 | import { storiesOf } from "@storybook/react";
6 | import { Button, Welcome } from "@storybook/react/demo";
7 |
8 | storiesOf("Welcome", module).add("to Storybook", () => );
9 |
10 | storiesOf("Button", module)
11 | .add("with text", () => )
12 | .add("with some emoji", () => (
13 |
18 | ));
19 |
--------------------------------------------------------------------------------
/src/theme/index.js:
--------------------------------------------------------------------------------
1 | import theme from "styled-theming";
2 |
3 | export default {
4 | mode: "light",
5 | };
6 | // define background colours for `mode` theme
7 | export const backgroundColor = theme("mode", {
8 | light: "#fafafa",
9 | dark: "#222",
10 | });
11 |
12 | // define text color for `mode` theme
13 | export const textColor = theme("mode", {
14 | light: "#000",
15 | dark: "#fff",
16 | });
17 |
18 | export const mainColor = theme("mode", {
19 | light: "#1976d2",
20 | dark: "#12397d",
21 | });
22 |
--------------------------------------------------------------------------------
/src/types/index.d.ts:
--------------------------------------------------------------------------------
1 | export interface IJwt {
2 | _id: string;
3 | sid: string;
4 | email: string;
5 | username: string;
6 | iat: number;
7 | exp: number;
8 | iss: string;
9 | }
10 |
11 | export interface IZabo {
12 | _id: string;
13 | title: string;
14 | owner: {
15 | _id: string;
16 | name: string;
17 | profilePhoto: string;
18 | subtitle: string;
19 | };
20 | description: string;
21 | category: string[];
22 | photos: [
23 | {
24 | height: number;
25 | width: number;
26 | url: string;
27 | },
28 | ];
29 | views: number;
30 | effectiveViews: number;
31 | createdAt: string;
32 | schedules: [
33 | {
34 | title: string;
35 | startAt: string;
36 | endAt: string;
37 | type: string;
38 | },
39 | ];
40 | showBoard: boolean;
41 | isLiked: boolean;
42 | isPinned: boolean;
43 | isMyZabo: boolean;
44 | }
45 |
46 | export interface IZaboMap {
47 | [key: string]: IZabo;
48 | }
49 |
50 | export interface IGroup {
51 | _id: string;
52 | name: string;
53 | profilePhoto: string;
54 | stats: {
55 | zaboCount: number;
56 | followerCount: number;
57 | recentUploadDate: string;
58 | };
59 | myRole: "admin" | "editor";
60 | }
61 |
62 | export interface IGroupMap {
63 | [key: string]: IGroup;
64 | }
65 |
66 | export interface IUser {
67 | _id: string;
68 | // eslint-disable-next-line camelcase
69 | sso_uid?: string;
70 | // eslint-disable-next-line camelcase
71 | sso_sid?: string;
72 | gender?: string;
73 | email?: string;
74 | kaistPersonType?: string;
75 | isAdmin?: boolean;
76 | flags: string[];
77 | pendingGroups: IGroup[];
78 | username: string;
79 | description?: string;
80 | profilePhoto?: string;
81 | backgroundPhoto?: string;
82 | birthday?: string;
83 | lastName?: string;
84 | firstName?: string;
85 | studentId?: string;
86 | koreanName?: string;
87 | boards?: {
88 | pinsCount: number;
89 | pins: any[];
90 | }[];
91 | currentGroup?: IGroup | string | null;
92 | groups: IGroup[];
93 | stats?: {
94 | likesCount: number;
95 | followingsCount: number;
96 | };
97 | }
98 |
99 | export interface IUserMap {
100 | [key: string]: IGroup;
101 | }
102 |
103 | export type IProfile = IUser | IGroup;
104 |
105 | export interface IProfileMap {
106 | [key: string]: IProfile;
107 | }
108 |
--------------------------------------------------------------------------------
/src/types/store.d.ts:
--------------------------------------------------------------------------------
1 | import { PenderState } from "redux-pender";
2 |
3 | import { IAdminState } from "store/reducers/admin";
4 | import { IAppState } from "store/reducers/app";
5 | import { IAuthState } from "store/reducers/auth";
6 | import { IProfileState } from "store/reducers/profile";
7 | import { IUploadState } from "store/reducers/upload";
8 | import { IZaboState } from "store/reducers/zabo";
9 |
10 | export interface IState {
11 | admin: IAdminState;
12 | app: IAppState;
13 | auth: IAuthState;
14 | profile: IProfileState;
15 | upload: IUploadState;
16 | zabo: IZaboState;
17 | pender: PenderState;
18 | }
19 |
--------------------------------------------------------------------------------
/tools/modules/colors.inc.sh:
--------------------------------------------------------------------------------
1 | DEFAULT=$(echo -en '\033[39m')
2 | BLK=$(echo -en '\033[30m')
3 | RED=$(echo -en '\033[31m')
4 | GRN=$(echo -en '\033[32m')
5 | YLW=$(echo -en '\033[33m')
6 | BLU=$(echo -en '\033[34m')
7 | MGT=$(echo -en '\033[35m')
8 | CYN=$(echo -en '\033[36m')
9 | LGRY=$(echo -en '\033[37m')
10 | DGRY=$(echo -en '\033[90m')
11 | LRED=$(echo -en '\033[91m')
12 | LGRN=$(echo -en '\033[92m')
13 | LYLW=$(echo -en '\033[93m')
14 | LBLU=$(echo -en '\033[94m')
15 | LMGT=$(echo -en '\033[95m')
16 | LCYN=$(echo -en '\033[96m')
17 | WHT=$(echo -en '\033[97m')
18 | ORNG=$(echo -en '\033[38;5;166m')
19 |
20 | RST=$(echo -en '\033[00m')
21 | BLD=$(echo -en '\033[1;2m')
22 | RBLD=$(echo -en '\033[21;22m')
23 | UND=$(echo -en '\033[4m')
24 | RUND=$(echo -en '\033[24m')
--------------------------------------------------------------------------------
/tools/modules/verbose.inc.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | verbose()
3 | {
4 | if $1; then
5 | exec 4>&2 3>&1
6 | IS_VERBOSE=true
7 | else
8 | exec 4>/dev/null 3>/dev/null
9 | IS_VERBOSE=false
10 | fi
11 | }
12 |
13 | to_verboseOut()
14 | {
15 | echo "$1" >&3
16 | }
17 |
18 | to_verboseErr()
19 | {
20 | echo >&2 "$1" 2>&4
21 | }
22 |
23 | if [ -n "$IS_VERBOSE" ]; then
24 | verbose $IS_VERBOSE
25 | else
26 | verbose false
27 | fi
--------------------------------------------------------------------------------
/tools/moveBuildFolder.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | SECOND=0
4 |
5 | # this causes the current directory to become the parent of this script,
6 | # which is the app's roots
7 | cd "$( dirname "${BASH_SOURCE[0]}" )/.."
8 | . tools/modules/colors.inc.sh
9 | . tools/modules/verbose.inc.sh
10 |
11 | say () {
12 | echo "=> $@"
13 | }
14 |
15 | help_move_build_folder()
16 | {
17 | echo "Usage: $0 -v"
18 | echo "Options: These are optional argument"
19 | echo " -v verbose"
20 | echo " -h help"
21 | echo " -l run only if running in linux machine"
22 | exit 1
23 | }
24 |
25 | while getopts vlh opt
26 | do
27 | case "$opt" in
28 | v) verbose true;;
29 | l)
30 | if [ ! $(uname -s) == "Linux" ]; then
31 | exit 0
32 | fi
33 | ;;
34 | h) help_move_build_folder; exit 1;;
35 | \?) help_move_build_folder; exit 1;;
36 | esac
37 | done
38 |
39 | say "Moving build folder to deploy"
40 |
41 | say "Check if maintenance is on"
42 | # since we're currently in the app's root, we just need to climb up one level
43 | # to find the maintenance file
44 | if [ -f ../maintenance_on.html ];
45 | then
46 | say "-- Maintenance is on, aborting the process"
47 | exit
48 | else
49 | say "-- Maintenance is off, proceed"
50 | fi
51 |
52 | say "Removing current deploy > $ rm -rf deploy"
53 | rm -rf deploy
54 |
55 | say "Copying build to deploy > $ cp -r build deploy"
56 | cp -r build deploy
57 |
58 | timestamp=`date "+%Y-%m-%d %H:%M:%S"`
59 | min=$(($SECONDS / 60))
60 | sec=$(($SECONDS % 60))
61 |
62 | say
63 | say "====== Directory copied successfully ======"
64 | say "+-----------------------------------------+"
65 | say "| Task completed at $timestamp |"
66 | say "| Time elapsed "$min"m and "$sec"s |"
67 | say "+-----------------------------------------+"
68 |
--------------------------------------------------------------------------------
/tools/updateStories.sh:
--------------------------------------------------------------------------------
1 | ls src/components | while read line; do
2 | ls src/components/$line | xargs -I% yarn generate $line % 'y'
3 | done
4 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "src",
4 | "target": "es5",
5 | "lib": ["dom", "dom.iterable", "esnext"],
6 | "allowJs": true,
7 | "skipLibCheck": true,
8 | "esModuleInterop": true,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react"
18 | },
19 | "includes": ["src"],
20 | }
21 |
--------------------------------------------------------------------------------