Skip to content

Commit

Permalink
Merge pull request #306 from boostcampwm-2024/refactor/rss-form-auto-…
Browse files Browse the repository at this point in the history
…fill

♻️ refactor: RSS 피드 등록 시 플랫폼별 URL 자동 구성 기능 구현
  • Loading branch information
junyeokk authored Dec 29, 2024
2 parents 1bde9a0 + 13c893f commit 0f34a04
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 126 deletions.
21 changes: 12 additions & 9 deletions client/src/components/RssRegistration/FormInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@ export default function FormInput({ id, label, value, placeholder, type = "text"
<Label htmlFor={id} className="text-sm font-medium text-foreground">
{label}
</Label>
<Input
id={id}
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
autoComplete="off"
className="flex-grow w-auto border-input focus:border-primary focus-visible:ring-primary placeholder:text-muted-foreground placeholder:text-sm"
type={type}
/>
<div className="relative flex-grow">
<Input
id={id}
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
autoComplete="off"
className="w-full peer border-0 border-b border-input bg-transparent focus:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 rounded-none placeholder:text-muted-foreground placeholder:text-sm"
type={type}
/>
<div className="absolute bottom-0 left-0 h-[2px] w-full origin-left scale-x-0 bg-primary transition-transform duration-300 ease-in-out peer-focus:scale-x-100" />
</div>
</div>
);
}
22 changes: 22 additions & 0 deletions client/src/components/RssRegistration/PlatformSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs.tsx";

import { PLATFORMS, PlatformType } from "@/constants/rss";

interface PlatformSelectorProps {
platform: PlatformType;
onPlatformChange: (platform: string) => void;
}

export const PlatformSelector = ({ platform, onPlatformChange }: PlatformSelectorProps) => {
return (
<Tabs defaultValue="tistory" value={platform} onValueChange={onPlatformChange}>
<TabsList className="grid w-full grid-cols-4 ">
{Object.entries(PLATFORMS).map(([key, { name }]) => (
<TabsTrigger key={key} value={key}>
{name}
</TabsTrigger>
))}
</TabsList>
</Tabs>
);
};
176 changes: 59 additions & 117 deletions client/src/components/RssRegistration/RssRegistrationModal.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useState } from "react";

import FormInput from "@/components/RssRegistration/FormInput";
import { PlatformSelector } from "@/components/RssRegistration/PlatformSelector.tsx";
import { RssUrlInput } from "@/components/RssRegistration/RssUrlInput";
import Alert from "@/components/common/Alert";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
import { Button } from "@/components/ui/button";
import {
Dialog,
Expand All @@ -12,84 +13,50 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Separator } from "@/components/ui/separator";

import { useRegisterRss } from "@/hooks/queries/useRegisterRss";
import { useRssRegistrationForm } from "@/hooks/common/useRssRegistrationForm.ts";
import { useRegisterRss } from "@/hooks/queries/useRegisterRss.ts";

import { validateRssUrl, validateName, validateEmail, validateBlogger } from "./RssValidation";
import { useRegisterModalStore } from "@/store/useRegisterModalStrore";
import { AlertType } from "@/types/alert";
import { RegisterRss } from "@/types/rss";

const Rss = [
{
name: "Tistory",
url: "https://{blogname}.tistory.com/rss",
},
{
name: "Velog",
url: "https://v2.velog.io/rss/@{username}",
},
{
name: "Medium",
url: "https://medium.com/feed/@{username}",
},
];
import { AlertType } from "@/types/alert.ts";
import { RegisterRss } from "@/types/rss.ts";

