Skip to content

Commit

Permalink
feat : webview page 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
0xC0FFE2 committed Jan 20, 2025
1 parent 9314745 commit 153e1c0
Show file tree
Hide file tree
Showing 8 changed files with 281 additions and 36 deletions.
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"framer-motion": "^11.18.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.4.0",
"react-router-dom": "^7.1.2",
"react-toastify": "^11.0.3"
},
Expand Down
4 changes: 4 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import LoginPage from "./pages/LoginPage";
import RegisterPage from "./pages/RegisterPage";
import "react-toastify/dist/ReactToastify.css";
import { ToastContainer } from "react-toastify";
import MobileLoginPage from "./pages/mobile/MobileLoginPage";
import MobileRegisterPage from "./pages/mobile/MobileRegisterPage";

const App = () => {
return (
Expand All @@ -18,6 +20,8 @@ const App = () => {
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
<Route path="/app/login" element={<MobileLoginPage />} />
<Route path="/app/register" element={<MobileRegisterPage />} />
<Route path="/" element={<Navigate to="/login" replace />} />
</Routes>
</BrowserRouter>
Expand Down
114 changes: 78 additions & 36 deletions src/components/auth/RegisterForm.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useState, useRef } from "react";
import VirtualKeypad from "./VirtualKeypad";
import { ToastContainer, toast } from "react-toastify"; // Import ToastContainer and toast
import "react-toastify/dist/ReactToastify.css"; // Import styles for toast notifications
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import { Link } from "react-router-dom";

interface RegisterFormProps {
formData: {
Expand All @@ -17,22 +18,46 @@ interface RegisterFormProps {
onInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
onPinChange: (name: string, value: string) => void;
onRegister: () => void;
isMobile?: boolean;
}

const RegisterForm: React.FC<RegisterFormProps> = ({
formData,
onInputChange,
onPinChange,
onRegister,
isMobile = false,
}) => {
const [step, setStep] = useState(1);
const [direction, setDirection] = useState<"next" | "prev">("next");
const containerRef = useRef<HTMLDivElement>(null);
const [isPinInput, setIsPinInput] = useState(false);
const [currentPinField, setCurrentPinField] = useState<"pin" | "confirmPin">(
"pin"
);

const mobileStyles = {
wrapper: "min-h-screen bg-white flex flex-col", // flexbox를 이용해 세로 정렬
container: "flex flex-col p-4 pt-6", // 세로 방향으로 아이템 배치
title: "text-3xl font-bold mb-6",
inputBase:
"w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-blue-500 focus:outline-none",
button: "w-full py-4 rounded-lg font-medium",
pinContainer: "mt-8",
pinBox: "w-12 h-12 border-2 rounded-lg",
};

const desktopStyles = {
container: "relative flex-1 flex items-center justify-center p-6",
title: "text-4xl font-bold mb-8",
inputBase:
"w-full px-0 py-2 border-b border-gray-300 focus:border-blue-500 focus:outline-none",
button: "flex-1 py-3 rounded",
pinContainer: "space-y-4",
pinBox: "w-10 h-10 border-b-2",
};

const styles = isMobile ? mobileStyles : desktopStyles;

const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter") {
if (step < 6) {
Expand Down Expand Up @@ -130,24 +155,28 @@ const RegisterForm: React.FC<RegisterFormProps> = ({
) => {
if (!bypassValidate && !validateForm()) return;

setDirection(dir);
const container = containerRef.current;
if (container) {
container.classList.add("transition-transform", "duration-300");
container.style.transform =
dir === "next" ? "translateX(-50%)" : "translateX(50%)";

setTimeout(() => {
setStep(nextStep);
container.classList.remove("transition-transform");
if (isMobile) {
setStep(nextStep);
} else {
setDirection(dir);
const container = containerRef.current;
if (container) {
container.classList.add("transition-transform", "duration-300");
container.style.transform =
dir === "next" ? "translateX(50%)" : "translateX(-50%)";
dir === "next" ? "translateX(-50%)" : "translateX(50%)";

setTimeout(() => {
setStep(nextStep);
container.classList.remove("transition-transform");
container.style.transform =
dir === "next" ? "translateX(50%)" : "translateX(-50%)";

requestAnimationFrame(() => {
container.classList.add("transition-transform");
container.style.transform = "translateX(0)";
});
}, 300);
requestAnimationFrame(() => {
container.classList.add("transition-transform");
container.style.transform = "translateX(0)";
});
}, 300);
}
}
};

Expand Down Expand Up @@ -302,18 +331,26 @@ const RegisterForm: React.FC<RegisterFormProps> = ({
return (
<div className="space-y-6">
<div className="space-y-2">
<h2 className="text-xl font-medium">{currentStep.title}</h2>
<h2
className={isMobile ? "text-xl font-medium" : "text-xl font-medium"}
>
{currentStep.title}
</h2>
<p className="text-gray-600 whitespace-pre-line">
{currentStep.subtitle}
</p>
</div>
{currentStep.input}
<div className="flex space-x-3 pt-4">
<div
className={`flex ${
isMobile ? "flex-col space-y-3" : "space-x-3"
} pt-4`}
>
{step > 1 && (
<button
type="button"
onClick={handlePrevStep}
className="flex-1 border border-blue-600 text-blue-600 py-3 rounded hover:bg-blue-50 transition-colors"
onClick={() => handleNavigation(step - 1, "prev", true)}
className={`${styles.button} border border-blue-600 text-blue-600 hover:bg-blue-50`}
>
이전
</button>
Expand All @@ -324,7 +361,7 @@ const RegisterForm: React.FC<RegisterFormProps> = ({
if (step === 6) onRegister();
else handleNavigation(step + 1, "next");
}}
className="flex-1 bg-blue-600 text-white py-3 rounded hover:bg-blue-700 transition-colors"
className={`${styles.button} bg-blue-600 text-white hover:bg-blue-700`}
>
{step === 6 ? "회원가입" : "다음"}
</button>
Expand All @@ -334,29 +371,34 @@ const RegisterForm: React.FC<RegisterFormProps> = ({
};

return (
<div
className="relative flex-1 flex items-center justify-center p-6"
onKeyDown={handleKeyDown}
tabIndex={0}
>
<div className="w-[310px]">
<h1 className="text-4xl font-bold mb-8">회원가입</h1>
<div className={styles.container} onKeyDown={handleKeyDown} tabIndex={0}>
<div className={isMobile ? "w-full" : "w-[310px]"}>
<h1 className={styles.title}>회원가입</h1>
<div
ref={containerRef}
className="transform transition-transform duration-300"
className={`transform ${
!isMobile ? "transition-transform duration-300" : ""
}`}
>
{renderStep()}
</div>
{step !== 5 && (
<div className="absolute bottom-8 left-0 right-0 text-center text-sm text-gray-600">
<div
className={`${
isMobile ? "mt-8" : "absolute bottom-10 left-0 right-0"
} text-center text-sm text-gray-600`}
>
이미 계정이 있으신가요?{" "}
<a href="/login" className="text-blue-600 hover:underline">
<Link
to={isMobile ? "/app/login" : "/login"}
className="text-blue-600 hover:underline"
>
로그인
</a>
</Link>
</div>
)}
</div>
<ToastContainer /> {/* Add the ToastContainer for toast notifications */}
<ToastContainer />
</div>
);
};
Expand Down
34 changes: 34 additions & 0 deletions src/components/mobile/BottomNavigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from "react";
import { useLocation } from "react-router-dom";
import { FaHome, FaKey, FaTag, FaUser } from "react-icons/fa";

const BottomNavigation = () => {
const location = useLocation();

const navItems = [
{ id: "home", label: "홈", icon: <FaHome />, path: "/app/home" },
{ id: "auth", label: "인증", icon: <FaKey />, path: "/app/auth" },
{ id: "token", label: "토큰", icon: <FaTag />, path: "/app/myinfo/token" },
{ id: "mypage", label: "마이페이지", icon: <FaUser />, path: "/app/mypage" },
];

return (
<div className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-300">
<div className="flex justify-around items-center h-16">
{navItems.map((item) => (
<button
key={item.id}
className={`flex flex-col items-center justify-center w-full h-full ${
location.pathname.startsWith(item.path) ? "text-blue-600" : "text-gray-600"
}`}
>
<div className="text-xl">{item.icon}</div>
<span className="text-xs mt-1">{item.label}</span>
</button>
))}
</div>
</div>
);
};

export default BottomNavigation;
17 changes: 17 additions & 0 deletions src/components/mobile/MobileContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from "react";
import BottomNavigation from "./BottomNavigation";

interface MobileContainerProps {
children: React.ReactNode;
}

const MobileContainer: React.FC<MobileContainerProps> = ({ children }) => {
return (
<div className="min-h-screen w-full bg-white flex flex-col">
<div className="flex-1 overflow-y-auto">{children}</div>
<BottomNavigation />
</div>
);
};

export default MobileContainer
81 changes: 81 additions & 0 deletions src/pages/mobile/MobileLoginPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React from "react";
import MobileContainer from "../../components/mobile/MobileContainer";
import { Link } from "react-router-dom";

const MobileLoginPage = () => {
const [formData, setFormData] = React.useState({
email: "",
password: "",
rememberMe: false,
});

const handleInputChange = (e: {
target: { name: any; value: any; type: any; checked: any };
}) => {
const { name, value, type, checked } = e.target;
setFormData((prev) => ({
...prev,
[name]: type === "checkbox" ? checked : value,
}));
};

const handleLogin = (type: string) => {
console.log(`Logging in with ${type}`, formData);
};

return (
<MobileContainer>
<div className="p-6 pt-12">
<h1 className="text-3xl font-bold mb-8">로그인</h1>
<form className="space-y-6">
<div className="space-y-4">
<input
type="email"
name="email"
placeholder="Enter Email"
className="w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-blue-500 focus:outline-none"
value={formData.email}
onChange={handleInputChange}
/>
<input
type="password"
name="password"
placeholder="Enter Password"
className="w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-blue-500 focus:outline-none"
value={formData.password}
onChange={handleInputChange}
/>
</div>
<a
href="https://sre.nanu.cc/nanuid/forgetpassword.html"
className="block text-center text-sm text-blue-600"
>
비밀번호를 잊으셨나요?
</a>

<div className="space-y-3 pt-4">
<button
type="button"
onClick={() => handleLogin("app")}
className="w-full bg-blue-600 text-white py-4 rounded-lg font-medium hover:bg-blue-700 transition-colors"
>
서비스 로그인
</button>
</div>
</form>

<div className="mt-8 text-center text-sm text-gray-600">
계정이 없으신가요?{" "}
<Link
to="/app/register"
className="text-blue-600 hover:underline"
>
NANU ID 생성하기
</Link>
</div>
</div>
</MobileContainer>
);
};

export default MobileLoginPage;
Loading

0 comments on commit 153e1c0

Please sign in to comment.