-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #306 from boostcampwm-2024/refactor/rss-form-auto-…
…fill ♻️ refactor: RSS 피드 등록 시 플랫폼별 URL 자동 구성 기능 구현
- Loading branch information
Showing
8 changed files
with
230 additions
and
126 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
client/src/components/RssRegistration/PlatformSelector.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
|
@@ -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]"> | ||
|
@@ -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" | ||
> | ||
등록 | ||
|
@@ -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> | ||
); | ||
})} | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: "블로그 주소", | ||
}, | ||
}; |
Oops, something went wrong.