이어서 할 곳

GitHub - fc-micro-frontends/career-up at step14

REACT_APP_AUTH0_DOMAIN=dev-vcrmf0xuep020tri.us.auth0.com
REACT_APP_AUTH0_CLIENT_ID=27LLb4I9cIjiiQNSjbNIX9sQM1KECUfk
REACT_APP_AUTH0_CALLBACK_URL=http://localhost:3000
➜ pnpm i

➜ pnpm build

API 코드 작성

// career-up/apps/job/src/types.ts

export interface ApplyStatusType {
  myJobsCount: number;
  myOnlineClassesCount: number;
  mySavedUpdatesCount: number;
}

export interface JobType {
  id: number;
  company: string;
  position: string;
  location: string;
}
// career-up/apps/job/src/apis.ts

import { type ApplyStatusType, type JobType } from "./types";

export async function getJobs(token: string): Promise<JobType[]> {
  const res = await fetch(`http://localhost:4000/jobs`, {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  });

  return await res.json();
}

export async function getApplyStatus(token: string): Promise<ApplyStatusType> {
  const res = await fetch("<http://localhost:4000/apply-status>", {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  });

  return await res.json();
}

공통 Layout 처리

// career-up/apps/job/src/routes.tsx

import React from "react";
import { type RouteObject } from "react-router-dom";
import { AppRoutingManager } from "@career-up/shell-router";
import Auth0ClientProvider from "./providers/auth0-client-provider";
**import Layout from "./components/layout";**

export const routes: RouteObject[] = [
  {
    path: "/",
    element: (
      <Auth0ClientProvider>
        **<Layout>**
          <AppRoutingManager type="app-edu" />
        **</Layout>**
      </Auth0ClientProvider>
    ),
    errorElement: <div>App Job Error</div>,
    children: [
      {
        index: true,
        element: <div>job home</div>,
      },
    ],
  },
];
// career-up/apps/job/src/components/layout.styles.ts

import styled from "@emotion/styled";

export const LayoutWrapper = styled.div`
  display: flex;
  flex-direction: row;
  gap: 24px;
  margin: 0 auto;
  max-width: 1128px;
  padding: 16px;

  .job--layout-apply-status {
    display: flex;
    flex-direction: column;
    width: 225px;
    gap: 10px;
  }

  .job--layout-children {
    display: flex;
    flex-direction: column;
    width: 879px;
    gap: 10px;
  }
`;
// career-up/apps/network/src/components/layout.tsx

import React from "react";
import { LayoutWrapper } from "./layout.styles";
import ApplyStatusContainer from "../containers/apply-status-container";

const Layout: React.FC<React.PropsWithChildren> = ({ children }) => {
  return (
    <LayoutWrapper>
      <div className="job--layout-apply-status">
        <ApplyStatusContainer />
      </div>
      <div className="job--layout-children">{children}</div>
    </LayoutWrapper>
  );
};

export default Layout;
// career-up/apps/job/src/redux/create.ts

import { configureStore } from "@reduxjs/toolkit";
import reducer from "./modules/rootReducer";

const create = () => {
  const store = configureStore({ reducer });

  return store;
};

export default create;

export type RootState = ReturnType<ReturnType<typeof create>["getState"]>;
export type AppDispatch = ReturnType<typeof create>["dispatch"];
// career-up/apps/job/src/redux/utils.ts

const prefix = "job--";

export const namespace = (name: string) => `${prefix}${name}`;
// career-up/apps/job/src/redux/modules/applyStatus.ts

import { createSlice } from "@reduxjs/toolkit";
import { namespace } from "../utils";
import { type ApplyStatusType } from "../../types";

const initialState: {
  data: ApplyStatusType | null;
  loading: boolean;
  error: Error | null;
} = {
  data: null,
  loading: false,
  error: null,
};

const {
  reducer,
  actions: { start, done, fail },
} = createSlice({
  name: namespace("apply-status"),
  initialState,
  reducers: {
    start: (state) => {
      state.loading = true;
    },
    done: (state, action) => {
      state.data = action.payload;
      state.loading = false;
    },
    fail: (state, action) => {
      state.error = action.payload;
      state.loading = false;
    },
  },
});

export default reducer;

export { start, done, fail };
// career-up/apps/job/src/redux/modules/rootReducer.ts

import applyStatus from "./applyStatus";

const rootReducer = { applyStatus };

export default rootReducer;
// career-up/apps/job/src/redux/modules/applyStatus.ts

import { createSlice } from "@reduxjs/toolkit";
import { namespace } from "../utils";
import { type ApplyStatusType } from "../../types";

const initialState: {
  data: ApplyStatusType | null;
  loading: boolean;
  error: Error | null;
} = {
  data: null,
  loading: false,
  error: null,
};

