Skip to content

Commit

Permalink
[feature] button states (#103)
Browse files Browse the repository at this point in the history
* [refactor] Update exit modal functionality and improve data handling

* [fix] implement feedback

* [chore] enhance event name input utility

* [chore] enhance event name input utility

* Add event name feature

* [fix] 스타일 고정 - 왼쪽 벽 간격 16px 고정

* [fix] Remove unnecessary padding and margin for the navigation and content areas

* [fix] build error

* [fix] button-states
  • Loading branch information
karnelll authored Nov 29, 2024
1 parent 297bd9d commit ca12bdd
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 65 deletions.
46 changes: 40 additions & 6 deletions fe/src/app/components/common/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
"use client";

import React from "react";
import React, { useState } from "react";

export interface ButtonProps {
label: string;
onClick: () => void;
type?: "start" | "next" | "submit" | "default";
className?: string;
disabled?: boolean;
isSubmitting?: boolean;
isFormComplete?: boolean;
}

function Button({
Expand All @@ -16,17 +18,49 @@ function Button({
type = "default",
className = "",
disabled = false,
isSubmitting = false,
isFormComplete = true,
}: ButtonProps) {
const [isActive, setIsActive] = useState(false);
const [isClicked, setIsClicked] = useState(false);

const handleClick = () => {
setIsActive(true);
setIsClicked(true);
onClick();
};

const getButtonStyle = () => {
if (disabled) {
return "bg-[#E4E4E4] text-[#8E8E8E] cursor-not-allowed";
if (isSubmitting) {
return "bg-[#000000] text-white cursor-not-allowed";
}

if (!isFormComplete) {
return "bg-[#E4E4E4] cursor-not-allowed text-[#8E8E8E]";
}

if (isClicked && type !== "start") {
return "bg-[#000000] text-white";
}

if (isActive) {
switch (type) {
case "start":
return "bg-[#A6251B] text-white";
case "next":
case "submit":
case "default":
default:
return "bg-[#1D1D1D] text-white";
}
}

switch (type) {
case "start":
return "bg-[#F73A2C] text-white";
case "next":
case "submit":
return "bg-[#1D1D1D] text-white";
case "default":
default:
return "bg-[#1D1D1D] text-white";
}
Expand All @@ -36,9 +70,9 @@ function Button({
<div className="w-full fixed bottom-[20px] left-0 right-0 flex justify-center">
<button
type={type === "submit" ? "submit" : "button"}
onClick={onClick}
onClick={handleClick}
className={`${getButtonStyle()} ${className} w-[328px] h-[60px] py-[17px] rounded-lg justify-center items-center inline-flex`}
disabled={disabled}
disabled={disabled || isSubmitting}
>
<div className="text-center text-lg font-medium font-['Pretendard'] leading-relaxed">
{label}
Expand Down
57 changes: 32 additions & 25 deletions fe/src/app/event-maps/[id]/[nonMemberId]/load-mappin-edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,16 @@ export default function LinkEditPage() {
const [isSaveButtonEnabled, setIsSaveButtonEnabled] = useState(false);
const [showExitModal, setShowExitModal] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [isClicked, setIsClicked] = useState(false);

const router = useRouter();
const { id, nonMemberId } = useParams();

const isValidLink = (link: string) => {
const urlPattern = /^(https?:\/\/[^\s]+)/g;
return urlPattern.test(link.trim());
};

useEffect(() => {
if (!id || !nonMemberId) {
console.error("누락된 라우트 매개변수:", { id, nonMemberId });
Expand All @@ -41,8 +47,6 @@ export default function LinkEditPage() {
);
setUserName(parsedData.name || "");
setUserData(parsedData);
} else {
console.warn("비회원 ID가 일치하지 않습니다.");
}
} else {
setMapLinks(userData.bookmarkUrls.length ? userData.bookmarkUrls : [""]);
Expand All @@ -54,23 +58,22 @@ export default function LinkEditPage() {
}, [id, nonMemberId, setUserData, userData, router]);

useEffect(() => {
const allMapLinksValid = mapLinks.every((link) => link.trim() !== "");
const allStoreLinksValid = storeLinks.every((link) => link.trim() !== "");
const allMapLinksValid = mapLinks.length > 0 && mapLinks.every(isValidLink);
const allStoreLinksValid =
storeLinks.length > 0 && storeLinks.every(isValidLink);

const isComplete = (allMapLinksValid || allStoreLinksValid) && isChecked;
setIsSaveButtonEnabled(isComplete);
}, [mapLinks, storeLinks, isChecked]);

const handleSubmit = async (e?: React.FormEvent) => {
if (e) e.preventDefault();

const filteredMapLinks = mapLinks.filter((link) => link.trim() !== "");
const filteredStoreLinks = storeLinks.filter((link) => link.trim() !== "");

const updatedData = {
nonMemberId: userData.nonMemberId || Number(nonMemberId),
name: userName,
bookmarkUrls: filteredMapLinks,
storeUrls: filteredStoreLinks,
bookmarkUrls: mapLinks,
storeUrls: storeLinks,
};

try {
Expand All @@ -91,6 +94,7 @@ export default function LinkEditPage() {

setUserData(updatedData);
localStorage.setItem("userData", JSON.stringify(updatedData));

router.push(`/event-maps/${id}`);
} catch (error) {
console.error("API 호출 오류:", error);
Expand All @@ -101,7 +105,7 @@ export default function LinkEditPage() {
setShowExitModal(true);
};

const handleExitConfirm = () => {
const handleExit = () => {
localStorage.removeItem("userData");
localStorage.removeItem("formData");
router.push(`/event-maps/${id}`);
Expand All @@ -115,17 +119,21 @@ export default function LinkEditPage() {
return <div />;
}

// Button class 설정
let buttonClass =
"w-fixl h-[60px] py-[17px] rounded-lg text-base font-medium text-white";

if (isSaveButtonEnabled) {
buttonClass += isClicked ? " bg-[#000000]" : " bg-[#1D1D1D]";
} else {
buttonClass += " bg-[#e0e0e0] cursor-not-allowed";
}

return (
<div className="w-[360px] h-screen bg-white mx-auto flex flex-col">
<Navigation onBack={handleBack} />

<div
className="flex-1 px-4 mt-[56px] overflow-y-auto"
style={{
maxHeight: "calc(100vh - 60px)",
paddingBottom: "120px",
}}
>
<div className="flex-1 px-[16px] w-full overflow-y-auto pb-[100px]">
<div className="mt-[72px] mb-[36px]" />
{userName && (
<div className="text-darkGray mt-[16px] text-title-md">
{userName}님의 맵핀 모음이에요
Expand Down Expand Up @@ -165,17 +173,16 @@ export default function LinkEditPage() {
>
<Button
label="저장"
className={`w-[328px] h-[60px] py-[17px] rounded-lg text-lg font-['Pretendard'] font-medium bg-[#F73A2C] text-white ${
isSaveButtonEnabled
? "bg-black"
: "bg-[#e0e0e0] pointer-events-none"
}`}
onClick={handleSubmit}
className={buttonClass}
onClick={() => {
setIsClicked(true);
handleSubmit();
}}
disabled={!isSaveButtonEnabled}
/>
</div>
{showExitModal && (
<ExitModal onCancel={handleCancel} onExit={handleExitConfirm} />
<ExitModal onCancel={handleCancel} onExit={handleExit} />
)}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import { nanoid } from "nanoid";
import Image from "next/image";
import { useRouter, useParams } from "next/navigation";

interface LinkFieldEditProps {
interface LinkFieldProps {
label: string;
placeholder: string;
value: string[];
onChange: (value: string[]) => void;
showTooltip?: boolean;
onInfoClick?: () => void;
}

interface InputField {
Expand All @@ -18,14 +20,15 @@ interface InputField {
error: string;
isValid: boolean;
isTyping: boolean;
canEdit: boolean;
}

export default function LinkFieldEdit({
export default function LinkField({
label,
placeholder,
value,
onChange,
}: LinkFieldEditProps) {
}: LinkFieldProps) {
const [inputFields, setInputFields] = useState<InputField[]>(
value.length > 0
? value.map((val) => ({
Expand All @@ -34,6 +37,7 @@ export default function LinkFieldEdit({
error: "",
isValid: true,
isTyping: false,
canEdit: true,
}))
: [
{
Expand All @@ -42,6 +46,7 @@ export default function LinkFieldEdit({
error: "",
isValid: false,
isTyping: false,
canEdit: true,
},
]
);
Expand All @@ -57,11 +62,6 @@ export default function LinkFieldEdit({
onChange(validLinks);
}, [inputFields, onChange]);

const cleanURL = (url: string): string => {
const match = url.match(/https?:\/\/[^\s]+/);
return match ? match[0].trim() : "";
};

const validateLink = async (fieldId: string, url: string, type: string) => {
const endpoint =
type === "북마크 공유 링크" ? "/pings/bookmark" : "/pings/store";
Expand Down Expand Up @@ -113,36 +113,32 @@ export default function LinkFieldEdit({
const handlePasteFromClipboard = async (fieldId: string) => {
try {
const clipboardText = await navigator.clipboard.readText();
const cleanedValue = cleanURL(clipboardText);
if (cleanedValue) {
if (clipboardText.trim()) {
setInputFields((prevFields) =>
prevFields.map((fieldItem) =>
fieldItem.id === fieldId
? { ...fieldItem, text: cleanedValue, isValid: false }
? { ...fieldItem, text: clipboardText, isValid: false }
: fieldItem
)
);

validateLink(fieldId, cleanedValue, label);
validateLink(fieldId, clipboardText, label);
}
} catch (error) {
console.error("클립보드에서 텍스트를 읽는 데 실패했습니다:", error);
}
};

const handleInputChange = (fieldId: string, inputValue: string) => {
const cleanedValue = cleanURL(inputValue); // URL 정리
setInputFields((prevFields) =>
prevFields.map((fieldItem) =>
fieldItem.id === fieldId
? { ...fieldItem, text: cleanedValue, isValid: false, isTyping: true }
? { ...fieldItem, text: inputValue, isValid: false, isTyping: true }
: fieldItem
)
);

if (cleanedValue) {
validateLink(fieldId, cleanedValue, label);
}
validateLink(fieldId, inputValue, label);
};

const handleFocus = (fieldId: string) => {
Expand Down Expand Up @@ -172,6 +168,7 @@ export default function LinkFieldEdit({
error: "",
isValid: false,
isTyping: false,
canEdit: true,
},
]);
};
Expand Down Expand Up @@ -248,9 +245,7 @@ export default function LinkFieldEdit({
{inputFields.map((item, index) => (
<div
key={item.id}
className={`relative w-full ${
index === inputFields.length - 1 ? "" : "mb-[16px]"
}`}
className={`relative w-full ${index === inputFields.length - 1 ? "" : "mb-[16px]"}`}
>
<div
className={`w-[296px] h-[52px] px-4 py-3.5 pr-[40px] rounded-md inline-flex relative ${getClassNames(
Expand Down
10 changes: 2 additions & 8 deletions fe/src/app/event-maps/[id]/load-mappin/forms/links/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ export default function LinksPage() {
onChange={setStoreLinks}
/>

{/* 체크박스 */}
<div className="flex items-center">
<CheckBox isChecked={isChecked} onChange={handleCheckboxChange} />
<span className="ml-2 text-[#8e8e8e] text-xs font-medium font-['Pretendard'] leading-none">
Expand All @@ -125,20 +124,15 @@ export default function LinksPage() {
</form>
</div>

{/* 하단 버튼 */}
<div
className="w-fix px-[16px] bg-white fixed py-[8px]"
style={{ zIndex: 50 }}
>
<Button
label="저장"
onClick={handleSubmit}
className={`w-[328px] h-[60px] py-[17px] rounded-lg text-lg font-['Pretendard'] font-medium bg-[#F73A2C] text-white ${
isFormComplete && !isSubmitting
? "bg-black"
: "bg-[#d9d9d9] cursor-not-allowed"
}`}
disabled={!isFormComplete || isSubmitting}
isSubmitting={isSubmitting}
isFormComplete={isFormComplete}
/>
</div>

Expand Down
6 changes: 1 addition & 5 deletions fe/src/app/eventcreate-page/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,8 @@ function EventCreatePage() {
label="다음"
type="next"
onClick={handleNextClick}
className={`w-[328px] h-[60px] py-[17px] rounded-lg text-lg font-['Pretendard'] font-medium ${
isFormComplete && !isSubmitting
? "bg-black text-white"
: "bg-[#E4E4E4] text-[#8E8E8E]"
}`}
disabled={!isFormComplete || isSubmitting}
isSubmitting={isSubmitting}
/>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion fe/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function LoadingPage() {
};

return (
<div className="flex flex-col items-center min-h-screen bg-black text-white px-[14.17px]">
<div className="flex flex-col items-center min-h-screen bg-black text-white px-[14.17px] max-w-[400px] mx-auto">
<div className="mt-[32px]" />

<div className="w-full h-[0px] border-t-[1px] border-white mb-[16px]" />
Expand Down

0 comments on commit ca12bdd

Please sign in to comment.