From 5c0593b75520a5cf6bcfc46358cdab909a66e8b0 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Wed, 14 May 2025 14:22:55 +0900 Subject: [PATCH 01/10] feat: integrate user info state management in Login and PwResetForm components for enhanced user experience --- src/components/Login.tsx | 11 ++++++++ src/components/pw-reset/PwResetForm.tsx | 9 +++++- src/store/userInfoState.ts | 37 +++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 src/store/userInfoState.ts diff --git a/src/components/Login.tsx b/src/components/Login.tsx index 13b4cc8..0bb7c66 100644 --- a/src/components/Login.tsx +++ b/src/components/Login.tsx @@ -6,6 +6,7 @@ import { useRouter } from 'next/navigation' import { useQuery } from '@tanstack/react-query' import { axiosInstance } from '@/libs/axios' import { useSessionStore } from '@/store/session' +import { useUserInfoState } from '@/store/userInfoState' interface AccountState { loginId: string @@ -24,6 +25,7 @@ export default function Login() { const [isLogin, setIsLogin] = useState(false) const { session, setSession } = useSessionStore() + const { userInfo, setUserInfo } = useUserInfoState() const reducer = (state: AccountState, newState: Partial) => ({ ...state, ...newState }) const [account, setAccount] = useReducer(reducer, { @@ -60,6 +62,15 @@ export default function Login() { useEffect(() => { setIsLogin(false) if (loginData?.code === 200) { + // 유저 정보 저장 + setUserInfo({ + loginId: account.loginId, + chgType: 'C', + emali: loginData?.result.email || '', + pwd: account.pwd, + chgPwd: '', + }) + // 세션 정보 저장 setSession({ ...session, ...loginData?.result, diff --git a/src/components/pw-reset/PwResetForm.tsx b/src/components/pw-reset/PwResetForm.tsx index 1dd3d85..65bc3c0 100644 --- a/src/components/pw-reset/PwResetForm.tsx +++ b/src/components/pw-reset/PwResetForm.tsx @@ -2,12 +2,19 @@ import { useState } from 'react' import { useRouter } from 'next/navigation' +import { useUserInfoState } from '@/store/userInfoState' export default function PwResetForm() { const [pwShow01, setPwShow01] = useState(false) //비밀번호 확인 보이기 숨기기 const [pwShow02, setPwShow02] = useState(false) //비밀번호 재확인 보이기 숨기기 const router = useRouter() + const { userInfo, setUserInfo } = useUserInfoState() + + const handleReset = () => { + console.log('reset') + } + return ( <>
@@ -48,7 +55,7 @@ export default function PwResetForm() {
-
diff --git a/src/store/userInfoState.ts b/src/store/userInfoState.ts new file mode 100644 index 0000000..275c2b1 --- /dev/null +++ b/src/store/userInfoState.ts @@ -0,0 +1,37 @@ +import { create } from 'zustand' + +type UserInfo = { + loginId: string + chgType: string + emali: string + pwd: string + chgPwd: string +} + +type UserInfoState = { + userInfo: UserInfo + setUserInfo: (value: UserInfo) => void + reset: () => void +} + +type InitialState = { + loginId: string + chgType: string + emali: string + pwd: string + chgPwd: string +} + +const initialState: InitialState = { + loginId: '', + chgType: '', + emali: '', + pwd: '', + chgPwd: '', +} + +export const useUserInfoState = create((set) => ({ + userInfo: initialState, + setUserInfo: (value: UserInfo) => set((state) => ({ ...state, userInfo: { ...state.userInfo, ...value } })), + reset: () => set({ userInfo: initialState }), +})) From 3212763a1baa24c3cba51f4c50ef4ce0be55fb89 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Wed, 14 May 2025 15:42:35 +0900 Subject: [PATCH 02/10] fix: update MemberInformationPopup to use session store name for display instead of hardcoded value --- src/components/popup/MemberInformationPopup.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/popup/MemberInformationPopup.tsx b/src/components/popup/MemberInformationPopup.tsx index f9bb653..1f4d6f3 100644 --- a/src/components/popup/MemberInformationPopup.tsx +++ b/src/components/popup/MemberInformationPopup.tsx @@ -42,7 +42,7 @@ export default function MemberInformationPopup() {
販売店名
- +
建設ID
From 8034424d158e38c6114119728386da5d1b819798 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Wed, 14 May 2025 16:01:16 +0900 Subject: [PATCH 03/10] fix: enhance Header component to manage user session storage and clear session data on logout --- src/components/ui/common/Header.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/ui/common/Header.tsx b/src/components/ui/common/Header.tsx index eb365b8..b3e1b86 100644 --- a/src/components/ui/common/Header.tsx +++ b/src/components/ui/common/Header.tsx @@ -4,6 +4,7 @@ import Link from 'next/link' import { usePathname, useRouter } from 'next/navigation' import { Swiper, SwiperSlide } from 'swiper/react' +import { useSessionStorage } from 'usehooks-ts' import { useQueryClient } from '@tanstack/react-query' import { useSideNavState } from '@/store/sideNavState' @@ -19,6 +20,7 @@ import { usePopupController } from '@/store/popupController' export default function Header() { const router = useRouter() const pathname = usePathname() + const [value, setValue, removeValue] = useSessionStorage('userInfo', {}) const { sideNavIsOpen, setSideNavIsOpen } = useSideNavState() const { backBtn } = useHeaderStore() const { getTitle } = useTitle() @@ -34,6 +36,7 @@ export default function Header() { const handleLogout = async () => { reset() + removeValue() const { data } = await axiosInstance(null).get('/api/auth/logout') if (data.code === 200) { queryClient.clear() From 2bfacca06b5bdf0af860f84aca3705c9a933b2c9 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Wed, 14 May 2025 16:01:33 +0900 Subject: [PATCH 04/10] chore: add usehooks-ts dependency for enhanced hooks management --- package-lock.json | 22 ++++++++++++++++++++++ package.json | 1 + pnpm-lock.yaml | 19 +++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/package-lock.json b/package-lock.json index f2c0d4f..07a4241 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "react-to-pdf": "^2.0.0", "sass": "^1.87.0", "swiper": "^11.2.6", + "usehooks-ts": "^3.1.1", "zustand": "^5.0.3" }, "devDependencies": { @@ -3293,6 +3294,12 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -4068,6 +4075,21 @@ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "license": "MIT" }, + "node_modules/usehooks-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.1.tgz", + "integrity": "sha512-I4diPp9Cq6ieSUH2wu+fDAVQO43xwtulo+fKEidHUwZPnYImbtkTjzIJYcDcJqxgmX31GVqNFURodvcgHcW0pA==", + "license": "MIT", + "dependencies": { + "lodash.debounce": "^4.0.8" + }, + "engines": { + "node": ">=16.15.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/utrie": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", diff --git a/package.json b/package.json index 7d5da52..d00c56c 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "react-to-pdf": "^2.0.0", "sass": "^1.87.0", "swiper": "^11.2.6", + "usehooks-ts": "^3.1.1", "zustand": "^5.0.3" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9ebc4c8..74c7972 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,9 @@ importers: swiper: specifier: ^11.2.6 version: 11.2.6 + usehooks-ts: + specifier: ^3.1.1 + version: 3.1.1(react@19.1.0) zustand: specifier: ^5.0.3 version: 5.0.3(@types/react@19.0.12)(react@19.1.0) @@ -1082,6 +1085,9 @@ packages: resolution: {integrity: sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==} engines: {node: '>= 12.0.0'} + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + lodash.includes@4.3.0: resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} @@ -1349,6 +1355,12 @@ packages: undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + usehooks-ts@3.1.1: + resolution: {integrity: sha512-I4diPp9Cq6ieSUH2wu+fDAVQO43xwtulo+fKEidHUwZPnYImbtkTjzIJYcDcJqxgmX31GVqNFURodvcgHcW0pA==} + engines: {node: '>=16.15.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc + utrie@1.0.2: resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==} @@ -2312,6 +2324,8 @@ snapshots: lightningcss-win32-arm64-msvc: 1.29.2 lightningcss-win32-x64-msvc: 1.29.2 + lodash.debounce@4.0.8: {} + lodash.includes@4.3.0: {} lodash.isboolean@3.0.3: {} @@ -2581,6 +2595,11 @@ snapshots: undici-types@6.19.8: {} + usehooks-ts@3.1.1(react@19.1.0): + dependencies: + lodash.debounce: 4.0.8 + react: 19.1.0 + utrie@1.0.2: dependencies: base64-arraybuffer: 1.0.2 From 221742615227098d7fe7afe69dc9d7b710dc392b Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Wed, 14 May 2025 17:49:59 +0900 Subject: [PATCH 05/10] feat: implement password change API and update Login and PwResetForm components to utilize local storage for password management --- src/app/api/auth/chg-pwd/route.ts | 22 ++++++++++ src/components/Login.tsx | 15 +++---- src/components/pw-reset/PwResetForm.tsx | 58 +++++++++++++++++++++---- src/components/ui/common/Header.tsx | 9 ++-- src/providers/EdgeProvider.tsx | 10 ++++- src/store/userInfoState.ts | 37 ---------------- 6 files changed, 91 insertions(+), 60 deletions(-) create mode 100644 src/app/api/auth/chg-pwd/route.ts delete mode 100644 src/store/userInfoState.ts diff --git a/src/app/api/auth/chg-pwd/route.ts b/src/app/api/auth/chg-pwd/route.ts new file mode 100644 index 0000000..436f101 --- /dev/null +++ b/src/app/api/auth/chg-pwd/route.ts @@ -0,0 +1,22 @@ +import { NextResponse } from 'next/server' + +import { axiosInstance } from '@/libs/axios' + +export async function POST(req: Request) { + const { loginId, email, pwd, chgPwd } = await req.json() + console.log('🚀 ~ POST ~ loginId:', loginId) + console.log('🚀 ~ POST ~ email:', email) + console.log('🚀 ~ POST ~ pwd:', pwd) + console.log('🚀 ~ POST ~ chgPwd:', chgPwd) + + const result = await axiosInstance(`${process.env.NEXT_PUBLIC_QSP_API_URL}`).post(`/api/user/userPwdChg`, { + loginId, + chgType: 'C', + email, + pwd, + chgPwd, + }) + console.log('🚀 ~ result ~ result:', result) + + return NextResponse.json({ code: 200, data: result.data }) +} diff --git a/src/components/Login.tsx b/src/components/Login.tsx index 0bb7c66..5f086af 100644 --- a/src/components/Login.tsx +++ b/src/components/Login.tsx @@ -3,10 +3,12 @@ import type { SessionData } from '@/types/Auth' import { useEffect, useReducer, useState } from 'react' import { useRouter } from 'next/navigation' + +import { useLocalStorage } from 'usehooks-ts' import { useQuery } from '@tanstack/react-query' + import { axiosInstance } from '@/libs/axios' import { useSessionStore } from '@/store/session' -import { useUserInfoState } from '@/store/userInfoState' interface AccountState { loginId: string @@ -25,7 +27,7 @@ export default function Login() { const [isLogin, setIsLogin] = useState(false) const { session, setSession } = useSessionStore() - const { userInfo, setUserInfo } = useUserInfoState() + const [value, setValue, removeValue] = useLocalStorage<{ indivisualData: string }>('hanasysIndivisualState', { indivisualData: '' }) const reducer = (state: AccountState, newState: Partial) => ({ ...state, ...newState }) const [account, setAccount] = useReducer(reducer, { @@ -50,7 +52,6 @@ export default function Login() { loginId: account.loginId, pwd: account.pwd, }) - // router.push('/') return data }, @@ -63,12 +64,8 @@ export default function Login() { setIsLogin(false) if (loginData?.code === 200) { // 유저 정보 저장 - setUserInfo({ - loginId: account.loginId, - chgType: 'C', - emali: loginData?.result.email || '', - pwd: account.pwd, - chgPwd: '', + setValue({ + indivisualData: account.pwd, }) // 세션 정보 저장 setSession({ diff --git a/src/components/pw-reset/PwResetForm.tsx b/src/components/pw-reset/PwResetForm.tsx index 65bc3c0..b332de4 100644 --- a/src/components/pw-reset/PwResetForm.tsx +++ b/src/components/pw-reset/PwResetForm.tsx @@ -2,17 +2,49 @@ import { useState } from 'react' import { useRouter } from 'next/navigation' -import { useUserInfoState } from '@/store/userInfoState' + +import { useLocalStorage } from 'usehooks-ts' + +import { axiosInstance } from '@/libs/axios' +import { useSessionStore } from '@/store/session' export default function PwResetForm() { - const [pwShow01, setPwShow01] = useState(false) //비밀번호 확인 보이기 숨기기 - const [pwShow02, setPwShow02] = useState(false) //비밀번호 재확인 보이기 숨기기 + const [pwShow01, setPwShow01] = useState(false) //비밀번호 확인 보이기 숨기기 + const [pwShow02, setPwShow02] = useState(false) //비밀번호 재확인 보이기 숨기기 + + const [pwd01, setPwd01] = useState('') + const [pwd02, setPwd02] = useState('') + const router = useRouter() - const { userInfo, setUserInfo } = useUserInfoState() + const { session } = useSessionStore() + const [value, setValue, removeValue] = useLocalStorage<{ indivisualData: string }>('hanasysIndivisualState', { indivisualData: '' }) - const handleReset = () => { - console.log('reset') + const validatePwd = () => { + if (pwd01 !== pwd02) { + alert('비밀번호가 일치하지 않습니다.') + return false + } + return true + } + + const handleReset = async () => { + if (validatePwd()) { + const { data } = await axiosInstance(null).post(`/api/auth/chg-pwd`, { + loginId: session.userId, + email: session.email, + pwd: value.indivisualData, + chgPwd: pwd01, + }) + + if (data.data.result.resultCode === 'S') { + setValue({ indivisualData: pwd01 }) + } + + window.neoAlert(data.data.result.resultMsg, () => { + router.back() + }) + } } return ( @@ -25,7 +57,12 @@ export default function PwResetForm() {
- + setPwd01(e.target.value)} + /> @@ -39,7 +76,12 @@ export default function PwResetForm() {
- + setPwd02(e.target.value)} + /> diff --git a/src/components/ui/common/Header.tsx b/src/components/ui/common/Header.tsx index b3e1b86..da3cf49 100644 --- a/src/components/ui/common/Header.tsx +++ b/src/components/ui/common/Header.tsx @@ -3,24 +3,25 @@ import Link from 'next/link' import { usePathname, useRouter } from 'next/navigation' -import { Swiper, SwiperSlide } from 'swiper/react' -import { useSessionStorage } from 'usehooks-ts' +import { useLocalStorage } from 'usehooks-ts' import { useQueryClient } from '@tanstack/react-query' +import { Swiper, SwiperSlide } from 'swiper/react' import { useSideNavState } from '@/store/sideNavState' import { useHeaderStore } from '@/store/header' import { useSessionStore } from '@/store/session' +import { usePopupController } from '@/store/popupController' + import { useTitle } from '@/hooks/useTitle' import { axiosInstance } from '@/libs/axios' import 'swiper/css' -import { usePopupController } from '@/store/popupController' export default function Header() { const router = useRouter() const pathname = usePathname() - const [value, setValue, removeValue] = useSessionStorage('userInfo', {}) + const [value, setValue, removeValue] = useLocalStorage<{ indivisualData: string }>('hanasysIndivisualState', { indivisualData: '' }) const { sideNavIsOpen, setSideNavIsOpen } = useSideNavState() const { backBtn } = useHeaderStore() const { getTitle } = useTitle() diff --git a/src/providers/EdgeProvider.tsx b/src/providers/EdgeProvider.tsx index 424ab2d..4cfd67e 100644 --- a/src/providers/EdgeProvider.tsx +++ b/src/providers/EdgeProvider.tsx @@ -1,14 +1,16 @@ 'use client' +import { useEffect } from 'react' +import { usePathname } from 'next/navigation' + import { useHeaderStore } from '@/store/header' import { usePopupController } from '@/store/popupController' import { useSideNavState } from '@/store/sideNavState' -import { usePathname } from 'next/navigation' -import { useEffect } from 'react' import { useSessionStore } from '@/store/session' declare global { interface Window { + neoAlert: (msg?: string, alertBtn?: Function) => void neoConfirm: (msg?: string, alertBtn2Yes?: Function, alertBtn2No?: Function) => boolean } } @@ -55,6 +57,10 @@ export default function EdgeProvider({ children, sessionData }: EdgeProviderProp window.alert = function (msg, alertBtn = () => setAlert(false)) { alertFunc(msg, alertBtn) } + window.neoAlert = function (msg?: string, alertBtn = () => setAlert(false)) { + if (!msg) return + alertFunc(msg, alertBtn) + } // confirm 함수 변경해서 바인딩 window.neoConfirm = function (msg: string | undefined, alertBtn2Yes?: Function, alertBtn2No?: Function) { if (!msg) return false diff --git a/src/store/userInfoState.ts b/src/store/userInfoState.ts deleted file mode 100644 index 275c2b1..0000000 --- a/src/store/userInfoState.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { create } from 'zustand' - -type UserInfo = { - loginId: string - chgType: string - emali: string - pwd: string - chgPwd: string -} - -type UserInfoState = { - userInfo: UserInfo - setUserInfo: (value: UserInfo) => void - reset: () => void -} - -type InitialState = { - loginId: string - chgType: string - emali: string - pwd: string - chgPwd: string -} - -const initialState: InitialState = { - loginId: '', - chgType: '', - emali: '', - pwd: '', - chgPwd: '', -} - -export const useUserInfoState = create((set) => ({ - userInfo: initialState, - setUserInfo: (value: UserInfo) => set((state) => ({ ...state, userInfo: { ...state.userInfo, ...value } })), - reset: () => set({ userInfo: initialState }), -})) From 6eb94dc82cd199ace1f2289b108f9a9ca80d4310 Mon Sep 17 00:00:00 2001 From: "DESKTOP-EJG0M5T\\User" Date: Thu, 15 May 2025 11:28:29 +0900 Subject: [PATCH 06/10] =?UTF-8?q?style:=20=EC=A7=80=EB=B6=95=EC=9E=AC=20?= =?UTF-8?q?=EC=A0=81=ED=95=A9=EC=84=B1=20=ED=99=94=EB=A9=B4=20css=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/styles/base/_check-radio.scss | 34 ++++++++++++++----------------- src/styles/components/_sub.scss | 5 +++-- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/styles/base/_check-radio.scss b/src/styles/base/_check-radio.scss index 3df63fa..d4d2904 100644 --- a/src/styles/base/_check-radio.scss +++ b/src/styles/base/_check-radio.scss @@ -3,7 +3,9 @@ // check radio 공통 .check-form-box input[type="checkbox"], .radio-form-box input[type="radio"]{ - position: static; + position: absolute; + top: 0; + left: 0; margin-left:0; opacity: 0; z-index: 1; @@ -44,20 +46,24 @@ border: 1px solid #A8B6C7; border-radius: 4px; background-color: #fff; - transition: border 0.15s ease-in-out, color 0.15s ease-in-out; + transition: border 0.15s ease-in-out, color 0.15s ease-in-out, background-color 0.15s ease-in-out; cursor: pointer; } &::after{ content: ""; display: inline-block; position: absolute; - width: 20px; - height: 20px; - top: 0px; - left: 0; - margin-left: 0; - border-color: #fff; + top: -1px; + left: -1px; + width: 7px; + height: 9px; + border: 2px solid transparent; + border-left: none; + border-top: none; + transform: translate(7.75px,4.5px) rotate(45deg); + -ms-transform: translate(7.75px,4.5px) rotate(45deg); cursor: pointer; + transition: border 0.15s ease-in-out, color 0.15s ease-in-out; } } input[type="checkbox"]:checked + label::before{ @@ -66,17 +72,7 @@ } input[type="checkbox"]:checked + label::after{ content: ""; - display: inline-block; - position: absolute; - top: -1px; - left: -1px; - width: 7px; - height: 9px; - border: 2px solid #fff; - border-left: none; - border-top: none; - transform: translate(7.75px,4.5px) rotate(45deg); - -ms-transform: translate(7.75px,4.5px) rotate(45deg); + border-color: #fff; } input[type="checkbox"]:disabled + label{ opacity: 0.7; diff --git a/src/styles/components/_sub.scss b/src/styles/components/_sub.scss index 6e6a5fb..5739fbe 100644 --- a/src/styles/components/_sub.scss +++ b/src/styles/components/_sub.scss @@ -576,6 +576,7 @@ @include defaultFont($font-s-13, $font-w-500, $font-c); } .check-name-btn{ + padding-left: 5px; margin-left: auto; .bx-btn{ display: block; @@ -621,13 +622,13 @@ } .check-item-wrap{ - display: flex; + @include flex(0px); align-items: center; } .compliance-icon-wrap{ margin-left: auto; min-width: 44px; - display: flex; + @include flex(0px); align-items: center; } From 1185d08c8d449c63f27cbd6e97474ba2f1e9c64a Mon Sep 17 00:00:00 2001 From: Daseul Kim Date: Thu, 15 May 2025 13:45:29 +0900 Subject: [PATCH 07/10] =?UTF-8?q?style:=20=EC=A7=80=EB=B6=95=EC=9E=AC=20?= =?UTF-8?q?=EC=A0=81=ED=95=A9=EC=84=B1=20=EA=B2=B0=EA=B3=BC=20=EC=95=84?= =?UTF-8?q?=EC=9D=B4=EC=BD=98=20css=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/styles/components/_sub.scss | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/styles/components/_sub.scss b/src/styles/components/_sub.scss index 5739fbe..ebcb374 100644 --- a/src/styles/components/_sub.scss +++ b/src/styles/components/_sub.scss @@ -529,24 +529,7 @@ // 지붕재 적합성 .compliance-icon{ - display: block; - width: 22px; - height: 22px; - background-size: cover; - background-repeat: no-repeat; - background-position: center; - &.check{ - background-image: url(/assets/images/sub/compliance_check_icon.svg); - } - &.x{ - background-image: url(/assets/images/sub/compliance_x_icon.svg); - } - &.quest{ - background-image: url(/assets/images/sub/compliance_quest_icon.svg); - } - &.tip{ - background-image: url(/assets/images/sub/compliance_tip_icon.svg); - } + display: flex; } .compliance-check-wrap{ padding-top: 10px; From 7a23f02e85ce777177c0133391ea666697985900 Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Thu, 15 May 2025 14:58:25 +0900 Subject: [PATCH 08/10] feat: add store name and role properties to default session data --- src/libs/session.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libs/session.ts b/src/libs/session.ts index 21a0479..5036e4e 100644 --- a/src/libs/session.ts +++ b/src/libs/session.ts @@ -25,6 +25,7 @@ export const defaultSession: SessionData = { compCd: null, agencyStoreId: null, storeId: null, + storeNm: null, userId: null, category: null, userNm: null, @@ -45,6 +46,7 @@ export const defaultSession: SessionData = { custCd: null, builderNo: null, isLoggedIn: false, + role: null, } export const getSession = async () => { From d5f30a565f23214ab87abd74969e9f560e1884a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EC=8B=9D?= <43837214+Minsiki@users.noreply.github.com> Date: Thu, 15 May 2025 15:15:01 +0900 Subject: [PATCH 09/10] =?UTF-8?q?[1:1=20=EB=AC=B8=EC=9D=98]=20=ED=8D=BC?= =?UTF-8?q?=EB=B8=94=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/inquiry/Answer.tsx | 2 +- src/components/inquiry/Detail.tsx | 16 +++++++++-- src/components/inquiry/ListTable.tsx | 29 ++++++++++++++++--- src/components/inquiry/RegistForm.tsx | 40 ++++++++++++++++++++++----- 4 files changed, 73 insertions(+), 14 deletions(-) diff --git a/src/components/inquiry/Answer.tsx b/src/components/inquiry/Answer.tsx index c1a5ec9..4eaa9a0 100644 --- a/src/components/inquiry/Answer.tsx +++ b/src/components/inquiry/Answer.tsx @@ -11,7 +11,7 @@ export default function Answer() {
-
回答
+
回答
一次側接続は, 自動切替開閉器と住宅分電盤昼間遮断器との間に蓄電システム遮断器を配線する方法です. 二次側接続は, 住宅分電盤週間ブレーカの二次側に蓄電システムブレーカを接続する diff --git a/src/components/inquiry/Detail.tsx b/src/components/inquiry/Detail.tsx index d41d5a0..3dcb625 100644 --- a/src/components/inquiry/Detail.tsx +++ b/src/components/inquiry/Detail.tsx @@ -7,7 +7,7 @@ export default function Detail() { //todo: 답변 완료 표시를 위해 임시로 추가 해 놓은 state // 추후에 api 작업 완료후 삭제 // 답변 완료 클래스 & 하단 답변내용 출력도 - const [inquiry, setInquiry] = useState(false) + const [inquiry, setInquiry] = useState(true) return ( <> @@ -31,6 +31,14 @@ export default function Detail() { 作者 Hong gi + + 名前 + Kim + + + 番号 + 070-1234-5678 + 販売店 interplug @@ -47,7 +55,11 @@ export default function Detail() {
-
屋根適合
+
+ 屋根 + 適合性 + 屋根材 +
屋根材適合性確認依頼
入力した内容が表示されます. diff --git a/src/components/inquiry/ListTable.tsx b/src/components/inquiry/ListTable.tsx index 4735ef1..95911a6 100644 --- a/src/components/inquiry/ListTable.tsx +++ b/src/components/inquiry/ListTable.tsx @@ -27,7 +27,11 @@ export default function ListTable() {
  • -
    屋根
    +
    + 屋根 + 適合性 + 屋根材 +
    屋根材適合性確認依頼
    2025.04.02
    回答待ち
    @@ -35,7 +39,11 @@ export default function ListTable() {
  • -
    設計
    +
    + 屋根 + 適合性 + 屋根材 +
    設置可能ですか?
    2025.04.02
    回答完了
    @@ -43,7 +51,11 @@ export default function ListTable() {
  • -
    屋根
    +
    + 屋根 + 適合性 + 屋根材 +
    屋根材適合性確認依頼屋根材適合性確認依頼屋根材適合性確認依頼屋根材適合性確認依頼
    2025.04.02
    回答待ち
    @@ -51,12 +63,21 @@ export default function ListTable() {
  • -
    設計
    +
    + 屋根 + 適合性 + 屋根材 +
    設置可能ですか?
    2025.04.02
    回答完了
  • +
  • +
    +
    조회된 데이터가 없습니다
    +
    +
+
+ +
+
+ +
+
+
+
+ 名前 * +
+
+ +
+
+
+
電話番号
+
+ +
お問い合わせタイトル *
- +
From 510317c67679a2e0156c82cbcface72f4fb2d93d Mon Sep 17 00:00:00 2001 From: yoosangwook Date: Thu, 15 May 2025 16:30:46 +0900 Subject: [PATCH 10/10] feat: implement authentication and survey sales APIs with suitable data structure --- src/api/auth.ts | 22 +++++ src/api/suitable.ts | 107 +++++++++++++++++++++ src/api/surveySales.ts | 127 ++++++++++++++++++++++++ src/api/user.ts | 37 +++++++ src/app/api/survey-sale/[id]/route.ts | 133 +++++++++++++------------- src/app/api/user/route.ts | 7 +- src/hooks/useSuitable.ts | 6 +- 7 files changed, 367 insertions(+), 72 deletions(-) create mode 100644 src/api/auth.ts create mode 100644 src/api/suitable.ts create mode 100644 src/api/surveySales.ts create mode 100644 src/api/user.ts diff --git a/src/api/auth.ts b/src/api/auth.ts new file mode 100644 index 0000000..00a415e --- /dev/null +++ b/src/api/auth.ts @@ -0,0 +1,22 @@ +'use server' + +import { sessionOptions } from '@/libs/session' +import type { SessionData } from '@/types/Auth' +import { getIronSession } from 'iron-session' +import { cookies } from 'next/headers' +import { redirect } from 'next/navigation' + +export const logout = async () => { + const cookieStore = await cookies() + const session = await getIronSession(cookieStore, sessionOptions) + + session.destroy() + return redirect('/login') +} + +export const getSession = async () => { + const cookieStore = await cookies() + const session = await getIronSession(cookieStore, sessionOptions) + + return session +} diff --git a/src/api/suitable.ts b/src/api/suitable.ts new file mode 100644 index 0000000..952f956 --- /dev/null +++ b/src/api/suitable.ts @@ -0,0 +1,107 @@ +import { database } from '@/data' +import { axiosInstance } from '@/libs/axios' + +export interface Suitable { + id?: number + product_name: string + manufacturer: string + roof_material: string + shape: string + support_roof_tile: string + support_roof_tile_memo: string + support_roof_bracket: string + support_roof_bracket_memo: string + yg_anchor: string + yg_anchor_memo: string + rg_roof_tile_part: string + rg_roof_tile_part_memo: string + dido_hunt_support_tile_2: string + dido_hunt_support_tile_2_memo: string + takashima_power_base: string + takashima_power_base_memo: string + takashima_tile_bracket: string + takashima_tile_bracket_memo: string + slate_bracket_4: string + slate_bracket_4_memo: string + slate_single_metal_bracket: string + slate_single_metal_bracket_memo: string + dido_hunt_short_rack_4: string + dido_hunt_short_rack_4_memo: string + takashima_slate_bracket_slate_single: string + takashima_slate_bracket_slate_single_memo: string + df_metal_bracket: string + df_metal_bracket_memo: string + slate_metal_bracket: string + slate_metal_bracket_memo: string + takashima_slate_bracket_metal_roof: string + takashima_slate_bracket_metal_roof_memo: string +} + +export const suitableApi = { + // getList: async (category?: string, keyword?: string): Promise => { + // let condition: any = {} + // if (category) { + // condition['category'] = category + // } + // if (keyword) { + // condition['keyword'] = { + // contains: keyword, + // } + // } + // console.log('🚀 ~ getList: ~ condition:', condition) + // const response = await axiosInstance(null).get('/api/suitable/list', { params: condition }) + // console.log('🚀 ~ getList: ~ response:', response) + // return response.data + // }, + // getCategory: async (): Promise => { + // const response = await axiosInstance(null).get('/api/suitable/category') + // console.log('🚀 ~ getCategory: ~ response:', response) + // return response.data + // }, + // getDetails: async (roofMaterial: string): Promise => { + // const response = await axiosInstance(null).get(`/api/suitable/details?roof-material=${roofMaterial}`) + // console.log('🚀 ~ getDetails: ~ response:', response) + // return response.data + // }, + // create: async () => { + // const suitableData: Suitable[] = [] + // database.forEach((item) => { + // suitableData.push({ + // product_name: item[0], + // manufacturer: item[1], + // roof_material: item[2], + // shape: item[3], + // support_roof_tile: item[4], + // support_roof_tile_memo: item[5], + // support_roof_bracket: item[6], + // support_roof_bracket_memo: item[7], + // yg_anchor: item[8], + // yg_anchor_memo: item[9], + // rg_roof_tile_part: item[10], + // rg_roof_tile_part_memo: item[11], + // dido_hunt_support_tile_2: item[12], + // dido_hunt_support_tile_2_memo: item[13], + // takashima_power_base: item[14], + // takashima_power_base_memo: item[15], + // takashima_tile_bracket: item[16], + // takashima_tile_bracket_memo: item[17], + // slate_bracket_4: item[18], + // slate_bracket_4_memo: item[19], + // slate_single_metal_bracket: item[20], + // slate_single_metal_bracket_memo: item[21], + // dido_hunt_short_rack_4: item[22], + // dido_hunt_short_rack_4_memo: item[23], + // takashima_slate_bracket_slate_single: item[24], + // takashima_slate_bracket_slate_single_memo: item[25], + // df_metal_bracket: item[26], + // df_metal_bracket_memo: item[27], + // slate_metal_bracket: item[28], + // slate_metal_bracket_memo: item[29], + // takashima_slate_bracket_metal_roof: item[30], + // takashima_slate_bracket_metal_roof_memo: item[31], + // }) + // }) + // const response = await axiosInstance(null).post('/api/suitable', suitableData) + // return response.data + // }, +} diff --git a/src/api/surveySales.ts b/src/api/surveySales.ts new file mode 100644 index 0000000..b17f053 --- /dev/null +++ b/src/api/surveySales.ts @@ -0,0 +1,127 @@ +import { axiosInstance } from '@/libs/axios' + +export interface SurveySalesBasicInfo { + id?: number + representative: string + store: string | null + construction_point: string | null + investigation_date: string | null + building_name: string | null + customer_name: string | null + post_code: string | null + address: string | null + address_detail: string | null + submission_status: boolean + submission_date?: string | null + detail_info?: SurveySalesDetailInfo | null +} + +export interface SurveySalesDetailInfo { + id?: number + contract_capacity: string | null + retail_company: string | null + supplementary_facilities: number | null + supplementary_facilities_etc: string | null + installation_system: number | null + installation_system_etc: string | null + construction_year: number | null + construction_year_etc: string | null + roof_material: number | null + roof_material_etc: string | null + roof_shape: number | null + roof_shape_etc: string | null + roof_slope: string | null + house_structure: number | null + house_structure_etc: string | null + rafter_material: number | null + rafter_material_etc: string | null + rafter_size: number | null + rafter_size_etc: string | null + rafter_pitch: number | null + rafter_pitch_etc: string | null + rafter_direction: number | null + open_field_plate_kind: number | null + open_field_plate_kind_etc: string | null + open_field_plate_thickness: string | null + leak_trace: boolean | null + waterproof_material: number | null + waterproof_material_etc: string | null + insulation_presence: number | null + insulation_presence_etc: string | null + structure_order: number | null + structure_order_etc: string | null + installation_availability: number | null + installation_availability_etc: string | null + memo: string | null +} + +export const surveySalesApi = { + create: async (data: SurveySalesBasicInfo): Promise => { + try { + const response = await axiosInstance(null).post('/api/survey-sales', data) + return response.data.id ?? 0 + } catch (error) { + console.error(error) + return 0 + } + }, + getList: async (): Promise => { + try { + const response = await axiosInstance(null).get('/api/survey-sales') + return response.data + } catch (error) { + console.error(error) + return [] + } + }, + getDetail: async (id: number): Promise => { + try { + const response = await axiosInstance(null).get(`/api/survey-sales/${id}`) + return response.data + } catch (error) { + console.error(error) + return null + } + }, + update: async (id: number, data: SurveySalesBasicInfo): Promise => { + try { + const response = await axiosInstance(null).put(`/api/survey-sales/${id}`, data) + return response.data + } catch (error) { + console.error(error) + return null + } + }, + delete: async (id: number, isDetail: boolean = false): Promise => { + try { + await axiosInstance(null).delete(`/api/survey-sales/${id}`, { + params: { + detail_id: isDetail ? id : undefined, + }, + }) + return true + } catch (error) { + throw error + } + }, + createDetail: async (surveyId: number, data: SurveySalesDetailInfo): Promise => { + try { + await axiosInstance(null).post(`/api/survey-sales/${surveyId}`, data) + return true + } catch (error) { + throw error + } + }, + confirm: async (id: number): Promise => { + try { + await axiosInstance(null).patch(`/api/survey-sales/${id}`) + return true + } catch (error) { + throw error + } + }, + // update: async (data: SurveySalesBasicInfo): Promise => { + // const response = await axiosInstance.put(`/api/survey-sales`, data) + // return response.data + // }, +} diff --git a/src/api/user.ts b/src/api/user.ts new file mode 100644 index 0000000..6b39cf0 --- /dev/null +++ b/src/api/user.ts @@ -0,0 +1,37 @@ +import { axiosInstance } from '@/libs/axios' + +export interface UserData { + username: string + email: string + password: string +} + +export interface User { + id: number + username: string + email: string + created_at: string + updated_at: string +} + +export interface LoginData { + username: string + password: string +} + +export const userApi = { + create: async (data: UserData): Promise => { + const response = await axiosInstance(null).post('/api/user/create', data) + return response.data + }, + + getList: async (): Promise => { + const response = await axiosInstance(null).get('/api/user/list') + return response.data + }, + + getUser: async (data: LoginData): Promise => { + const response = await axiosInstance(null).post(`/api/user`, data) + return response.data + }, +} diff --git a/src/app/api/survey-sale/[id]/route.ts b/src/app/api/survey-sale/[id]/route.ts index bf5afe4..04ccf2d 100644 --- a/src/app/api/survey-sale/[id]/route.ts +++ b/src/app/api/survey-sale/[id]/route.ts @@ -1,72 +1,73 @@ import { NextResponse } from 'next/server' -export async function POST(request: Request, context: { params: { id: string } }) { - const body = await request.json() - const { id } = await context.params +// export async function POST(request: Request, { params }: { params: { id: string } }) { +// const body = await request.json() +// const { id } = params - // @ts-ignore - const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({ - where: { id: Number(id) }, - data: { - detail_info: { - create: body, - }, - }, - }) - return NextResponse.json({ message: 'Survey detail created successfully' }) -} -export async function GET(request: Request, context: { params: { id: string } }) { - const { id } = await context.params - // @ts-ignore - const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.findUnique({ - where: { id: Number(id) }, - include: { - detail_info: true, - }, - }) - return NextResponse.json(survey) -} +// // @ts-ignore +// const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({ +// where: { id: Number(id) }, +// data: { +// detail_info: { +// create: body, +// }, +// }, +// }) +// return NextResponse.json({ message: 'Survey detail created successfully' }) +// } -export async function PUT(request: Request, context: { params: { id: string } }) { - const { id } = await context.params - const body = await request.json() - // @ts-ignore - const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({ - where: { id: Number(id) }, - data: { - ...body, - detail_info: { - update: body.detail_info, - }, - }, - }) - return NextResponse.json(survey) -} +// export async function GET(request: Request, { params }: { params: { id: string } }) { +// const { id } = params +// // @ts-ignore +// const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.findUnique({ +// where: { id: Number(id) }, +// include: { +// detail_info: true, +// }, +// }) +// return NextResponse.json(survey) +// } -export async function DELETE(request: Request, context: { params: { id: string; detail_id: string } }) { - const { id, detail_id } = await context.params - if (detail_id) { - // @ts-ignore - const survey = await prisma.SD_SERVEY_SALES_DETAIL_INFO.delete({ - where: { id: Number(detail_id) }, - }) - } else { - // @ts-ignore - const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.delete({ - where: { id: Number(id) }, - }) - } - return NextResponse.json({ message: 'Survey deleted successfully' }) -} +// export async function PUT(request: Request, { params }: { params: { id: string } }) { +// const { id } = params +// const body = await request.json() +// // @ts-ignore +// const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({ +// where: { id: Number(id) }, +// data: { +// ...body, +// detail_info: { +// update: body.detail_info, +// }, +// }, +// }) +// return NextResponse.json(survey) +// } -export async function PATCH(request: Request, context: { params: { id: string } }) { - const { id } = await context.params - // @ts-ignore - const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({ - where: { id: Number(id) }, - data: { - submission_status: true, - }, - }) - return NextResponse.json({ message: 'Survey confirmed successfully' }) -} +// export async function DELETE(request: Request, { params }: { params: { id: string; detail_id: string } }) { +// const { id, detail_id } = params +// if (detail_id) { +// // @ts-ignore +// const survey = await prisma.SD_SERVEY_SALES_DETAIL_INFO.delete({ +// where: { id: Number(detail_id) }, +// }) +// } else { +// // @ts-ignore +// const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.delete({ +// where: { id: Number(id) }, +// }) +// } +// return NextResponse.json({ message: 'Survey deleted successfully' }) +// } + +// export async function PATCH(request: Request, { params }: { params: { id: string } }) { +// const { id } = params +// // @ts-ignore +// const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({ +// where: { id: Number(id) }, +// data: { +// submission_status: true, +// }, +// }) +// return NextResponse.json({ message: 'Survey confirmed successfully' }) +// } diff --git a/src/app/api/user/route.ts b/src/app/api/user/route.ts index d3cda5f..0249fd6 100644 --- a/src/app/api/user/route.ts +++ b/src/app/api/user/route.ts @@ -2,7 +2,8 @@ import { NextResponse } from 'next/server' import { prisma } from '@/libs/prisma' import { getIronSession } from 'iron-session' import { cookies } from 'next/headers' -import { SessionData, sessionOptions } from '@/libs/session' +import { sessionOptions } from '@/libs/session' +import type { SessionData } from '@/types/Auth' export async function POST(request: Request) { const { username, password } = await request.json() @@ -25,8 +26,8 @@ export async function POST(request: Request) { const cookieStore = await cookies() const session = await getIronSession(cookieStore, sessionOptions) console.log('start session edit!') - session.username = user.username! - session.email = user.email! + // session.username = user.username! + // session.email = user.email! session.isLoggedIn = true console.log('end session edit!') await session.save() diff --git a/src/hooks/useSuitable.ts b/src/hooks/useSuitable.ts index 62f00e2..c3f0dde 100644 --- a/src/hooks/useSuitable.ts +++ b/src/hooks/useSuitable.ts @@ -3,7 +3,7 @@ import { suitableApi } from '@/api/suitable' export function useSuitable() { const getCategories = async () => { try { - return await suitableApi.getCategory() + // return await suitableApi.getCategory() } catch (error) { console.error('카테고리 데이터 로드 실패:', error) return [] @@ -12,7 +12,7 @@ export function useSuitable() { const getSuitables = async () => { try { - return await suitableApi.getList() + // return await suitableApi.getList() } catch (error) { console.error('지붕재 데이터 로드 실패:', error) } @@ -20,7 +20,7 @@ export function useSuitable() { const updateSearchResults = async (selectedCategory: string | undefined, searchValue: string | undefined) => { try { - return await suitableApi.getList(selectedCategory, searchValue) + // return await suitableApi.getList(selectedCategory, searchValue) } catch (error) { console.error('지붕재 데이터 검색 실패:', error) }