Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/components/Common/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ type InputProps = {
label?: string;
name?: string;
onChange?: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
onBlur?: (e: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
placeholder?: string;
className?: string;
error?: boolean;
error?: string | boolean;
} & Omit<React.ComponentPropsWithoutRef<"input">, "type"> &
Omit<React.ComponentPropsWithoutRef<"textarea">, "type">;

Expand All @@ -23,6 +24,7 @@ const Input = forwardRef<HTMLInputElement | HTMLTextAreaElement, InputProps>(
name,
value,
onChange,
onBlur = () => {},
placeholder = "",
className = "",
error = false,
Expand Down Expand Up @@ -80,6 +82,7 @@ const Input = forwardRef<HTMLInputElement | HTMLTextAreaElement, InputProps>(
name={name}
value={value}
onChange={onChange}
onBlur={onBlur}
placeholder={placeholder}
{...rest}
/>
Expand All @@ -93,6 +96,9 @@ const Input = forwardRef<HTMLInputElement | HTMLTextAreaElement, InputProps>(
</label>
)}
{inputType}
{typeof error === "string" && error && (
<p className="mb-4 mt-[-20px] text-sm text-color-red-200">{error}</p>
)}
</div>
);
},
Expand Down
6 changes: 3 additions & 3 deletions src/components/MyPlans/Cards/QuotationCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,10 @@ export default function QuotationCard({ quotationDetail, planDetail }: Quotation
</div>
</div>
<div className="flex items-center justify-end gap-4 py-6">
<p className="medium text-2lg mobile-tablet:text-md">견적 금액</p>
<p className="medium text-2lg mobile-tablet:text-md">견적 코코넛</p>
<p className="bold text-2xl mobile-tablet:text-2lg">
{" "}
{quotationDetail.price.toLocaleString()}
{quotationDetail.price.toLocaleString()}
</p>
</div>
</div>
Expand All @@ -151,7 +151,7 @@ export default function QuotationCard({ quotationDetail, planDetail }: Quotation
className="semibold w-full text-nowrap rounded-lg bg-color-blue-300 px-[32.5px] py-4 text-xl text-gray-50 mobile:text-md tablet:text-lg mobile-tablet:px-[16px] mobile-tablet:py-[11px]"
onClick={handleConfirmButton}
>
플랜 확정하기
견적 확정하기
</button>
)}
<button className="semibold w-full text-nowrap rounded-lg border-[1px] border-solid border-color-blue-300 px-[32.5px] py-4 text-xl text-color-blue-300 mobile:text-md tablet:text-lg mobile-tablet:px-[16px] mobile-tablet:py-[11px]">
Expand Down
36 changes: 30 additions & 6 deletions src/features/ProfileEditMaker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export default function ProfileEditorMaker({ makerId }: ProfileEditMakerProps) {
const [description, setDescription] = useState<string>("");
const [detailDescription, setDetailDescription] = useState<string>("");
const [snsAddress, setSnsAddress] = useState<string>("");
const [snsAddressError, setSnsAddressError] = useState<string>("");
const [userId, setUserId] = useState<string>((makerId as string) || ""); // makerId로 초기화

useEffect(() => {
Expand Down Expand Up @@ -79,8 +80,29 @@ export default function ProfileEditorMaker({ makerId }: ProfileEditMakerProps) {
setDetailDescription(e.target.value);
};

const validateSnsUrl = (url: string): boolean => {
if (!url) return true;
const snsUrlPattern =
/^@?(https?:\/\/)?(www\.)?[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+\/[a-zA-Z0-9_.-]+\/?$/;
return snsUrlPattern.test(url);
};

const handleSnsAddressChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setSnsAddress(e.target.value);
const value = e.target.value;
setSnsAddress(value);
setSnsAddressError(""); // 입력 중에는 에러 메시지 제거
};

const handleSnsAddressBlur = () => {
if (!snsAddress) {
setSnsAddressError("SNS 주소를 입력해주세요.");
} else if (!validateSnsUrl(snsAddress)) {
setSnsAddressError(
"올바른 SNS 주소를 입력해주세요. (예: https://www.instagram.com/username)",
);
} else {
setSnsAddressError("");
}
};

const handleSubmit = async () => {
Expand Down Expand Up @@ -142,15 +164,17 @@ export default function ProfileEditorMaker({ makerId }: ProfileEditMakerProps) {
<div>
<Input
label="SNS 주소"
className="mb-8 border-none border-color-line-100 bg-color-background-200"
className="mb-8 overflow-hidden text-ellipsis border-none border-color-line-100 bg-color-background-200"
type="text"
placeholder="SNS 주소를 입력해주세요."
placeholder="예시:www.instagram.com/username"
value={snsAddress}
onChange={handleSnsAddressChange}
onBlur={handleSnsAddressBlur}
error={snsAddressError}
/>
<Input
className="border-none bg-color-background-200"
label="한 줄 소개*"
className="overflow-hidden text-ellipsis border-none bg-color-background-200"
label="한 줄 소개"
type="text"
placeholder="한 줄 소개를 입력해주세요."
value={description}
Expand All @@ -169,7 +193,7 @@ export default function ProfileEditorMaker({ makerId }: ProfileEditMakerProps) {
onChange={handleDetailDescriptionChange}
/>
<div>
<p className="semibold mb-3 text-xl mobile-tablet:text-lg">제공 서비스*</p>
<p className="semibold mb-3 text-xl mobile-tablet:text-lg">제공 서비스</p>
<p className="mb-4 text-lg text-color-gray-400 mobile-tablet:text-xs">
*제공 서비스는 중복 선택 가능하며, 언제든 수정 가능해요!
</p>
Expand Down
35 changes: 29 additions & 6 deletions src/features/ProfileMaker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default function ProfileMaker() {
const [description, setDescription] = useState<string>("");
const [detailDescription, setDetailDescription] = useState<string>("");
const [snsAddress, setSnsAddress] = useState<string>("");
const [snsAddressError, setSnsAddressError] = useState<string>("");
const [isSubmitting, setIsSubmitting] = useState(false);

const handleImageSelect = (imageKey: string) => {
Expand Down Expand Up @@ -49,8 +50,27 @@ export default function ProfileMaker() {
setDetailDescription(e.target.value);
};

const validateSnsUrl = (url: string): boolean => {
if (!url) return true;
const snsUrlPattern =
/^@?(https?:\/\/)?(www\.)?[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+\/[a-zA-Z0-9_.-]+\/?$/;
return snsUrlPattern.test(url);
};

const handleSnsAddressChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setSnsAddress(e.target.value);
const value = e.target.value;
setSnsAddress(value);
setSnsAddressError("");
};

const handleSnsAddressBlur = () => {
if (!snsAddress) {
setSnsAddressError("SNS 주소를 입력해주세요.");
} else if (!validateSnsUrl(snsAddress)) {
setSnsAddressError("올바른 SNS 주소를 입력해주세요. (예: www.instagram.com/username)");
} else {
setSnsAddressError("");
}
};

const handleSubmit = async () => {
Expand Down Expand Up @@ -92,6 +112,7 @@ export default function ProfileMaker() {
!description ||
!detailDescription ||
!snsAddress ||
!!snsAddressError ||
isSubmitting;

return (
Expand Down Expand Up @@ -129,15 +150,17 @@ export default function ProfileMaker() {
<div>
<Input
label="SNS 주소"
className="mb-8 border-none border-color-line-100 bg-color-background-200"
className="mb-8 overflow-hidden text-ellipsis border-none border-color-line-100 bg-color-background-200"
type="text"
placeholder="SNS 주소를 입력해주세요."
placeholder="예시:www.instagram.com/username"
value={snsAddress}
onChange={handleSnsAddressChange}
onBlur={handleSnsAddressBlur}
error={snsAddressError}
/>
<Input
className="border-none bg-color-background-200"
label="한 줄 소개*"
className="overflow-hidden text-ellipsis border-none bg-color-background-200"
label="한 줄 소개"
type="text"
placeholder="한 줄 소개를 입력해주세요."
value={description}
Expand All @@ -156,7 +179,7 @@ export default function ProfileMaker() {
onChange={handleDetailDescriptionChange}
/>
<div>
<p className="semibold mb-3 text-xl mobile-tablet:text-lg">제공 서비스*</p>
<p className="semibold mb-3 text-xl mobile-tablet:text-lg">제공 서비스</p>
<p className="mb-4 text-lg text-color-gray-400 mobile-tablet:text-xs">
*제공 서비스는 중복 선택 가능하며, 언제든 수정 가능해요!
</p>
Expand Down
36 changes: 23 additions & 13 deletions src/pages/managequo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useEffect } from "react";
import withAuthAccess from "@/stores/withAuthAccess";
import Image from "next/image";
import loading from "@public/assets/icon_loading.gif";
import request_empty from "@public/assets/icon_luggage_frown.svg";

export function ManageQuo() {
const { ref, inView } = useInView();
Expand Down Expand Up @@ -42,27 +43,36 @@ export function ManageQuo() {
<div className="mx-[auto] w-full mobile:mx-[auto] tablet:mx-[auto]">
<div className="flex items-center gap-8 border-b border-color-line-200">
<Link href="/managequo">
<p className="text-4 cursor-pointer border-b-[3px] border-black py-6 semibold">
<p className="text-4 semibold cursor-pointer border-b-[3px] border-black py-6">
보낸 견적 조회
</p>
</Link>
<Link href="/reject-list">
<p className="text-4 cursor-pointer semibold">반려된 견적</p>
<p className="text-4 semibold cursor-pointer">반려된 견적</p>
</Link>
</div>
</div>
<div className="mobiel-tablet:felx pt-10 pc:grid pc:grid-cols-2 pc:gap-2 mobile-tablet:grid-cols-none mobile-tablet:flex-col mobile-tablet:items-center mobile-tablet:justify-center">
{allItems.map((item) => (
<SendQuotation key={item.id} data={item} />
))}
</div>
<div ref={ref} className="h-10">
{isFetchingNextPage && (
<div className="flex items-center justify-center py-4">
<span>더 불러오는 중...</span>
{allItems.length === 0 ? (
<div className="flex flex-col items-center justify-center pt-10">
<Image src={request_empty} alt="request_empty" width={300} height={300} />
<p className="semibold text-xl text-color-gray-300">아직 보낸 견적이 없어요!</p>
</div>
) : (
<>
<div className="mobiel-tablet:felx pt-10 pc:grid pc:grid-cols-2 pc:gap-2 mobile-tablet:grid-cols-none mobile-tablet:flex-col mobile-tablet:items-center mobile-tablet:justify-center">
{allItems.map((item) => (
<SendQuotation key={item.id} data={item} />
))}
</div>
)}
</div>
<div ref={ref} className="h-10">
{isFetchingNextPage && (
<div className="flex items-center justify-center py-4">
<span>더 불러오는 중...</span>
</div>
)}
</div>
</>
)}
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,13 @@ export function QuotationDetailDreamer() {
}, [planId, quotationId]);

async function handleConfirmButton() {

try {
if (quotationDetail)
await QuotationServiceDreamer.confirmQuotation({ isConfirmed: true }, quotationDetail.id);
alert("플랜이 확정되었습니다.");
alert("견적확정 확정되었습니다.");
} catch (error) {
alert(`플랜 확정에 실패했습니다. 다시 시도해주세요. ${error}`);
alert(`견적확정에 실패했습니다. 다시 시도해주세요. ${error}`);
}
}

Expand Down Expand Up @@ -238,7 +239,7 @@ export function QuotationDetailDreamer() {
/>
<p>{quotationDetail.maker.averageRating}</p>
<p className="text-color-gray-400">
(${quotationDetail.maker.totalReviews})
({quotationDetail.maker.totalReviews})
</p>
</div>
<p className="mx-4 text-color-line-200 mobile-tablet:mx-1">ㅣ</p>
Expand Down Expand Up @@ -281,9 +282,11 @@ export function QuotationDetailDreamer() {
</div>
<hr className="border-Line-100 my-10 mobile-tablet:my-6" />
<div className="flex flex-col gap-8 mobile-tablet:gap-4">
<p className="semibold text-2xl text-color-black-400 mobile-tablet:text-lg">견적가</p>
<p className="semibold text-2xl text-color-black-400 mobile-tablet:text-lg">
받은 견적 코코넛
</p>
<p className="bold text-3xl text-color-black-400 mobile-tablet:text-xl">
{quotationDetail.price}
{quotationDetail.price}
</p>
</div>
<hr className="border-Line-100 my-10 mobile-tablet:my-6" />
Expand Down
2 changes: 2 additions & 0 deletions src/pages/receive/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import Link from "next/link";
import withAuthAccess from "@/stores/withAuthAccess";
import { PlanItem } from "@/services/requestService";
import loading from "@public/assets/icon_loading.gif";


export function Receive() {
const [filterIsOpen, setFilterIsOpen] = useState<boolean>(false);
const [quotationIsOpen, setQuotationIsOpen] = useState<boolean>(false);
Expand Down
34 changes: 22 additions & 12 deletions src/pages/reject-list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useEffect } from "react";
import withAuthAccess from "@/stores/withAuthAccess";
import Image from "next/image";
import loading from "@public/assets/icon_loading.gif";
import request_empty from "@public/assets/icon_luggage_frown.svg";

export function RejectList() {
const { ref, inView } = useInView();
Expand Down Expand Up @@ -51,20 +52,29 @@ export function RejectList() {
</Link>
</div>
</div>
<div className="mobiel-tablet:felx pt-10 pc:grid pc:grid-cols-2 pc:gap-2 mobile-tablet:grid-cols-none mobile-tablet:flex-col mobile-tablet:items-center mobile-tablet:justify-center">
{allItems.map((item) => (
<SendQuotation key={item.id} data={item} />
))}
</div>
<div ref={ref} className="h-10">
{isFetchingNextPage && (
<div className="flex items-center justify-center py-4">
<span>더 불러오는 중...</span>
{allItems.length === 0 ? (
<div className="flex flex-col items-center justify-center pt-10">
<Image src={request_empty} alt="request_empty" width={300} height={300} />
<p className="semibold text-xl text-color-gray-300">아직 반려된 견적이 없어요!</p>
</div>
) : (
<>
<div className="mobiel-tablet:felx pt-10 pc:grid pc:grid-cols-2 pc:gap-2 mobile-tablet:grid-cols-none mobile-tablet:flex-col mobile-tablet:items-center mobile-tablet:justify-center">
{allItems.map((item) => (
<SendQuotation key={item.id} data={item} />
))}
</div>
)}
</div>
<div ref={ref} className="h-10">
{isFetchingNextPage && (
<div className="flex items-center justify-center py-4">
<span>더 불러오는 중...</span>
</div>
)}
</div>
</>
)}
</>
);
}

export default withAuthAccess(RejectList,"MAKER");
export default withAuthAccess(RejectList, "MAKER");