From d6d8be8273cec17a88c691d0d0731d78851f4c03 Mon Sep 17 00:00:00 2001 From: kimjaehyeon0101 <47347352-kimjaehyeon0101@users.noreply.replit.com> Date: Mon, 29 Sep 2025 18:49:09 +0000 Subject: [PATCH] Add login and logout functionality with admin dashboard access Introduce a login modal for user authentication, implement user logout functionality, and display an "Admin Dashboard" button for authenticated users. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 069d4324-6c40-4355-955e-c714a50de1ea Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3df548ff-50ae-432f-9be4-25d34eccc983/069d4324-6c40-4355-955e-c714a50de1ea/jvFIdY3 --- ...α…³α„…ᅡᆫ샷 2025-09-30 α„‹α…©α„Œα…₯ᆫ 3.44.56_1759171505632.png | Bin 0 -> 2599 bytes client/src/components/LoginModal.tsx | 121 ++++++++++++++++++ client/src/pages/Home.tsx | 86 +++++++++++-- 3 files changed, 195 insertions(+), 12 deletions(-) create mode 100644 attached_assets/스크란샷 2025-09-30 α„‹α…©α„Œα…₯ᆫ 3.44.56_1759171505632.png create mode 100644 client/src/components/LoginModal.tsx diff --git a/attached_assets/스크란샷 2025-09-30 α„‹α…©α„Œα…₯ᆫ 3.44.56_1759171505632.png b/attached_assets/스크란샷 2025-09-30 α„‹α…©α„Œα…₯ᆫ 3.44.56_1759171505632.png new file mode 100644 index 0000000000000000000000000000000000000000..8f28c9f13a4534906be5b9f5b4e579cda1dd1db5 GIT binary patch literal 2599 zcmZWrdpy(o8{dw|^^BD=p_xk~yO8{BHqz!YjD+bH8QzIiX@j%3Y8Ob zPRFH0bWU;$Cz=2GO-~56c=~avLn-mY9IF|8Vt))kc6{B}_ z=L5V(B1VLI#~E|O8crc;=eHkRE#O_5b*(Ep6+`PPdw>P!oj3I`&V5Q8KNw$#Fo)(> zEja`~9Jlk+)l5v{C65NuDM}Vfk*85Hml6a+>7N$jm&URm3ih8;dvN>1-v;ckimf1Od z8MrO$I`6cS%^bt!Q{RbqPG`DP0;p7wz8Fh`z=>?o1~CGQO;v0lki@Cupp9Y=6&tze zr|(&VB8l&KoeQ#bAyFt|@8ZX2GPwd453u(EV#QK@Y*%-{o$7%1<8jcw{yYW~E#w5R zQ$PeEUJN-*z!xs$1aSp;ArbMV0x!nvVGIKPr3Cneh;XOU;3OWO2{%Dw(O87JEF2Ce z@cjeu&SdLvbnzDv!2*C_JO&dM7KRSnh34@OV+?UP90qHIF)~7lD^P-PF5oLfaRpjm zMgFTpW(xfH>|lV+H~Rj3{ZFQl{ck6(;M-W@0Ws?tj3F9}`K~Rd z64s-58e7N=IzVP~#Oo3FFgG?Ld@29G#J>%HAiDoRH2#tJL*jpl`vpusiN_H)1kC?E zmv7`Bli!F0%=+9v?BVN@zeL4bHkTz}zMq)6?6XX_6c9+tf_R2_AA`EL}gvME2mQe&FH(ULZv!+_Nw+SLOf6GaL*Vj<6Ipd zJX1)q61nP(^W81?SsrMveX|-e_LO^IY4K^9=we3Ha!12|-gdl=oO{s`NA?kc$q&Jz zY*O|IDXco!?f?d(V;{@p%IN53kwk&q$IVF z$UhHD+a;*kmF6E!>ar_P4K>r&uXs)K@=9xJ+LJY9=ZjUr=K_azdtPO+cQ><)>Uon^ z<28xw0sD#$mJonEUL(lWdYou$vzCWJ({DT;`}-l)%`hSP{-Hq*Z+UaehBK0IkF?MY zi3;}`(rRhl}5@_iUY5iM-J_LniqBgo12?>Uh&Q@qF%c{pH;g_twf_y2FUAq zV7bg_@{`Y@Zwo%eOK4j2FsMDbH*Qa>&*jlqzI;)YcF(fEWFc{7HOp~$R+dW~3A|BU z*C~o{k&LPP08TbHfkd3UcXeLArvq^e$ zEhAO!nW_cp_odVg9%ae{PdHXcr{#}&gvz9nSl$$XEHg{@n4s=(*T3o*1)(VQ?t5RR zXk#C$sXu9TcE&o#r14s}^RGU|&(g^{%FmUtV_2<6<{jlZwNaAR32d5+OML2%_r=v| zf;m5XbgCeizfR<6=+{;qva2U$GV+T1vHRJU09M^{}I8J|E(@+SZY@?R;#5V5?fhXbf+#P9aCG@@D@9 zNS#Y7-37C=pe%89b(k2AJd8OO-`{XruobNtxqu%**WWqQeRM88K_}8Fyf?2Q7iV+r z=jJ%#q4zPbY@a8SOTpj@G`?J~X7FQ2%KenZ>|d{!sR#~6tDiGPUFvjbE$WyERe_T85 z_)FHrT&MTlBfov@G7C91H#~Q%338?LI6|`LWYJ_n70bMuUTGO@i;elX5Z2wz`8-J~ zbJd@va#ld4y6VTZdv`8~@ayXmB%!O$;qr7p9+;{2Z5bp!H!`W`G z2#*U#Ql^9!cz#xmqh!i(=jQDemv*(6IxfragShOp^UKW4G%)r*dx`-2YnL-E__@BW zligkiU^5FEdhzw^`m`r&O8fSyj+9rBFX0Vopyn$*!b+ zcTN{W`P?0#PTBM2!Y3(FLNcUuknwCQDt_-?eBNZpmZ4Gni@X8JG^g0V@Cv8`?sDMp z;OW$?p}KpwBtmZ{Rpv(FB5^~hjZ-llIh(4_R(5I3mN|^+b^}t=KQFX0eKJ61d#aO- zu$7+bvC9)PhL8^%;k@WI$Ut6V#Ln$P4K6Eidd9|Le>B@A6 void; +} + +export default function LoginModal({ isOpen, onClose }: LoginModalProps) { + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const { toast } = useToast(); + + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault(); + setIsLoading(true); + + try { + const response = await fetch("/api/login", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + credentials: "include", + body: JSON.stringify({ username, password }), + }); + + if (response.ok) { + const user = await response.json(); + toast({ + title: "둜그인 성곡", + description: `ν™˜μ˜ν•©λ‹ˆλ‹€, ${user.firstName}λ‹˜!`, + }); + + // Invalidate auth queries to refresh user state + queryClient.invalidateQueries({ queryKey: ["/api/auth/user"] }); + + // Reset form and close modal + setUsername(""); + setPassword(""); + onClose(); + } else { + const error = await response.json(); + toast({ + title: "둜그인 μ‹€νŒ¨", + description: error.message || "잘λͺ»λœ 아이디 λ˜λŠ” λΉ„λ°€λ²ˆν˜Έμž…λ‹ˆλ‹€.", + variant: "destructive", + }); + } + } catch (error) { + toast({ + title: "둜그인 였λ₯˜", + description: "둜그인 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.", + variant: "destructive", + }); + } finally { + setIsLoading(false); + } + }; + + return ( + + + + 둜그인 + +
+
+ + setUsername(e.target.value)} + placeholder="아이디λ₯Ό μž…λ ₯ν•˜μ„Έμš”" + required + data-testid="input-username" + /> +
+
+ + setPassword(e.target.value)} + placeholder="λΉ„λ°€λ²ˆν˜Έλ₯Ό μž…λ ₯ν•˜μ„Έμš”" + required + data-testid="input-password" + /> +
+
+ + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/client/src/pages/Home.tsx b/client/src/pages/Home.tsx index 0023cd9..47e7ea8 100644 --- a/client/src/pages/Home.tsx +++ b/client/src/pages/Home.tsx @@ -1,14 +1,41 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { useAuth } from "@/hooks/useAuth"; -import { Search, Settings } from "lucide-react"; +import { Search, Settings, User, LogOut } from "lucide-react"; +import { useState } from "react"; import MainContent from "@/components/MainContent"; +import LoginModal from "@/components/LoginModal"; +import { useToast } from "@/hooks/use-toast"; +import { queryClient } from "@/lib/queryClient"; export default function Home() { - const { user } = useAuth(); + const { user, isAuthenticated } = useAuth(); + const [isLoginModalOpen, setIsLoginModalOpen] = useState(false); + const { toast } = useToast(); - const handleLogout = () => { - window.location.href = "/api/logout"; + const handleLogout = async () => { + try { + const response = await fetch("/api/logout", { + method: "POST", + credentials: "include", + }); + + if (response.ok) { + toast({ + title: "λ‘œκ·Έμ•„μ›ƒ μ™„λ£Œ", + description: "μ„±κ³΅μ μœΌλ‘œ λ‘œκ·Έμ•„μ›ƒλ˜μ—ˆμŠ΅λ‹ˆλ‹€.", + }); + + // Invalidate auth queries to refresh user state + queryClient.invalidateQueries({ queryKey: ["/api/auth/user"] }); + } + } catch (error) { + toast({ + title: "λ‘œκ·Έμ•„μ›ƒ 였λ₯˜", + description: "λ‘œκ·Έμ•„μ›ƒ 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.", + variant: "destructive", + }); + } }; const handleAdminPage = () => { @@ -41,14 +68,43 @@ export default function Home() { /> - + {isAuthenticated && user ? ( + <> + + +
+ + + {user.firstName} {user.lastName} + +
+ + + + ) : ( + + )}