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
// 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();
}
// 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;