[설정 및 상태 관리 개선] Nuxt 설정에 pinia-plugin-persistedstate 추가, package.json 및 package-lock.json 업데이트, tab 및 user 스토어에 persist 기능 적용
This commit is contained in:
		@@ -7,6 +7,7 @@ export default defineNuxtConfig({
 | 
				
			|||||||
    "@nuxt/image",
 | 
					    "@nuxt/image",
 | 
				
			||||||
    "@nuxt/icon",
 | 
					    "@nuxt/icon",
 | 
				
			||||||
    "@pinia/nuxt",
 | 
					    "@pinia/nuxt",
 | 
				
			||||||
 | 
					    "pinia-plugin-persistedstate/nuxt",
 | 
				
			||||||
    "@nuxtjs/tailwindcss",
 | 
					    "@nuxtjs/tailwindcss",
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  app: {
 | 
					  app: {
 | 
				
			||||||
@@ -17,14 +18,12 @@ export default defineNuxtConfig({
 | 
				
			|||||||
          href: "https://fonts.googleapis.com/icon?family=Material+Icons",
 | 
					          href: "https://fonts.googleapis.com/icon?family=Material+Icons",
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
      script: [
 | 
					      script: [{ src: "/dist/igv.js", defer: true }],
 | 
				
			||||||
        { src: '/dist/igv.js', defer: true }
 | 
					 | 
				
			||||||
      ]
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  vite: {
 | 
					  vite: {
 | 
				
			||||||
    optimizeDeps: {
 | 
					    optimizeDeps: {
 | 
				
			||||||
      include: ['cytoscape-overlays'],
 | 
					      include: ["cytoscape-overlays"],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    build: {
 | 
					    build: {
 | 
				
			||||||
      commonjsOptions: {
 | 
					      commonjsOptions: {
 | 
				
			||||||
@@ -33,20 +32,20 @@ export default defineNuxtConfig({
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  nitro: {
 | 
					  nitro: {
 | 
				
			||||||
    logLevel: 'debug'
 | 
					    logLevel: "debug",
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  runtimeConfig: {
 | 
					  runtimeConfig: {
 | 
				
			||||||
    public: {
 | 
					    public: {
 | 
				
			||||||
      apiBase: process.env.API_BASE || 'http://localhost',
 | 
					      apiBase: process.env.API_BASE || "http://localhost",
 | 
				
			||||||
      contextPath: process.env.CONTEXT_PATH || '/service',
 | 
					      contextPath: process.env.CONTEXT_PATH || "/service",
 | 
				
			||||||
    }
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  typescript: {
 | 
					  typescript: {
 | 
				
			||||||
    shim: false,
 | 
					    shim: false,
 | 
				
			||||||
    strict: true,
 | 
					    strict: true,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  plugins: ['~/plugins/vue3-tui-grid.client.ts'],
 | 
					  plugins: ["~/plugins/vue3-tui-grid.client.ts"],
 | 
				
			||||||
  components: [
 | 
					  components: [
 | 
				
			||||||
    { path: '~/components', pathPrefix: false }, // 경로 접두사 제거
 | 
					    { path: "~/components", pathPrefix: false }, // 경로 접두사 제거
 | 
				
			||||||
  ]
 | 
					  ],
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										42
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										42
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -11,7 +11,7 @@
 | 
				
			|||||||
        "@nuxt/icon": "^1.14.0",
 | 
					        "@nuxt/icon": "^1.14.0",
 | 
				
			||||||
        "@nuxt/image": "^1.10.0",
 | 
					        "@nuxt/image": "^1.10.0",
 | 
				
			||||||
        "@nuxtjs/tailwindcss": "^7.0.0-beta.0",
 | 
					        "@nuxtjs/tailwindcss": "^7.0.0-beta.0",
 | 
				
			||||||
        "@pinia/nuxt": "^0.11.1",
 | 
					        "@pinia/nuxt": "^0.11.2",
 | 
				
			||||||
        "ag-grid-community": "^34.0.0",
 | 
					        "ag-grid-community": "^34.0.0",
 | 
				
			||||||
        "ag-grid-vue3": "^34.0.0",
 | 
					        "ag-grid-vue3": "^34.0.0",
 | 
				
			||||||
        "chart.js": "^4.5.0",
 | 
					        "chart.js": "^4.5.0",
 | 
				
			||||||
@@ -22,6 +22,7 @@
 | 
				
			|||||||
        "eslint": "^9.29.0",
 | 
					        "eslint": "^9.29.0",
 | 
				
			||||||
        "nuxt": "^3.17.5",
 | 
					        "nuxt": "^3.17.5",
 | 
				
			||||||
        "pinia": "^3.0.3",
 | 
					        "pinia": "^3.0.3",
 | 
				
			||||||
 | 
					        "pinia-plugin-persistedstate": "^4.5.0",
 | 
				
			||||||
        "tui-code-snippet": "^2.3.3",
 | 
					        "tui-code-snippet": "^2.3.3",
 | 
				
			||||||
        "tui-grid": "^4.21.22",
 | 
					        "tui-grid": "^4.21.22",
 | 
				
			||||||
        "vue": "^3.5.17",
 | 
					        "vue": "^3.5.17",
 | 
				
			||||||
@@ -2827,9 +2828,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/@pinia/nuxt": {
 | 
					    "node_modules/@pinia/nuxt": {
 | 
				
			||||||
      "version": "0.11.1",
 | 
					      "version": "0.11.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@pinia/nuxt/-/nuxt-0.11.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@pinia/nuxt/-/nuxt-0.11.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-tCD8ioWhhIHKwm8Y9VvyhBAV/kK4W5uGBIYbI5iM4N1t7duOqK6ECBUavrMxMolELayqqMLb9+evegrh3S7s2A==",
 | 
					      "integrity": "sha512-CgvSWpbktxxWBV7ModhAcsExsQZqpPq6vMYEe9DexmmY6959ev8ukL4iFhr/qov2Nb9cQAWd7niFDnaWkN+FHg==",
 | 
				
			||||||
      "license": "MIT",
 | 
					      "license": "MIT",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@nuxt/kit": "^3.9.0"
 | 
					        "@nuxt/kit": "^3.9.0"
 | 
				
			||||||
@@ -6086,6 +6087,12 @@
 | 
				
			|||||||
      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
 | 
					      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
 | 
				
			||||||
      "license": "MIT"
 | 
					      "license": "MIT"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/deep-pick-omit": {
 | 
				
			||||||
 | 
					      "version": "1.2.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/deep-pick-omit/-/deep-pick-omit-1.2.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-2J6Kc/m3irCeqVG42T+SaUMesaK7oGWaedGnQQK/+O0gYc+2SP5bKh/KKTE7d7SJ+GCA9UUE1GRzh6oDe0EnGw==",
 | 
				
			||||||
 | 
					      "license": "MIT"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/deepmerge": {
 | 
					    "node_modules/deepmerge": {
 | 
				
			||||||
      "version": "4.3.1",
 | 
					      "version": "4.3.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
 | 
				
			||||||
@@ -10770,6 +10777,33 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/pinia-plugin-persistedstate": {
 | 
				
			||||||
 | 
					      "version": "4.5.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-4.5.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-QTkP1xJVyCdr2I2p3AKUZM84/e+IS+HktRxKGAIuDzkyaKKV48mQcYkJFVVDuvTxlI5j6X3oZObpqoVB8JnWpw==",
 | 
				
			||||||
 | 
					      "license": "MIT",
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "deep-pick-omit": "^1.2.1",
 | 
				
			||||||
 | 
					        "defu": "^6.1.4",
 | 
				
			||||||
 | 
					        "destr": "^2.0.5"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "@nuxt/kit": ">=3.0.0",
 | 
				
			||||||
 | 
					        "@pinia/nuxt": ">=0.10.0",
 | 
				
			||||||
 | 
					        "pinia": ">=3.0.0"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependenciesMeta": {
 | 
				
			||||||
 | 
					        "@nuxt/kit": {
 | 
				
			||||||
 | 
					          "optional": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "@pinia/nuxt": {
 | 
				
			||||||
 | 
					          "optional": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "pinia": {
 | 
				
			||||||
 | 
					          "optional": true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/pkg-types": {
 | 
					    "node_modules/pkg-types": {
 | 
				
			||||||
      "version": "2.2.0",
 | 
					      "version": "2.2.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.2.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.2.0.tgz",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@
 | 
				
			|||||||
    "@nuxt/icon": "^1.14.0",
 | 
					    "@nuxt/icon": "^1.14.0",
 | 
				
			||||||
    "@nuxt/image": "^1.10.0",
 | 
					    "@nuxt/image": "^1.10.0",
 | 
				
			||||||
    "@nuxtjs/tailwindcss": "^7.0.0-beta.0",
 | 
					    "@nuxtjs/tailwindcss": "^7.0.0-beta.0",
 | 
				
			||||||
    "@pinia/nuxt": "^0.11.1",
 | 
					    "@pinia/nuxt": "^0.11.2",
 | 
				
			||||||
    "ag-grid-community": "^34.0.0",
 | 
					    "ag-grid-community": "^34.0.0",
 | 
				
			||||||
    "ag-grid-vue3": "^34.0.0",
 | 
					    "ag-grid-vue3": "^34.0.0",
 | 
				
			||||||
    "chart.js": "^4.5.0",
 | 
					    "chart.js": "^4.5.0",
 | 
				
			||||||
@@ -27,6 +27,7 @@
 | 
				
			|||||||
    "eslint": "^9.29.0",
 | 
					    "eslint": "^9.29.0",
 | 
				
			||||||
    "nuxt": "^3.17.5",
 | 
					    "nuxt": "^3.17.5",
 | 
				
			||||||
    "pinia": "^3.0.3",
 | 
					    "pinia": "^3.0.3",
 | 
				
			||||||
 | 
					    "pinia-plugin-persistedstate": "^4.5.0",
 | 
				
			||||||
    "tui-code-snippet": "^2.3.3",
 | 
					    "tui-code-snippet": "^2.3.3",
 | 
				
			||||||
    "tui-grid": "^4.21.22",
 | 
					    "tui-grid": "^4.21.22",
 | 
				
			||||||
    "vue": "^3.5.17",
 | 
					    "vue": "^3.5.17",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,9 @@
 | 
				
			|||||||
import { defineStore } from "pinia";
 | 
					import { defineStore } from "pinia";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface Tab {
 | 
					interface Tab {
 | 
				
			||||||
  key: number;          // 1~10
 | 
					  key: number; // 1~10
 | 
				
			||||||
  label: string;
 | 
					  label: string;
 | 
				
			||||||
  to: string;           // 페이지 라우트
 | 
					  to: string; // 페이지 라우트
 | 
				
			||||||
  componentName: string;
 | 
					  componentName: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -12,7 +12,7 @@ const defaultTab = { key: 1, label: "홈", to: "/", componentName: "home" };
 | 
				
			|||||||
export const useTabsStore = defineStore("tabs", {
 | 
					export const useTabsStore = defineStore("tabs", {
 | 
				
			||||||
  state: () => ({
 | 
					  state: () => ({
 | 
				
			||||||
    tabs: [defaultTab] as Tab[],
 | 
					    tabs: [defaultTab] as Tab[],
 | 
				
			||||||
    activeTab: 1
 | 
					    activeTab: 1,
 | 
				
			||||||
  }),
 | 
					  }),
 | 
				
			||||||
  actions: {
 | 
					  actions: {
 | 
				
			||||||
    // ✅ 새 탭 추가 (기본 페이지는 "/")
 | 
					    // ✅ 새 탭 추가 (기본 페이지는 "/")
 | 
				
			||||||
@@ -27,7 +27,7 @@ export const useTabsStore = defineStore("tabs", {
 | 
				
			|||||||
      let key = 1;
 | 
					      let key = 1;
 | 
				
			||||||
      while (this.tabs.find(t => t.key === key)) key++;
 | 
					      while (this.tabs.find(t => t.key === key)) key++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.tabs.push({...defaultTab, key : key});
 | 
					      this.tabs.push({ ...defaultTab, key: key });
 | 
				
			||||||
      this.activeTab = key;
 | 
					      this.activeTab = key;
 | 
				
			||||||
      $router.push(defaultTab.to);
 | 
					      $router.push(defaultTab.to);
 | 
				
			||||||
      return key;
 | 
					      return key;
 | 
				
			||||||
@@ -49,7 +49,9 @@ export const useTabsStore = defineStore("tabs", {
 | 
				
			|||||||
    removeTab(key: number) {
 | 
					    removeTab(key: number) {
 | 
				
			||||||
      this.tabs = this.tabs.filter(t => t.key !== key);
 | 
					      this.tabs = this.tabs.filter(t => t.key !== key);
 | 
				
			||||||
      if (this.activeTab === key) {
 | 
					      if (this.activeTab === key) {
 | 
				
			||||||
        this.activeTab = this.tabs.length ? this.tabs[this.tabs.length - 1].key : 0;
 | 
					        this.activeTab = this.tabs.length
 | 
				
			||||||
 | 
					          ? this.tabs[this.tabs.length - 1].key
 | 
				
			||||||
 | 
					          : 0;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -59,6 +61,7 @@ export const useTabsStore = defineStore("tabs", {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      const tab = this.tabs.find(t => t.key === this.activeTab);
 | 
					      const tab = this.tabs.find(t => t.key === this.activeTab);
 | 
				
			||||||
      $router.push(`/${tab?.key}${tab?.to}`);
 | 
					      $router.push(`/${tab?.key}${tab?.to}`);
 | 
				
			||||||
    }
 | 
					    },
 | 
				
			||||||
  }
 | 
					  },
 | 
				
			||||||
 | 
					  persist: true,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										207
									
								
								stores/user.ts
									
									
									
									
									
								
							
							
						
						
									
										207
									
								
								stores/user.ts
									
									
									
									
									
								
							@@ -1,103 +1,112 @@
 | 
				
			|||||||
export const useUserStore = defineStore("user", () => {
 | 
					export const useUserStore = defineStore(
 | 
				
			||||||
  // 상태
 | 
					  "user",
 | 
				
			||||||
  const isLoggedIn = ref(false);
 | 
					  () => {
 | 
				
			||||||
  const user = ref<{
 | 
					 | 
				
			||||||
    userId?: string;
 | 
					 | 
				
			||||||
    name?: string;
 | 
					 | 
				
			||||||
  } | null>(null);
 | 
					 | 
				
			||||||
  const token = ref<string | null>(null);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // 추후 제거 필요
 | 
					 | 
				
			||||||
  const isAdmin = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  interface LoginData {
 | 
					 | 
				
			||||||
    userId: string;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  // 액션
 | 
					 | 
				
			||||||
  const login = async (userId: string, password: string) => {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      // 실제 API 호출로 대체할 수 있습니다
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const { success, data } = await useApi<ApiResponse<LoginData>>("/login", {
 | 
					 | 
				
			||||||
        method: "post",
 | 
					 | 
				
			||||||
        body: { userId, password },
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (success) {
 | 
					 | 
				
			||||||
        user.value = data;
 | 
					 | 
				
			||||||
        isLoggedIn.value = true;
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        throw new Error("아이디 또는 비밀번호가 올바르지 않습니다.");
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return { success };
 | 
					 | 
				
			||||||
    } catch (error: any) {
 | 
					 | 
				
			||||||
      console.log(error);
 | 
					 | 
				
			||||||
      return {
 | 
					 | 
				
			||||||
        success: false,
 | 
					 | 
				
			||||||
        error:
 | 
					 | 
				
			||||||
          error?.response?.status === 401
 | 
					 | 
				
			||||||
            ? "아이디 또는 비밀번호가 올바르지 않습니다."
 | 
					 | 
				
			||||||
            : error instanceof Error
 | 
					 | 
				
			||||||
              ? error.message
 | 
					 | 
				
			||||||
              : "로그인에 실패했습니다.",
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const logout = async () => {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      await useApi("/members/logout", {
 | 
					 | 
				
			||||||
        method: "post",
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    } catch (error) {
 | 
					 | 
				
			||||||
      console.error("로그아웃 요청 실패:", error);
 | 
					 | 
				
			||||||
    } finally {
 | 
					 | 
				
			||||||
      // 로컬 상태 정리
 | 
					 | 
				
			||||||
      user.value = null;
 | 
					 | 
				
			||||||
      isLoggedIn.value = false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const checkAuth = () => {
 | 
					 | 
				
			||||||
    // 페이지 로드 시 로컬 스토리지에서 사용자 정보 복원
 | 
					 | 
				
			||||||
    const savedUser = localStorage.getItem("user");
 | 
					 | 
				
			||||||
    const savedToken = localStorage.getItem("token");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (savedUser && savedToken) {
 | 
					 | 
				
			||||||
      user.value = JSON.parse(savedUser);
 | 
					 | 
				
			||||||
      token.value = savedToken;
 | 
					 | 
				
			||||||
      isLoggedIn.value = true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const setToken = (accessToken: string) => {
 | 
					 | 
				
			||||||
    token.value = accessToken;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const getToken = () => {
 | 
					 | 
				
			||||||
    return token;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // 초기 인증 상태 확인
 | 
					 | 
				
			||||||
  if (import.meta.client) {
 | 
					 | 
				
			||||||
    checkAuth();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    // 상태
 | 
					    // 상태
 | 
				
			||||||
    isLoggedIn,
 | 
					    const isLoggedIn = ref(false);
 | 
				
			||||||
    user,
 | 
					    const user = ref<{
 | 
				
			||||||
    token,
 | 
					      userId?: string;
 | 
				
			||||||
 | 
					      name?: string;
 | 
				
			||||||
 | 
					    } | null>(null);
 | 
				
			||||||
 | 
					    const token = ref<string | null>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 게터
 | 
					    // 추후 제거 필요
 | 
				
			||||||
    isAdmin,
 | 
					    const isAdmin = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    interface LoginData {
 | 
				
			||||||
 | 
					      userId: string;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    // 액션
 | 
					    // 액션
 | 
				
			||||||
    login,
 | 
					    const login = async (userId: string, password: string) => {
 | 
				
			||||||
    logout,
 | 
					      try {
 | 
				
			||||||
    checkAuth,
 | 
					        // 실제 API 호출로 대체할 수 있습니다
 | 
				
			||||||
    setToken,
 | 
					
 | 
				
			||||||
    getToken,
 | 
					        const { success, data } = await useApi<ApiResponse<LoginData>>(
 | 
				
			||||||
  };
 | 
					          "/login",
 | 
				
			||||||
});
 | 
					          {
 | 
				
			||||||
 | 
					            method: "post",
 | 
				
			||||||
 | 
					            body: { userId, password },
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (success) {
 | 
				
			||||||
 | 
					          user.value = data;
 | 
				
			||||||
 | 
					          isLoggedIn.value = true;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          throw new Error("아이디 또는 비밀번호가 올바르지 않습니다.");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return { success };
 | 
				
			||||||
 | 
					      } catch (error: any) {
 | 
				
			||||||
 | 
					        console.log(error);
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          success: false,
 | 
				
			||||||
 | 
					          error:
 | 
				
			||||||
 | 
					            error?.response?.status === 401
 | 
				
			||||||
 | 
					              ? "아이디 또는 비밀번호가 올바르지 않습니다."
 | 
				
			||||||
 | 
					              : error instanceof Error
 | 
				
			||||||
 | 
					                ? error.message
 | 
				
			||||||
 | 
					                : "로그인에 실패했습니다.",
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const logout = async () => {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        await useApi("/members/logout", {
 | 
				
			||||||
 | 
					          method: "post",
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        console.error("로그아웃 요청 실패:", error);
 | 
				
			||||||
 | 
					      } finally {
 | 
				
			||||||
 | 
					        // 로컬 상태 정리
 | 
				
			||||||
 | 
					        user.value = null;
 | 
				
			||||||
 | 
					        isLoggedIn.value = false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const checkAuth = () => {
 | 
				
			||||||
 | 
					      // 페이지 로드 시 로컬 스토리지에서 사용자 정보 복원
 | 
				
			||||||
 | 
					      const savedUser = localStorage.getItem("user");
 | 
				
			||||||
 | 
					      const savedToken = localStorage.getItem("token");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (savedUser && savedToken) {
 | 
				
			||||||
 | 
					        user.value = JSON.parse(savedUser);
 | 
				
			||||||
 | 
					        token.value = savedToken;
 | 
				
			||||||
 | 
					        isLoggedIn.value = true;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const setToken = (accessToken: string) => {
 | 
				
			||||||
 | 
					      token.value = accessToken;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const getToken = () => {
 | 
				
			||||||
 | 
					      return token;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 초기 인증 상태 확인
 | 
				
			||||||
 | 
					    if (import.meta.client) {
 | 
				
			||||||
 | 
					      checkAuth();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      // 상태
 | 
				
			||||||
 | 
					      isLoggedIn,
 | 
				
			||||||
 | 
					      user,
 | 
				
			||||||
 | 
					      token,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // 게터
 | 
				
			||||||
 | 
					      isAdmin,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // 액션
 | 
				
			||||||
 | 
					      login,
 | 
				
			||||||
 | 
					      logout,
 | 
				
			||||||
 | 
					      checkAuth,
 | 
				
			||||||
 | 
					      setToken,
 | 
				
			||||||
 | 
					      getToken,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    persist: true,
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user