GitHub - fc-micro-frontends/career-up at step21
REACT_APP_AUTH0_DOMAIN=dev-vcrmf0xuep020tri.us.auth0.com
REACT_APP_AUTH0_CLIENT_ID=27LLb4I9cIjiiQNSjbNIX9sQM1KECUfk
REACT_APP_AUTH0_CALLBACK_URL=http://localhost:3000
REACT_APP_MICROAPP_POSTING=http://localhost:3001
REACT_APP_MICROAPP_EDU=http://localhost:3002
REACT_APP_MICROAPP_NETWORK=http://localhost:3003
REACT_APP_MICROAPP_JOB=http://localhost:3004
REACT_APP_FRAGMENT_RECOMMEND_CONNECTIONS=http://localhost:5001
REACT_APP_SERVER_URL=http://localhost:4000
➜ pnpm i
➜ pnpm build
➜ pnpm --filter @career-up/ui-kit add @auth0/auth0-spa-js -D
// career-up/packages/ui-kit/src/components/Profile.module.css
.profile-top {
display: flex;
flex-direction: column;
background-color: white;
padding: 30px 12px 16px;
border-radius: 8px 8px 0 0;
border-bottom: 1px solid rgb(0 0 0 / 0.1);
gap: 10px;
justify-content: center;
align-items: center;
}
.profile-img {
width: 50px;
}
.profile-name {
font-size: 16px;
font-weight: bold;
}
.profile-email {
color: rgb(0 0 0/0.6);
font-size: 12px;
}
.profile-bottom {
display: flex;
flex-direction: column;
padding: 16px 12px 16px;
border-radius: 0 0 8px 8px;
background-color: white;
gap: 10px;
}
.profile-bottom-item {
display: flex;
flex-direction: row;
justify-content: space-between;
color: rgb(0 0 0/0.6);
font-size: 14px;
}
.profile-bottom-item-count {
color: #0a66c2;
cursor: pointer;
}
// career-up/packages/ui-kit/src/components/Profile.tsx
import React from "react";
import { type User } from "@auth0/auth0-spa-js";
import styles from "./Profile.module.css";
export interface UserType extends User {
view_count: number;
update_count: number;
courses: { courseId: number; done: false }[];
}
interface ProfileProps {
user: UserType | null;
}
const Profile: React.FC<ProfileProps> = ({ user }) => {
if (user === null) {
return null;
}
const { picture, name, email, view_count, update_count } = user;
return (
<div>
<div className={styles["profile-top"]}>
<img className={styles["profile-img"]} src={picture} />
<div className={styles["profile-name"]}>{name}</div>
<div className={styles["profile-email"]}>{email}</div>
</div>
<div className={styles["profile-bottom"]}>
<div className={styles["profile-bottom-item"]}>
<div>프로필 조회자</div>
<div className={styles["profile-bottom-item-count"]}>
{view_count}
</div>
</div>
<div className={styles["profile-bottom-item"]}>
<div>업데이트 노출</div>
<div className={styles["profile-bottom-item-count"]}>
{update_count}
</div>
</div>
</div>
</div>
);
};
export default Profile;
// career-up/packages/ui-kit/src/index.ts
import "./global.css";
import Icon from "./components/Icon";
import Button from "./components/Button";
**import Profile from "./components/Profile";**
export { Icon, Button, **Profile** };
➜ pnpm --filter @career-up/ui-kit build
// career-up/apps/edu/src/containers/profile-container.tsx
import { useAtomValue } from "jotai";
import React from "react";
import { userAtom } from "../atoms";
import { Profile } from "@career-up/ui-kit";
const ProfileContainer: React.FC = () => {
const user = useAtomValue(userAtom);
return <Profile user={user} />;
};
export default ProfileContainer;
➜ pnpm --filter @career-up/edu build
➜ pnpm --filter @career-up/edu build:start
// career-up/apps/edu/webpack.config.js
const HtmlWebPackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const Dotenv = require("dotenv-webpack");
const deps = require("./package.json").dependencies;
module.exports = (_, argv) => ({
output: {
publicPath: "<http://localhost:3002/>",
},
resolve: {
extensions: [".tsx", ".ts", ".jsx", ".js", ".json"],
},
devServer: {
port: 3002,
historyApiFallback: true,
},
module: {
rules: [
{
test: /\\.m?js/,
type: "javascript/auto",
resolve: {
fullySpecified: false,
},
},
{
test: /\\.(css|s[ac]ss)$/i,
use: ["style-loader", "css-loader", "postcss-loader"],
},
{
test: /\\.(ts|tsx|js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
},
},
],
},
plugins: [
new Dotenv({
path: "../../.env",
}),
new ModuleFederationPlugin({
name: "edu",
filename: "remoteEntry.js",
remotes: {},
exposes: {
"./injector": "./src/injector.tsx",
},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
"@career-up/shell-router": {
singleton: true,
},
"@career-up/ui-kit": {
singleton: true,
**shareScope: "v2",**
},
},
}),
new HtmlWebPackPlugin({
template: "./src/index.html",
}),
],
});
➜ pnpm --filter @career-up/edu build
➜ pnpm --filter @career-up/edu build:start
// career-up/apps/edu/src/injector.tsx
import { injectFactory } from "@career-up/shell-router";
import { routes } from "./routes";
**import "@career-up/ui-kit/index.css";**
const inject = injectFactory({
routes,
});
export default inject;
➜ pnpm --filter @career-up/edu build
➜ pnpm --filter @career-up/edu build:start
// career-up/apps/posting/src/containers/profile-container.tsx
import React, { useEffect, useState } from "react";
import { getUser } from "../apis";
import { type UserType } from "../types";
import { useAuth0Client } from "@career-up/shell-router";
import { Profile } from "@career-up/ui-kit";
const ProfileContainer = () => {
const auth0Client = useAuth0Client();
const [user, setUser] = useState<UserType | null>(null);
useEffect(() => {
(async () => {
try {
const token = await auth0Client.getTokenSilently();
const user = await getUser(token);
setUser(user);
} catch (error) {
alert(error);
}
})();
}, [auth0Client]);
return <Profile user={user} />;
};
export default ProfileContainer;
// career-up/apps/posting/src/types.ts
import { type User } from "@auth0/auth0-spa-js";
export interface PostType {
id: number;
message: string;
createdAt: string;
author: {
email: string;
name: string;
picture: string;
};
}
export interface UserType extends User {
view_count: number;
update_count: number;
**courses: { courseId: number; done: false }[];**
}