const {
  reducer,
  actions: { start, done, fail },
} = createSlice({
  name: namespace("apply-status"),
  initialState,
  reducers: {
    start: (state) => {
      state.loading = true;
    },
    done: (state, action) => {
      state.data = action.payload;
      state.loading = false;
    },
    fail: (state, action) => {
      state.error = action.payload;
      state.loading = false;
    },
  },
});

export default reducer;

export { start, done, fail };
// career-up/apps/job/src/routes.tsx

import React from "react";
import { type RouteObject } from "react-router-dom";
import { AppRoutingManager } from "@career-up/shell-router";
import Auth0ClientProvider from "./providers/auth0-client-provider";
import { Provider } from "react-redux";
import Layout from "./components/layout";
**import create from "./redux/create";**

**const store = create();**

export const routes: RouteObject[] = [
  {
    path: "/",
    element: (
      **<Provider store={store}>**
        <Auth0ClientProvider>
          <Layout>
            <AppRoutingManager type="app-job" />
          </Layout>
        </Auth0ClientProvider>
      **</Provider>**
    ),
    errorElement: <div>App Job Error</div>,
    children: [
      {
        index: true,
        element: <div>job home</div>,
      },
    ],
  },
];
// career-up/apps/job/src/containers/apply-status-container.tsx

import React, { useCallback } from "react";
import ApplyStatus from "../components/apply-status";
import { useDispatch, useSelector } from "react-redux";
import { done, fail, start } from "../redux/modules/applyStatus";
import useAuth0Client from "../hooks/use-auth0-client";
import { getApplyStatus } from "../apis";
import { AppDispatch, type RootState } from "../redux/create";

const ApplyStatusContainer: React.FC = () => {
  const auth0Client = useAuth0Client();
  const dispatch = useDispatch<AppDispatch>();
  const applyStatus = useSelector((state: RootState) => state.applyStatus.data);

  const fetchApplyStatus = useCallback(async () => {
    try {
      dispatch(start());
      const token = await auth0Client.getTokenSilently();
      const applyStatus = await getApplyStatus(token);
      dispatch(done(applyStatus));
    } catch (error) {
      dispatch(fail(error));
    }
  }, [dispatch, auth0Client]);

  return (
    <ApplyStatus
      fetchApplyStatus={fetchApplyStatus}
      applyStatus={applyStatus}
    />
  );
};

export default ApplyStatusContainer;
// career-up/apps/job/src/components/apply-status.styles.ts

import styled from "@emotion/styled";

export const ApplyStatusWrapper = styled.div`
  .job--apply-status-top {
    display: flex;
    flex-direction: column;
    background-color: white;

    padding: 16px 12px 16px;
    border-radius: 8px 8px 0 0;
    border-bottom: 1px solid rgb(0 0 0 / 0.1);
    gap: 10px;

    .job--apply-status-top-title {
      font-size: 15px;
      font-weight: bold;
      height: 15px;
    }
  }

  .job--apply-status-bottom {
    display: flex;
    flex-direction: column;
    padding: 16px 12px 16px;
    border-radius: 0 0 8px 8px;
    background-color: white;
    gap: 10px;

    .job--apply-status-bottom-item {
      display: flex;
      flex-direction: row;
      justify-content: space-between;

      color: rgb(0 0 0/0.6);
      font-size: 14px;

      .job--apply-status-bottom-item-count {
        color: #0a66c2;
        cursor: pointer;
      }
    }
  }
`;
// career-up/apps/job/src/components/apply-status.tsx

import React, { useEffect } from "react";
import { ApplyStatusWrapper } from "./apply-status.styles";
import { type ApplyStatusType } from "../types";

interface ApplyStatusProps {
  fetchApplyStatus: () => Promise<void>;
  applyStatus: ApplyStatusType | null;
}

const ApplyStatus: React.FC<ApplyStatusProps> = ({
  fetchApplyStatus,
  applyStatus,
}) => {
  useEffect(() => {
    fetchApplyStatus();
  }, [fetchApplyStatus]);

  if (applyStatus === null) {
    return <div>Loading...</div>;
  }

  return (
    <ApplyStatusWrapper>
      <div className="job--apply-status-top">
        <span className="job--apply-status-top-title">내 항목</span>
      </div>
      <div className="job--apply-status-bottom">
        <div className="job--apply-status-bottom-item">
          <div>나의 채용공고</div>
          <div className="job--apply-status-bottom-item-count">
            {applyStatus.myJobsCount}
          </div>
        </div>
        <div className="job--apply-status-bottom-item">
          <div>나의 온라인클래스</div>
          <div className="job--apply-status-bottom-item-count">
            {applyStatus.myOnlineClassesCount}
          </div>
        </div>
        <div className="job--apply-status-bottom-item">
          <div>저장한 업데이트와 글</div>
          <div className="job--apply-status-bottom-item-count">
            {applyStatus.mySavedUpdatesCount}
          </div>
        </div>
      </div>
    </ApplyStatusWrapper>
  );
};

export default ApplyStatus;