recruitment-web — Repository Map
บทนี้เป็นแผนที่ไฟล์ของ recruitment-web (branch: prod, 200 tracked files) — CRA React 18 + Ant Design 5 + Recoil สำหรับเว็บสมัครงานสาธารณะ
แผนที่โฟลเดอร์ระดับ repo
recruitment-web/ (branch: prod)
├── .env / .env.uat / .env.dev # tracked ใน git — มี secret key จริง (SEC-RCWEB-01)
├── src/.env.example # ตัวอย่างใน src/ — CRA ไม่โหลดจากที่นี่
├── Dockerfile / default.conf # multi-stage → nginx SPA
├── package.json # react-scripts 5, PORT=3006
├── public/
│ ├── index.html
│ └── manifest.json
├── postcss.config.js / tailwind.config.js
└── src/
├── index.js # RecoilRoot > BrowserRouter > App
├── App.js # theme, GA, route gate OTP
├── config.js # รวม REACT_APP_* เป็น object
├── config/
│ ├── axios-rc.js # HTTP client หลัก → recruitment-api + refresh
│ ├── axios-common.js # client สำรอง — ไม่พบ caller
│ └── routes.js
├── utils/
│ ├── axios.js # client ที่ 3 — ใช้แค่ api/login.js (dead)
│ ├── cryptoJs.js # AES encrypt/decrypt (secretKey + privateKey)
│ ├── localStorage.js # wrapper — expiry comment ปิดทุกจุด
│ ├── file.js # FileReader → base64 data URL
│ ├── formatDate.js / number.js / hexToRgba.js
├── api/
│ ├── index.js / login.js # dead code — ไม่มี import จาก pages
├── routes/
│ ├── index.js
│ └── mainRoutes.js # route table ทั้งแอป
├── recoil/states/default.js # atoms: loading, theme, login popup, AI
├── components/
│ ├── header/index.js # login modal + OTP + decrypt หลัง login
│ ├── footer/ / Form/* / modalRc.js / sharedModal.js ...
├── pages/
│ ├── login/ # form-signIn, form-otp, form-signOut, timers
│ ├── home/ # รายการประกาศงาน + search + AI recommend
│ ├── jobDetail/ # รายละเอียดตำแหน่ง + apply
│ ├── jobHistory/ # ประวัติการสมัคร (MyJob)
│ ├── applicantProfile/ # ใบสมัคร 10 step — แกนหลัก
│ ├── clear/ # /RefreshData → localStorage.clear()
│ └── notFound/
├── assets/ # images, scss, theme, tutorial.pdf
└── services/index.jsBoot sequence — จาก npm start / yarn build ถึงหน้าแรก
1. react-scripts start (PORT=3006) หรือ yarn build
│ CRA โหลด .env* จาก root ของ project เท่านั้น
│ `recruitment-web/package.json:40-41`
▼
2. src/index.js
│ createRoot → RecoilRoot → BrowserRouter → <App />
│ `recruitment-web/src/index.js:11-18`
▼
3. App.js
│ ReactGA.initialize(REACT_APP_GA_ID)
│ mute console นอก uat-jobs / localhost
│ gate /myJob + /applicantProfile ด้วย decryptData(checkLogin)
│ `recruitment-web/src/App.js:29-62`
▼
4. routes/ → mainRoutes.js
│ match path → page component
│ `recruitment-web/src/routes/mainRoutes.js:9-42`
▼
5. หน้า Home (path `/` หรือ `/:org`)
│ HeaderHome + HomeContextProvider โหลด dropdown/job listRoute table ทั้งระบบ
จาก mainRoutes.js (branch: prod):
| Path | Component | Auth gate ใน App.js? | Citation |
|---|---|---|---|
/ | Home | ไม่ | mainRoutes.js:11-13 |
/:org | Home | ไม่ | mainRoutes.js:15-17 |
/JobDetail/:org/:jobId | JobDetail | ไม่ | mainRoutes.js:19-21 |
/MyJob | JobHistory | ใช่ — ต้องมี isOtp | mainRoutes.js:23-25, App.js:59-61 |
/ApplicantProfile | ApplicantProfile | ใช่ — ต้องมี isOtp | mainRoutes.js:27-29, App.js:59-61 |
/RefreshData | RefreshData | ไม่ — clear แล้วไป / | mainRoutes.js:31-33, pages/clear/index.js:6-8 |
/NotFound | NotFound | ไม่ | mainRoutes.js:35-37 |
* | <Navigate to="/" replace /> | — | mainRoutes.js:39-41 |
หมายเหตุ: path matching ของ React Router เป็น case-sensitive ตาม default ของ v6 — แต่ gate ใน App.js ใช้ pathname.toLowerCase().includes('myjob') / 'applicantprofile' (App.js:59) จึงครอบคลุมทั้ง /MyJob และ /myJob
Axios clients — สามตัวใน repo
| Client | Base URL source | Auth header | ใช้จริง? | Citation |
|---|---|---|---|---|
config/axios-rc.js | REACT_APP_END_POINT_RECRUITMENT_API | Authorization: Bearer {token} จาก localStorage.token + refresh บน 401/403 | ใช่ — ทุกหน้าหลัก | axios-rc.js:10-78 |
config/axios-common.js | REACT_APP_END_POINT_COMMON_API | ไม่ตั้ง (comment ปิด) | ไม่พบ caller | axios-common.js:9-26 |
utils/axios.js | REACT_APP_KEY_URLAPI (config.baseUrl) | Authorization = raw accessToken + CORS header ฝั่ง client | เฉพาะ api/login.js ที่ dead | utils/axios.js:4-15, api/login.js:1 |
Refresh-token flow (axios-rc.js)
response 401 หรือ 403 และยังไม่ _retry
│ `axios-rc.js:62`
▼
POST {baseURL}/auth/refresh_token
body: { refreshToken }
header: x-refresh-token: true
│ `axios-rc.js:34-37`
▼
สำเร็จ → เขียน token/refreshToken ใหม่ แล้ว retry originalRequest
ล้มเหลว → message.error('Session หมดอายุ') + localStorage.clear() + redirect /
│ `axios-rc.js:44-47`Crypto helpers (utils/cryptoJs.js)
| Function | Key source | พฤติกรรม | Citation |
|---|---|---|---|
encryptData(plaintext) | config.secretKey (REACT_APP_SECRET_KEY) | AES.encrypt → string; ถ้าไม่ใช่ string จะ JSON.stringify ก่อน | cryptoJs.js:4-13 |
decryptData(encryptedData) | config.secretKey | AES.decrypt → Utf8 → JSON.parse | cryptoJs.js:15-21 |
encryptText(plaintext) | config.privateKey (REACT_APP_PRIVATE_KEY) | AES.encrypt field PII ก่อนส่ง API | cryptoJs.js:29-32 |
decryptText(encryptedText) | config.privateKey | AES.decrypt field PII หลังรับจาก API | cryptoJs.js:23-27 |
encryptData/decryptData ใช้กับ object checkLogin (flag isOtp) ที่เก็บใน user-login storage — ไม่ใช่ field ใบสมัคร (header/index.js:63, App.js:52-55)
encryptText/decryptText ใช้กับ ~25+ field PII ของ applicant profile (content.js:706-748, header/index.js:104-148)
เนื่องจาก CRA bake REACT_APP_* เข้า bundle การ "เข้ารหัส" นี้ไม่ปกป้องความลับจากผู้ที่อ่าน JS bundle ได้ — ดู SEC-RCWEB-01
localStorage keys (utils/localStorage.js)
| Key | Setter / Getter | เก็บอะไร | Expiry จริง? | Citation |
|---|---|---|---|---|
user-login | setUserStorage / getUserStorage | email, id, name, PII ใน data (JSON string ที่ decrypt แล้ว), checkLogin (AES ของ {isOtp}), profileImage | ไม่ — expiry comment ปิดที่บรรทัด 7, 19-26 | localStorage.js:35-46,1-28 |
token | localStorage.setItem โดยตรงใน form-otp | JWT access token | ไม่มี wrapper | form-otp.js:54 |
refreshToken | 同上 | refresh token | ไม่มี wrapper | form-otp.js:55 |
home-image | setHomeImageStorage | logo/banner/theme ของ org unit | ไม่ | localStorage.js:50-60 |
rc-theme | setThemeStorage | สี theme (red / อื่น) | ไม่ | localStorage.js:64-75 |
rc-job-recommend-ai | setJobRecommendAIStorage | cache แนะนำงาน AI | ไม่ | localStorage.js:79-90 |
rc-job-req | setJobReqStorage | job ที่กำลังจะสมัคร ระหว่าง login flow | ไม่ | localStorage.js:97-107 |
search_history | setSearchHistoryStorage | org unit ที่เคยเลือก | ไม่ | localStorage.js:109-119 |
clearAllStorage() เรียก localStorage.clear() ทั้งก้อน (localStorage.js:93-95) — ใช้ตอน logout (header/index.js:270)
/RefreshData ก็ localStorage.clear() แล้ว navigate / (pages/clear/index.js:6-8)
Recoil atoms (recoil/states/default.js)
| Atom | Default | ใช้ทำอะไร | Citation |
|---|---|---|---|
isLoadingState | false | overlay loading ทั้งแอป | default.js:5-8 |
loadingProgressState | null | % progress ตอน upload profile | default.js:10-13 |
homeLogoState / homeBannerState | default logo / null | แสดงบนหน้า home | default.js:15-23 |
themeState / themeColorState | DefaultTheme / 'red' | CSS variables | default.js:25-37 |
isUpdateUserAfterSaveState | false | trigger header refresh หลัง save | default.js:39-42 |
toggleLoginPopUpState | false | บังคับเปิด login modal | default.js:44-47 |
isAILoadingState / isGetJobRecommendAIState | false | AI job recommend UI | default.js:49-57 |
Applicant profile — โครงสร้าง 10 step
Progress labels จาก constant.js (progressSteps):
| Index | Title (UI) | Module path | Citation |
|---|---|---|---|
| 0 | ข้อมูลส่วนตัว | personal/personal.js | constant.js:54-56 |
| 1 | ข้อมูลการติดต่อ | contact/contact.js | constant.js:58-60 |
| 2 | ประวัติการศึกษา | edu/education.js, edu/profesLicense.js | constant.js:62-64 |
| 3 | ประสบการณ์การทำงาน | exp/index.js | constant.js:66-68 |
| 4 | ความสามารถ | capability/* | constant.js:70-72 |
| 5 | บุคคลอ้างอิง | reference/* | constant.js:74-76 |
| 6 | เอกสารแบบ | attachment/* | constant.js:78-80 |
| 7 | ประวัติดำเนินคดี | criminal/index.js | constant.js:82-84 |
| 8 | ข่าวสารการรับสมัคร | information/index.js | constant.js:86-88 |
| 9 | ข้อตกลง | consent/* + PDFViewer | constant.js:90-92 |
ไฟล์แนบประเภทมาตรฐาน (attachmentList): photo, cv, idCardCopy, houseRegisCopy, diploma, transcriptCopy, other (constant.js:23-52)
Accept MIME: PNG / JPEG / PDF; ขนาด ≤ 2MB ที่ client (constant.js:1-16, uploadForm.js:22-30)
Business logic รวมศูนย์ที่ content.js (~1,400 บรรทัด) — encrypt ก่อนส่ง, validate, reCAPTCHA, apply job
env files ที่พบใน repo
| ไฟล์ | Tracked? | ใช้เมื่อไหร่ |
|---|---|---|
.env | ใช่ | ค่า prod ที่ bake ตอน build ด้วยไฟล์นี้ |
.env.uat | ใช่ | ค่า UAT |
.env.dev | ใช่ | ค่า local (NODE_ENV=dev → OTP bypass) |
src/.env.example | ใช่ | ตัวอย่างเท่านั้น — CRA อ่าน .env* จาก root ไม่ใช่ src/ |
Key ที่มีใน .env* (ชื่อเท่านั้น — ไม่ใส่ค่า secret ในเอกสารนี้):
PUBLIC_URL, REACT_APP_SECRET_KEY, REACT_APP_PRIVATE_KEY, REACT_APP_END_POINT_RECRUITMENT_API, REACT_APP_END_POINT_RECRUITMENT_API_ATTACHFILE, REACT_APP_END_POINT_COMMON_API, REACT_APP_END_POINT_WF_API_ATTACHFILE, REACT_APP_NODE_ENV, REACT_APP_GA_ID, REACT_APP_COOKIES, REACT_APP_RECAPTCHA_SITE_KET (typo — ดู QUAL-RCWEB-03)
Dead code ที่ยืนยันแล้ว
| Path | หลักฐานว่า dead | Citation |
|---|---|---|
src/api/login.js + src/api/index.js | ไม่มี component import; ใช้ endpoint login / login/check คนละชุดกับ OTP flow จริง | api/login.js:1-53 |
src/utils/axios.js | ใช้เฉพาะผ่าน api/login.js | utils/axios.js:1-17 |
src/config/axios-common.js | ไม่พบ import ... axios-common จาก pages/components | axios-common.js:1-28 |
Login จริงทั้งหมดวิ่งผ่าน components/header/index.js + pages/login/form-*.js + axios-rc.js