The weeb for the next gen discord boat - Wamellow wamellow.com
bot discord
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

notifications add multiple platform support

Luna 21192025 2e5a1569

+183 -63
+1 -1
app/dashboard/[guildId]/layout.tsx
··· 180 180 icon: <HiStar /> 181 181 }, 182 182 { 183 - name: "YouTube Notifications", 183 + name: "Notifications", 184 184 value: "/notifications", 185 185 icon: <BiLogoYoutube /> 186 186 },
+33 -57
app/dashboard/[guildId]/notifications/create.component.tsx app/dashboard/[guildId]/notifications/create-youtube.component.tsx
··· 1 1 "use client"; 2 2 3 - import { Button, Chip } from "@nextui-org/react"; 4 3 import Image from "next/image"; 5 4 import Link from "next/link"; 6 5 import { useState } from "react"; 7 - import { HiPencil } from "react-icons/hi"; 8 6 9 7 import { guildStore } from "@/common/guilds"; 10 8 import DumbTextInput from "@/components/inputs/dumb-text-input"; ··· 14 12 import { ApiV1GuildsModulesNotificationsGetResponse } from "@/typings"; 15 13 import { createSelectableItems } from "@/utils/create-selectable-items"; 16 14 17 - const URL_CHANNEL_REGEX = /^https:\/\/(www\.)?youtube\.com\/channel\/UC([a-zA-Z0-9_-]{16,32})$/; 18 - const URL_HANDLE_REGEX = /^https:\/\/(www\.)?youtube\.com\/@([a-zA-Z0-9._-]{3,30})$/; 15 + const URL_CHANNEL_REGEX = /^https:\/\/((www|m)\.)?youtube\.com\/channel\/UC([a-zA-Z0-9_-]{16,32})$/; 16 + const URL_HANDLE_REGEX = /^https:\/\/((www|m)\.)?youtube\.com\/@([a-zA-Z0-9._-]{3,30})$/; 19 17 const CHANNEL_ID = /^UC[a-zA-Z0-9_-]{16,32}$/; 20 18 const CHANNE_HANDLE = /^@?[a-zA-Z0-9._-]{3,30}$/; 21 19 22 - export const Style = { 23 - Compact: 1, 24 - Big: 2 25 - } as const; 20 + function validateAccount(input: string) { 21 + if (URL_CHANNEL_REGEX.exec(input)) return input.split("/channel/")[1]; 22 + if (URL_HANDLE_REGEX.exec(input)) return input.split("/@")[1]; 23 + 24 + if (CHANNEL_ID.exec(input)) return input; 25 + if (CHANNE_HANDLE.exec(input)) return input.replace("@", ""); 26 + 27 + return null; 28 + } 26 29 27 30 interface Props { 28 - style: typeof Style[keyof typeof Style]; 29 31 add: (notification: ApiV1GuildsModulesNotificationsGetResponse) => void; 30 32 set: (id: string) => void; 33 + 34 + isOpen: boolean; 35 + onClose: () => void; 31 36 } 32 37 33 - export default function CreateNotification({ 34 - style, 38 + export function YoutubeNotificationModal({ 35 39 add, 36 - set 40 + set, 41 + 42 + isOpen, 43 + onClose 37 44 }: Props) { 38 45 const guildId = guildStore((g) => g?.id); 39 46 const channels = guildStore((g) => g?.channels); 40 47 41 - const [open, setOpen] = useState(false); 42 48 const [name, setName] = useState(""); 43 49 const [channelId, setChannelId] = useState<string | null>(null); 44 50 45 - function validateAccount(input: string) { 46 - if (URL_CHANNEL_REGEX.exec(input)) return input.split("/channel/")[1]; 47 - if (URL_HANDLE_REGEX.exec(input)) return input.split("/@")[1]; 48 - 49 - if (CHANNEL_ID.exec(input)) return input; 50 - if (CHANNE_HANDLE.exec(input)) return input.replace("@", ""); 51 - 52 - return null; 53 - } 54 - 55 51 return (<> 56 - {style === Style.Compact 57 - ? 58 - <Chip 59 - as={Button} 60 - className="default" 61 - onClick={() => setOpen(true)} 62 - startContent={<HiPencil className="relative left-1 ml-1" />} 63 - > 64 - Add Channel 65 - </Chip> 66 - : 67 - <Button 68 - color="secondary" 69 - onClick={() => setOpen(true)} 70 - startContent={<HiPencil />} 71 - > 72 - Add a YouTube channel 73 - </Button> 74 - } 75 - 76 52 <Modal<ApiV1GuildsModulesNotificationsGetResponse> 77 53 title="Create new notification" 78 - isOpen={open} 79 - onClose={() => setOpen(false)} 54 + isOpen={isOpen} 55 + onClose={onClose} 80 56 onSubmit={() => { 81 57 const validated = validateAccount(name); 82 58 if (!validated && name.startsWith("https://")) return new Error("Invalid channel url"); ··· 123 99 }} 124 100 /> 125 101 126 - <div className="flex items-center gap-2"> 102 + <div> 127 103 <span className="text-lg dark:text-neutral-300 text-neutral-700 font-medium">How to get a channel&apos;s @handle or Id</span> 128 - </div> 129 104 130 - <Link 131 - href="/docs/notifications" 132 - target="_blank" 133 - > 134 - <Image 135 - alt="How to get a Creator's @handle, id or URL" 136 - className="rounded-md" 137 - src={TutorialPic} 138 - /> 139 - </Link> 105 + <Link 106 + href="/docs/notifications" 107 + target="_blank" 108 + > 109 + <Image 110 + alt="How to get a Creator's @handle, id or URL" 111 + className="rounded-md" 112 + src={TutorialPic} 113 + /> 114 + </Link> 115 + </div> 140 116 141 117 </Modal> 142 118 </>);
+1 -1
app/dashboard/[guildId]/notifications/delete.component.tsx
··· 14 14 remove: (id: string) => void; 15 15 } 16 16 17 - export default function DeleteNotification({ 17 + export function DeleteNotification({ 18 18 id, 19 19 name, 20 20
+4 -4
app/dashboard/[guildId]/notifications/page.tsx
··· 17 17 import { ApiV1GuildsModulesNotificationsGetResponse } from "@/typings"; 18 18 import { createSelectableItems } from "@/utils/create-selectable-items"; 19 19 20 - import CreateNotification, { Style } from "./create.component"; 21 - import DeleteNotification from "./delete.component"; 20 + import { DeleteNotification } from "./delete.component"; 21 + import { CreateNotificationSelect, Style } from "./select.component"; 22 22 23 23 export default function Home() { 24 24 const guild = guildStore((g) => g); ··· 65 65 docs="/notifications" 66 66 67 67 createButton={(options) => ( 68 - <CreateNotification 68 + <CreateNotificationSelect 69 69 style={options.style} 70 70 add={addItem} 71 71 set={setItemId} ··· 108 108 name="notifications" 109 109 description="Notify your community when new videos are released." 110 110 > 111 - <CreateNotification 111 + <CreateNotificationSelect 112 112 style={Style.Big} 113 113 add={addItem} 114 114 set={setItemId}
+144
app/dashboard/[guildId]/notifications/select.component.tsx
··· 1 + import { Button } from "@/components/ui/button"; 2 + import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; 3 + import { ApiV1GuildsModulesNotificationsGetResponse } from "@/typings"; 4 + import { BsTwitch, BsYoutube } from "react-icons/bs"; 5 + import { YoutubeNotificationModal } from "./create-youtube.component"; 6 + import React, { useEffect, useState } from "react"; 7 + import { PopoverClose } from "@radix-ui/react-popover"; 8 + import { TwitchNotificationModal } from "./create-twitch.component"; 9 + import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, DrawerTrigger } from "@/components/ui/drawer"; 10 + import { badgeVariants } from "@/components/ui/badge"; 11 + import { cn } from "@/utils/cn"; 12 + 13 + export const Style = { 14 + Compact: 1, 15 + Big: 2 16 + } as const; 17 + 18 + interface Props { 19 + style: typeof Style[keyof typeof Style]; 20 + add: (notification: ApiV1GuildsModulesNotificationsGetResponse) => void; 21 + set: (id: string) => void; 22 + } 23 + 24 + enum Platform { 25 + YouTube = "YouTube", 26 + Twitch = "Twitch" 27 + } 28 + 29 + const platforms = [ 30 + { 31 + icon: <BsYoutube />, 32 + name: Platform.YouTube 33 + }, 34 + ] 35 + 36 + export function CreateNotificationSelect({ 37 + style, 38 + add, 39 + set 40 + }: Props) { 41 + const [isMobile, setIsMobile] = useState(false); 42 + const [platform, setPlatform] = useState<Platform | null>(null); 43 + 44 + useEffect(() => { 45 + const mediaQuery = window.matchMedia("(max-width: 768px)"); // Adjust breakpoint as needed 46 + const handleMediaChange = () => setIsMobile(mediaQuery.matches); 47 + 48 + handleMediaChange(); // Check initial value 49 + mediaQuery.addEventListener("change", handleMediaChange); 50 + 51 + return () => mediaQuery.removeEventListener("change", handleMediaChange); 52 + }, []); 53 + 54 + const Wrapper = isMobile ? DrawerWrapper : PopoverWrapper; 55 + 56 + return (<> 57 + <Wrapper 58 + button={ 59 + <Button 60 + className={style === Style.Compact ? cn(badgeVariants(), "h-6") : ""} 61 + variant="secondary" 62 + > 63 + Create new Notification 64 + </Button> 65 + } 66 + children={(platform) => ( 67 + <Button 68 + className="w-full" 69 + onClick={() => setPlatform(Platform[platform.name])} 70 + > 71 + {platform.icon} 72 + {platform.name} 73 + </Button> 74 + )} 75 + style={style} 76 + /> 77 + 78 + <YoutubeNotificationModal add={add} set={set} isOpen={platform === Platform.YouTube} onClose={() => setPlatform(null)} /> 79 + </>) 80 + } 81 + 82 + function PopoverWrapper({ 83 + children, 84 + button, 85 + style 86 + }: { 87 + children: (platform: typeof platforms[number]) => React.ReactNode; 88 + button: React.ReactNode; 89 + style: typeof Style[keyof typeof Style]; 90 + }) { 91 + return ( 92 + <Popover> 93 + <PopoverTrigger asChild> 94 + {button} 95 + </PopoverTrigger> 96 + <PopoverContent 97 + className="space-y-2 wamellow-modal" 98 + align={style === Style.Compact ? "start" : "center"} 99 + > 100 + <div className="space-y-2 mb-2"> 101 + <h4 className="font-medium leading-none">Platform</h4> 102 + <p className="text-sm text-muted-foreground"> 103 + Select a social to create a notify for. 104 + </p> 105 + </div> 106 + 107 + {platforms.map((platform) => ( 108 + <PopoverClose key={platform.name} asChild> 109 + {children(platform)} 110 + </PopoverClose> 111 + ))} 112 + </PopoverContent> 113 + </Popover> 114 + ); 115 + } 116 + 117 + function DrawerWrapper({ 118 + children, 119 + button 120 + }: { 121 + children: (platform: typeof platforms[number]) => React.ReactNode; 122 + button: React.ReactNode; 123 + style: typeof Style[keyof typeof Style]; 124 + }) { 125 + return ( 126 + <Drawer> 127 + <DrawerTrigger asChild> 128 + {button} 129 + </DrawerTrigger> 130 + <DrawerContent className="space-y-2 wamellow-modal"> 131 + <DrawerHeader> 132 + <DrawerTitle>Platform</DrawerTitle> 133 + <DrawerDescription>Select a platform to create notifications for.</DrawerDescription> 134 + </DrawerHeader> 135 + 136 + {platforms.map((platform) => ( 137 + <DrawerClose key={platform.name} asChild> 138 + {children(platform)} 139 + </DrawerClose> 140 + ))} 141 + </DrawerContent> 142 + </Drawer> 143 + ) 144 + }