export default function RssRegistrationModal({ onClose, rssOpen }: { onClose: () => void; rssOpen: boolean }) {
const [alertOpen, setAlertOpen] = useState<AlertType>({ title: "", content: "", isOpen: false });

const {
rssUrl,
bloggerName,
userName,
email,
setRssUrl,
setBloggerName,
setUserName,
setEmail,
setRssUrlValid,
setBloggerNameValid,
setUserNameValid,
setEmailValid,
resetInputs,
handleInputChange,
isFormValid,
} = useRegisterModalStore();

const onSuccess = () => {
setAlertOpen({
title: "RSS 요청 성공!",
content: "관리자가 검토후 처리 결과를 입력해주신 메일을 통해 전달드릴 예정이에요!",
isOpen: true,
});
};

const onError = () => {
setAlertOpen({
title: "RSS 요청 실패!",
content: "입력한 정보를 확인하거나 다시 시도해주세요. 문제가 계속되면 관리자에게 문의하세요!",
isOpen: true,
});
};

const { mutate } = useRegisterRss(onSuccess, onError);
const { platform, values, handlers, formState } = useRssRegistrationForm();
const { mutate } = useRegisterRss(
() => {
setAlertOpen({
title: "RSS 요청 성공!",
content: "관리자가 검토후 처리 결과를 입력해주신 메일을 통해 전달드릴 예정이에요!",
isOpen: true,
});
},
() => {
setAlertOpen({
title: "RSS 요청 실패!",
content: "입력한 정보를 확인하거나 다시 시도해주세요. 문제가 계속되면 관리자에게 문의하세요!",
isOpen: true,
});
}
);

const handleAlertClose = () => {
setAlertOpen({ title: "", content: "", isOpen: false });
resetInputs();
formState.reset();
onClose();
};

const handleRegister = () => {
const data: RegisterRss = {
rssUrl: rssUrl,
blog: bloggerName,
name: userName,
email: email,
rssUrl: values.rssUrl,
blog: values.bloggerName,
name: values.userName,
email: values.email,
};

mutate(data);
};

return (
<Dialog open={rssOpen} onOpenChange={onClose}>
<DialogContent className="sm:max-w-[425px]">
Expand All @@ -100,50 +67,41 @@ export default function RssRegistrationModal({ onClose, rssOpen }: { onClose: ()
<span>검토 및 등록에는 영업일 기준 3-5일이 소요될 수 있습니다.</span>
</DialogDescription>
</DialogHeader>
<Accordion type="single" collapsible>
<AccordionItem value="item-1">
<AccordionTrigger>RSS 예시</AccordionTrigger>
<AccordionContent>
<InfoCard />
</AccordionContent>
</AccordionItem>
</Accordion>
<div className="space-y-4">
<FormInput
id="rss"
label="RSS URL"
onChange={(value: string) => handleInputChange(value, setRssUrl, setRssUrlValid, validateRssUrl)}
placeholder="https://example.com/rss"
value={rssUrl}
/>

<FormInput
id="blog"
label="블로그명"
onChange={(value: string) => handleInputChange(value, setBloggerName, setBloggerNameValid, validateBlogger)}
placeholder="블로그명을 입력하세요"
value={bloggerName}
/>
<FormInput
id="name"
label="신청자 이름"
onChange={(value: string) => handleInputChange(value, setUserName, setUserNameValid, validateName)}
placeholder="이름을 입력하세요"
value={userName}
/>
<FormInput
id="email"
label="이메일"
onChange={(value: string) => handleInputChange(value, setEmail, setEmailValid, validateEmail)}
placeholder="[email protected]"
value={email}
/>
<div className="space-y-6">
<PlatformSelector platform={platform} onPlatformChange={handlers.handlePlatformChange} />
<RssUrlInput platform={platform} value={values.urlUsername} onChange={handlers.handleUsernameChange} />

<div className="space-y-4">
<FormInput
id="blog"
label="블로그명"
onChange={handlers.handleBloggerName}
placeholder="블로그명을 입력하세요"
value={values.bloggerName}
/>
<FormInput
id="name"
label="신청자 이름"
onChange={handlers.handleUserName}
placeholder="이름을 입력하세요"
value={values.userName}
/>
<FormInput
id="email"
label="이메일"
onChange={handlers.handleEmail}
placeholder="[email protected]"
value={values.email}
/>
</div>
</div>

<DialogFooter>
<Button
type="submit"
onClick={handleRegister}
disabled={!isFormValid()}
disabled={!formState.isValid}
className="bg-primary hover:bg-primary/90 text-white"
>
등록
Expand All @@ -154,19 +112,3 @@ export default function RssRegistrationModal({ onClose, rssOpen }: { onClose: ()
</Dialog>
);
}

function InfoCard() {
return (
<>
{Rss.map((r) => {
return (
<div>
<b>{r.name}</b>
<p>{r.url}</p>
<Separator className="my-2" />
</div>
);
})}
</>
);
}
40 changes: 40 additions & 0 deletions client/src/components/RssRegistration/RssUrlInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Input } from "@/components/ui/input.tsx";
import { Label } from "@/components/ui/label.tsx";

import { PLATFORMS, PlatformType } from "@/constants/rss";

interface RssUrlInputProps {
platform: PlatformType;
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
placeholder?: string;
}

export const RssUrlInput = ({ platform, value, onChange }: RssUrlInputProps) => {
const { prefix, suffix, placeholder } = PLATFORMS[platform];

return (
<div className="space-y-2">
<Label>RSS URL</Label>
<div className="flex items-center bg-background relative group">
<div className="px-3 h-9 flex items-center text-sm text-muted-foreground bg-muted/30 rounded-l-md border-y border-l border-input">
{prefix}
</div>
<div className="relative flex-grow h-9">
<Input
value={value}
onChange={onChange}
className="peer h-full border-0 border-y border-input bg-transparent focus:outline-none focus-visible:ring-0 focus-visible:ring-transparent focus-visible:ring-offset-0 rounded-none"
placeholder={placeholder}
/>
<div className="absolute bottom-0 left-0 h-[2px] w-full origin-left scale-x-0 bg-primary transition-transform duration-300 ease-in-out peer-focus:scale-x-100" />
</div>
{suffix && (
<div className="px-3 h-9 flex items-center text-sm text-muted-foreground bg-muted/30 rounded-r-md border-y border-r border-input">
{suffix}
</div>
)}
</div>
</div>
);
};
36 changes: 36 additions & 0 deletions client/src/constants/rss.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export const PLATFORM_TYPES = ["tistory", "velog", "medium", "personal_blog"] as const;
export type PlatformType = (typeof PLATFORM_TYPES)[number];

interface Platform {
name: string;
prefix: string;
suffix: string;
placeholder: string;
}

export const PLATFORMS: Record<PlatformType, Platform> = {
tistory: {
name: "Tistory",
prefix: "https://",
suffix: ".tistory.com/rss",
placeholder: "서브도메인",
},
velog: {
name: "Velog",
prefix: "https://v2.velog.io/rss/@",
suffix: "",
placeholder: "사용자명",
},
medium: {
name: "Medium",
prefix: "https://medium.com/feed/@",
suffix: "",
placeholder: "사용자명",
},
personal_blog: {
name: "개인 블로그",
prefix: "https://",
suffix: "",
placeholder: "블로그 주소",
},
};
Loading

0 comments on commit 0f34a04

Please sign in to comment.