사용자 명시적 운영 배포 승인 ('운영배포까지 진행 ... 될때까지 하라고').
- 로그인 후 redirectTo /dashboard → /m/dashboard 로 통일 (plm_admin 도 모모로)
- 세션 있으면 / · /login · /signup → /m/dashboard 리다이렉트 (middleware)
- /m/items 페이지를 /m/orders/new 로 redirect — 메뉴 통합
- 출고요청 카트를 상단 sticky bar 로 이동, 클릭 시 펼침 + 발주 버튼 항상 노출
- user_info 에 biz_no/ceo_name 컬럼 추가 (migration 006)
- signupMomoUser 가 biz_no/ceo_name 저장하도록 수정
- 메뉴: 9000101 품목 검색 비활성화 (출고요청과 통합으로 중복)
- admin-panel: 메뉴관리 섹션 idempotent 복구 (migration 006)
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
-- 거래처 가입자에 필요한 추가 정보를 user_info 에 직접 컬럼으로 추가 (스펙 §3.1 B안)
|
||||
-- supply_mng 와 user_info.partner_objid 연결도 가능하지만, 신규 가입 흐름 단순화 위해 직접 컬럼 추가.
|
||||
-- 이미 컬럼이 있으면 ADD COLUMN IF NOT EXISTS 로 idempotent.
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE user_info
|
||||
ADD COLUMN IF NOT EXISTS biz_no VARCHAR(20),
|
||||
ADD COLUMN IF NOT EXISTS ceo_name VARCHAR(100);
|
||||
|
||||
-- 품목 검색 메뉴(스펙 §5에서 출고 요청과 통합으로 변경됨) 비활성화
|
||||
UPDATE menu_info SET status = 'inactive' WHERE objid = 9000101;
|
||||
|
||||
-- ===== 관리자 admin-panel 의 [메뉴관리] 섹션 복구 =====
|
||||
-- [관리자] 루트(parent=0, menu_name_kor='관리자') 아래에 [메뉴관리] 섹션 + [메뉴관리] 자식이 status='active' 로 존재해야
|
||||
-- /api/admin/sidebar-menus 가 노출함. 누락된 경우 idempotent 하게 보장.
|
||||
DO $$
|
||||
DECLARE
|
||||
admin_root_id NUMERIC;
|
||||
menu_section_id NUMERIC;
|
||||
BEGIN
|
||||
SELECT objid INTO admin_root_id FROM menu_info
|
||||
WHERE parent_obj_id = 0 AND menu_name_kor = '관리자' LIMIT 1;
|
||||
IF admin_root_id IS NULL THEN
|
||||
RAISE NOTICE '[admin] 루트가 없어 메뉴관리 복구 스킵';
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
-- 섹션이 존재하면 active 로 보장, 없으면 9000600 으로 신규 등록
|
||||
SELECT objid INTO menu_section_id FROM menu_info
|
||||
WHERE parent_obj_id = admin_root_id AND menu_name_kor = '메뉴관리' LIMIT 1;
|
||||
IF menu_section_id IS NULL THEN
|
||||
menu_section_id := 9000600;
|
||||
INSERT INTO menu_info (objid, menu_type, parent_obj_id, menu_name_kor, menu_name_eng,
|
||||
seq, menu_url, status, system_name, regdate)
|
||||
VALUES (menu_section_id, '1', admin_root_id, '메뉴관리', 'Menu Management',
|
||||
10, '', 'active', 'PMS', NOW());
|
||||
ELSE
|
||||
UPDATE menu_info SET status = 'active' WHERE objid = menu_section_id;
|
||||
END IF;
|
||||
|
||||
-- 자식: 메뉴관리 (LABEL_TO_TAB 매핑이 '메뉴관리' → 'menu' 이므로 정확히 동일 이름 필수)
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM menu_info
|
||||
WHERE parent_obj_id = menu_section_id AND menu_name_kor = '메뉴관리' AND COALESCE(status,'') = 'active'
|
||||
) THEN
|
||||
INSERT INTO menu_info (objid, menu_type, parent_obj_id, menu_name_kor, menu_name_eng,
|
||||
seq, menu_url, status, system_name, regdate)
|
||||
VALUES (9000601, '1', menu_section_id, '메뉴관리', 'Menus',
|
||||
10, '', 'active', 'PMS', NOW())
|
||||
ON CONFLICT (objid) DO UPDATE SET status = 'active';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,123 +1,6 @@
|
||||
"use client";
|
||||
// 품목 검색은 /m/orders/new 와 기능이 동일하므로 통합. 이 경로는 호환성 유지를 위해 리다이렉트만 수행.
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Search, ShoppingCart } from "lucide-react";
|
||||
|
||||
interface Item {
|
||||
OBJID: string;
|
||||
ITEM_CODE: string;
|
||||
ITEM_NAME: string;
|
||||
ITEM_DETAIL: string;
|
||||
MAKER_NAME: string;
|
||||
UNIT: string;
|
||||
UNIT_PRICE: number;
|
||||
IS_TAX_FREE: string;
|
||||
IMAGE_URL: string;
|
||||
STOCK_QTY: number;
|
||||
}
|
||||
|
||||
const fmt = (n: number) => Number(n || 0).toLocaleString("ko-KR");
|
||||
|
||||
export default function ItemsBrowsePage() {
|
||||
const router = useRouter();
|
||||
const [items, setItems] = useState<Item[]>([]);
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [taxFilter, setTaxFilter] = useState<"" | "Y" | "N">("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const fetchItems = async () => {
|
||||
setLoading(true);
|
||||
const res = await fetch("/api/m/items/list", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ keyword, isTaxFree: taxFilter || undefined }),
|
||||
});
|
||||
const j = await res.json();
|
||||
setItems(j.RESULTLIST ?? []);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchItems();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full overflow-hidden">
|
||||
{/* 헤더 */}
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-900">품목 검색</h1>
|
||||
<p className="text-slate-500 text-sm mt-1">현재 재고와 단가를 조회합니다. 발주는 [출고 요청] 메뉴에서 진행하세요.</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => router.push("/m/orders/new")}
|
||||
className="px-4 h-10 inline-flex items-center gap-2 rounded-lg bg-emerald-700 text-white text-sm font-bold hover:bg-emerald-800"
|
||||
>
|
||||
<ShoppingCart size={16} /> 출고 요청 작성
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 검색 바 */}
|
||||
<div className="flex gap-2 items-center mb-4">
|
||||
<div className="relative flex-1 max-w-md">
|
||||
<Search size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400" />
|
||||
<input
|
||||
value={keyword}
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && fetchItems()}
|
||||
placeholder="품목명 또는 품목코드"
|
||||
className="w-full h-10 pl-9 pr-3 rounded-lg border border-slate-200 text-sm focus:border-emerald-500 focus:ring-2 focus:ring-emerald-500/15 outline-none"
|
||||
/>
|
||||
</div>
|
||||
<select value={taxFilter} onChange={(e) => setTaxFilter(e.target.value as "" | "Y" | "N")} className="h-10 px-3 rounded-lg border border-slate-200 text-sm">
|
||||
<option value="">전체</option>
|
||||
<option value="Y">면세</option>
|
||||
<option value="N">과세</option>
|
||||
</select>
|
||||
<button onClick={fetchItems} className="h-10 px-4 rounded-lg bg-slate-800 text-white text-sm font-semibold">
|
||||
조회
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 목록 (그리드) — 영역만 스크롤 */}
|
||||
<div className="flex-1 overflow-y-auto pr-1">
|
||||
{loading ? (
|
||||
<div className="text-slate-400 text-center py-12">불러오는 중...</div>
|
||||
) : items.length === 0 ? (
|
||||
<div className="text-slate-400 text-center py-12 bg-white rounded-xl border border-slate-100">검색 결과가 없습니다.</div>
|
||||
) : (
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
|
||||
{items.map((it) => (
|
||||
<div key={it.OBJID} className="bg-white border border-slate-200 rounded-xl p-4 hover:shadow-md transition">
|
||||
<div className="aspect-square bg-slate-50 rounded-lg mb-3 overflow-hidden flex items-center justify-center">
|
||||
{it.IMAGE_URL ? (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img src={it.IMAGE_URL} alt={it.ITEM_NAME} className="w-full h-full object-cover" />
|
||||
) : (
|
||||
<div className="text-slate-300 text-xs">이미지 없음</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-start justify-between gap-2 mb-1">
|
||||
<div className="font-bold text-sm text-slate-900 leading-tight">{it.ITEM_NAME}</div>
|
||||
{it.IS_TAX_FREE === "Y" && (
|
||||
<span className="px-1.5 py-0.5 rounded bg-violet-100 text-violet-700 text-[10px] font-bold">면세</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-slate-500 mb-1">{it.ITEM_CODE}</div>
|
||||
<div className="text-xs text-slate-500 mb-2">{it.MAKER_NAME || "-"}</div>
|
||||
<div className="flex items-baseline justify-between">
|
||||
<div className="font-bold text-slate-900 tabular-nums">₩{fmt(it.UNIT_PRICE)}</div>
|
||||
<div className={`text-xs font-semibold ${Number(it.STOCK_QTY) > 0 ? "text-emerald-700" : "text-rose-500"}`}>
|
||||
재고 {fmt(it.STOCK_QTY)} {it.UNIT}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
export default function ItemsRedirect() {
|
||||
redirect("/m/orders/new");
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Search, ShoppingCart, Plus, Minus, Trash2, X } from "lucide-react";
|
||||
import { Search, ShoppingCart, Plus, Minus, X } from "lucide-react";
|
||||
import Swal from "sweetalert2";
|
||||
|
||||
interface Item {
|
||||
@@ -128,27 +128,94 @@ export default function ItemsBrowse() {
|
||||
}
|
||||
};
|
||||
|
||||
const [cartOpen, setCartOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-[1fr_360px] gap-4 h-full overflow-hidden">
|
||||
<div className="space-y-4 overflow-y-auto pr-1">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-900">출고 요청</h1>
|
||||
<p className="text-slate-500 text-sm mt-1">현재 재고가 있는 품목을 선택해 장바구니에 담고 우측에서 발주를 요청하세요.</p>
|
||||
<div className="flex flex-col h-full overflow-hidden">
|
||||
{/* ===== 상단 sticky 카트 바 — 항상 노출, 클릭하면 내역 펼침 ===== */}
|
||||
<div className="sticky top-0 z-20 bg-white border-2 border-emerald-300 rounded-xl shadow-lg mb-3 overflow-hidden">
|
||||
<button
|
||||
onClick={() => setCartOpen((v) => !v)}
|
||||
className="w-full flex items-center justify-between gap-3 px-4 py-2.5 hover:bg-emerald-50/40 transition"
|
||||
>
|
||||
<div className="flex items-center gap-2 font-bold text-slate-800">
|
||||
<ShoppingCart size={18} className="text-emerald-700" />
|
||||
발주 장바구니
|
||||
<span className="px-2 py-0.5 rounded-full bg-emerald-100 text-emerald-700 text-xs font-bold tabular-nums">
|
||||
{cart.length}
|
||||
</span>
|
||||
</div>
|
||||
{/* 좁은 화면(lg 미만)에서 우측 카트가 아래로 내려가 안 보일 때를 대비한 상단 요약 + 버튼 */}
|
||||
<div className="lg:hidden flex items-center gap-2 shrink-0">
|
||||
<div className="px-3 h-10 inline-flex items-center gap-2 rounded-lg bg-emerald-50 text-emerald-800 border border-emerald-200 text-sm font-bold">
|
||||
<ShoppingCart size={14} /> {cart.length}건 · ₩{fmt(totals.total)}
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="hidden md:inline text-xs text-violet-700 tabular-nums">면세 ₩{fmt(totals.taxFree)}</span>
|
||||
<span className="hidden md:inline text-xs text-rose-700 tabular-nums">과세 ₩{fmt(totals.taxable)}</span>
|
||||
<span className="text-base font-bold text-emerald-700 tabular-nums">₩{fmt(totals.total)}</span>
|
||||
<button
|
||||
onClick={submitOrder}
|
||||
onClick={(e) => { e.stopPropagation(); submitOrder(); }}
|
||||
disabled={cart.length === 0}
|
||||
className="h-10 px-4 rounded-lg bg-emerald-700 text-white text-sm font-bold disabled:bg-slate-200 disabled:text-slate-400"
|
||||
className="h-9 px-4 rounded-lg bg-emerald-700 text-white text-sm font-bold hover:bg-emerald-800 disabled:bg-slate-200 disabled:text-slate-400 disabled:cursor-not-allowed"
|
||||
>
|
||||
발주 요청
|
||||
</button>
|
||||
<span className={`text-slate-400 text-xs transition-transform ${cartOpen ? "rotate-180" : ""}`}>▼</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{cartOpen && (
|
||||
<div className="border-t border-emerald-100 px-4 py-3 max-h-[40vh] overflow-y-auto bg-slate-50/50">
|
||||
{cart.length === 0 ? (
|
||||
<div className="text-slate-400 text-sm text-center py-6">
|
||||
아래 품목 카드의 <span className="font-bold text-emerald-700">+ 담기</span> 버튼으로 추가하세요.
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex justify-end mb-2">
|
||||
<button onClick={() => setCart([])} className="text-xs text-slate-400 hover:text-rose-500">
|
||||
전체 삭제
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-2 gap-2">
|
||||
{cart.map((ln) => {
|
||||
const lineTotal = Math.round(Number(ln.item.UNIT_PRICE) * ln.qty);
|
||||
return (
|
||||
<div key={ln.item.OBJID} className="bg-white border border-slate-100 rounded-lg p-2.5">
|
||||
<div className="flex items-start justify-between gap-2 mb-2">
|
||||
<div className="text-sm font-semibold leading-tight">{ln.item.ITEM_NAME}</div>
|
||||
<button onClick={() => removeLine(ln.item.OBJID)} className="text-slate-300 hover:text-rose-500">
|
||||
<X size={14} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-1">
|
||||
<button onClick={() => updateQty(ln.item.OBJID, -1)} className="w-7 h-7 rounded-md bg-slate-100 hover:bg-slate-200 flex items-center justify-center">
|
||||
<Minus size={12} />
|
||||
</button>
|
||||
<span className="w-10 text-center text-sm font-bold tabular-nums">{ln.qty}</span>
|
||||
<button onClick={() => updateQty(ln.item.OBJID, 1)} className="w-7 h-7 rounded-md bg-slate-100 hover:bg-slate-200 flex items-center justify-center">
|
||||
<Plus size={12} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-sm font-bold tabular-nums">₩{fmt(lineTotal)}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="border-t border-slate-200 mt-3 pt-2 grid grid-cols-2 md:grid-cols-4 gap-2 text-xs">
|
||||
<Row label="면세 합계" value={`₩${fmt(totals.taxFree)}`} color="violet" />
|
||||
<Row label="과세 공급가" value={`₩${fmt(totals.taxable)}`} color="rose" />
|
||||
<Row label="세액" value={`₩${fmt(totals.vat)}`} />
|
||||
<Row label="총 합계" value={`₩${fmt(totals.total)}`} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 space-y-4 overflow-y-auto pr-1">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-900">출고 요청</h1>
|
||||
<p className="text-slate-500 text-sm mt-1">현재 재고가 있는 품목을 선택해 상단 장바구니에 담고 [발주 요청] 버튼으로 전송하세요.</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 items-center">
|
||||
@@ -213,75 +280,6 @@ export default function ItemsBrowse() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 장바구니 — lg 이상은 우측 sticky, 미만은 흐름상 하단으로 떨어져도 항상 노출 */}
|
||||
<aside className="self-start bg-white border-2 border-emerald-300 rounded-xl p-5 shadow-lg flex flex-col max-h-full overflow-hidden">
|
||||
<div className="flex items-center justify-between mb-3 shrink-0">
|
||||
<div className="flex items-center gap-2 font-bold text-slate-800">
|
||||
<ShoppingCart size={16} />
|
||||
발주 장바구니 <span className="text-emerald-700">{cart.length}</span>
|
||||
</div>
|
||||
{cart.length > 0 && (
|
||||
<button onClick={() => setCart([])} className="text-xs text-slate-400 hover:text-rose-500">
|
||||
전체 삭제
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto pr-1 min-h-[120px]">
|
||||
{cart.length === 0 ? (
|
||||
<div className="text-slate-400 text-sm text-center py-10 border border-dashed border-slate-200 rounded-lg">
|
||||
좌측에서 <span className="font-bold text-emerald-700">담기</span> 버튼을 눌러
|
||||
<br /> 품목을 추가하세요.
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2.5">
|
||||
{cart.map((ln) => {
|
||||
const lineTotal = Math.round(Number(ln.item.UNIT_PRICE) * ln.qty);
|
||||
return (
|
||||
<div key={ln.item.OBJID} className="border border-slate-100 rounded-lg p-2.5">
|
||||
<div className="flex items-start justify-between gap-2 mb-2">
|
||||
<div className="text-sm font-semibold leading-tight">{ln.item.ITEM_NAME}</div>
|
||||
<button onClick={() => removeLine(ln.item.OBJID)} className="text-slate-300 hover:text-rose-500">
|
||||
<X size={14} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-1">
|
||||
<button onClick={() => updateQty(ln.item.OBJID, -1)} className="w-7 h-7 rounded-md bg-slate-100 hover:bg-slate-200 flex items-center justify-center">
|
||||
<Minus size={12} />
|
||||
</button>
|
||||
<span className="w-10 text-center text-sm font-bold tabular-nums">{ln.qty}</span>
|
||||
<button onClick={() => updateQty(ln.item.OBJID, 1)} className="w-7 h-7 rounded-md bg-slate-100 hover:bg-slate-200 flex items-center justify-center">
|
||||
<Plus size={12} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-sm font-bold tabular-nums">₩{fmt(lineTotal)}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-200 mt-4 pt-3 space-y-1.5 text-sm shrink-0">
|
||||
<Row label="면세 합계" value={`₩${fmt(totals.taxFree)}`} color="violet" />
|
||||
<Row label="과세 공급가" value={`₩${fmt(totals.taxable)}`} color="rose" />
|
||||
<Row label="세액" value={`₩${fmt(totals.vat)}`} />
|
||||
<div className="flex items-center justify-between pt-2 border-t border-slate-100">
|
||||
<span className="font-bold">총 합계</span>
|
||||
<span className="font-bold text-lg text-emerald-700 tabular-nums">₩{fmt(totals.total)}</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={submitOrder}
|
||||
disabled={cart.length === 0}
|
||||
className="w-full mt-3 h-11 rounded-xl bg-gradient-to-r from-emerald-700 to-emerald-600 text-white font-bold shadow hover:-translate-y-0.5 transition disabled:from-slate-200 disabled:to-slate-200 disabled:text-slate-400 disabled:hover:translate-y-0 disabled:shadow-none disabled:cursor-not-allowed"
|
||||
>
|
||||
발주 요청
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,8 @@ export async function POST(request: NextRequest) {
|
||||
const fito = await verifyCredentials(userId, password);
|
||||
if (fito.success && fito.user) {
|
||||
await createSession(fito.user);
|
||||
return NextResponse.json({ success: true, user: fito.user, redirectTo: "/dashboard" });
|
||||
// 모모유통 도메인은 모두 모모 대시보드로 (FITO admin 도 /m/dashboard 로 진입)
|
||||
return NextResponse.json({ success: true, user: fito.user, redirectTo: "/m/dashboard" });
|
||||
}
|
||||
|
||||
// FITO 도 실패하면 MOMO를 한 번 더 시도 (이메일 형태가 아니지만 MOMO 계정인 경우)
|
||||
|
||||
+16
-6
@@ -36,8 +36,8 @@ function rowToUser(r: Record<string, unknown>): MomoUser {
|
||||
objid: userId,
|
||||
email,
|
||||
companyName,
|
||||
ceoName: (r.USER_NAME_ENG as string) || "",
|
||||
bizNo: "",
|
||||
ceoName: (r.CEO_NAME as string) || (r.USER_NAME_ENG as string) || "",
|
||||
bizNo: (r.BIZ_NO as string) || "",
|
||||
phone: (r.CELL_PHONE as string) || (r.TEL as string) || "",
|
||||
role,
|
||||
status: (r.STATUS as string) || "active",
|
||||
@@ -52,7 +52,8 @@ export async function findMomoUserByEmail(email: string): Promise<MomoUser | nul
|
||||
`SELECT user_id AS "USER_ID", user_name AS "USER_NAME",
|
||||
user_name_eng AS "USER_NAME_ENG",
|
||||
email AS "EMAIL", cell_phone AS "CELL_PHONE", tel AS "TEL",
|
||||
user_type AS "USER_TYPE", status AS "STATUS"
|
||||
user_type AS "USER_TYPE", status AS "STATUS",
|
||||
biz_no AS "BIZ_NO", ceo_name AS "CEO_NAME"
|
||||
FROM user_info
|
||||
WHERE LOWER(user_id) = LOWER($1) OR LOWER(email) = LOWER($1)
|
||||
LIMIT 1`,
|
||||
@@ -104,9 +105,18 @@ export async function signupMomoUser(input: SignupInput): Promise<{ success: boo
|
||||
|
||||
const enc = encrypt(input.password);
|
||||
await execute(
|
||||
`INSERT INTO user_info (user_id, user_password, user_name, email, cell_phone, user_type, user_type_name, status, regdate)
|
||||
VALUES ($1, $2, $3, $1, $4, 'C', '거래처', 'active', NOW())`,
|
||||
[email, enc, input.companyName.trim(), input.phone?.trim() ?? ""]
|
||||
`INSERT INTO user_info
|
||||
(user_id, user_password, user_name, email, cell_phone,
|
||||
user_type, user_type_name, biz_no, ceo_name, status, regdate)
|
||||
VALUES ($1, $2, $3, $1, $4, 'C', '거래처', $5, $6, 'active', NOW())`,
|
||||
[
|
||||
email,
|
||||
enc,
|
||||
input.companyName.trim(),
|
||||
input.phone?.trim() ?? "",
|
||||
input.bizNo?.trim() ?? "",
|
||||
input.ceoName?.trim() ?? "",
|
||||
]
|
||||
);
|
||||
|
||||
const user = await findMomoUserByEmail(email);
|
||||
|
||||
+13
-1
@@ -18,7 +18,19 @@ export function middleware(request: NextRequest) {
|
||||
"/momo-logo.svg",
|
||||
"/momo-icon.svg",
|
||||
];
|
||||
if (pathname === "/") return NextResponse.next(); // 랜딩 페이지
|
||||
// 랜딩 페이지: 세션이 살아있으면 대시보드로 직행 (이미 로그인된 사용자가 /로 들어와도 마케팅 화면 안 보이게)
|
||||
if (pathname === "/") {
|
||||
if (request.cookies.get("plm-session")) {
|
||||
return NextResponse.redirect(new URL("/m/dashboard", request.url));
|
||||
}
|
||||
return NextResponse.next();
|
||||
}
|
||||
// 로그인/가입 페이지도 세션 있으면 대시보드로
|
||||
if (pathname === "/login" || pathname === "/signup") {
|
||||
if (request.cookies.get("plm-session")) {
|
||||
return NextResponse.redirect(new URL("/m/dashboard", request.url));
|
||||
}
|
||||
}
|
||||
if (publicPaths.some((p) => pathname.startsWith(p))) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user