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.

new tab list & copy to clipboard

Luna 1b288a59 f2317a34

+103 -92
+1 -1
app/(home)/privacy/page.tsx
··· 2 2 import React from "react"; 3 3 4 4 import BeautifyMarkdown from "@/components/BeautifyMarkdown"; 5 - import { CopyToClipboardButton } from "@/components/copyToClipboard"; 5 + import { CopyToClipboardButton } from "@/components/copy-to-clipboard"; 6 6 import { getBaseUrl, getCanonicalUrl } from "@/utils/urls"; 7 7 8 8 export const generateMetadata = async (): Promise<Metadata> => {
+1 -1
app/(home)/terms/page.tsx
··· 2 2 import React from "react"; 3 3 4 4 import BeautifyMarkdown from "@/components/BeautifyMarkdown"; 5 - import { CopyToClipboardButton } from "@/components/copyToClipboard"; 5 + import { CopyToClipboardButton } from "@/components/copy-to-clipboard"; 6 6 import { getBaseUrl, getCanonicalUrl } from "@/utils/urls"; 7 7 8 8 export const generateMetadata = async (): Promise<Metadata> => {
+1 -1
app/dashboard/[guildId]/greeting/passport/page.tsx
··· 6 6 import { HiArrowNarrowLeft, HiFingerPrint } from "react-icons/hi"; 7 7 8 8 import { guildStore } from "@/common/guilds"; 9 - import { CopyToClipboardButton } from "@/components/copyToClipboard"; 9 + import { CopyToClipboardButton } from "@/components/copy-to-clipboard"; 10 10 import ErrorBanner from "@/components/Error"; 11 11 import SelectInput from "@/components/inputs/SelectMenu"; 12 12 import Switch from "@/components/inputs/Switch";
+1 -1
app/dashboard/[guildId]/layout.tsx
··· 8 8 9 9 import { guildStore } from "@/common/guilds"; 10 10 import { webStore } from "@/common/webstore"; 11 - import { CopyToClipboardButton } from "@/components/copyToClipboard"; 11 + import { CopyToClipboardButton } from "@/components/copy-to-clipboard"; 12 12 import ImageReduceMotion from "@/components/image-reduce-motion"; 13 13 import { ListTab } from "@/components/list"; 14 14 import { ScreenMessage } from "@/components/screen-message";
+37 -39
app/dashboard/page.tsx
··· 158 158 return false; 159 159 }) 160 160 .map((guild) => ( 161 - <> 162 - <motion.li 163 - key={"guildGrid" + guild.id} 164 - variants={{ 165 - hidden: { y: 20, opacity: 0 }, 166 - visible: { 167 - y: 0, 168 - opacity: 1, 169 - transition: { 170 - type: "spring", 171 - bounce: guilds.length > 20 ? 0.2 : 0.4, 172 - duration: guilds.length > 20 ? 0.35 : 0.7 173 - } 161 + <motion.li 162 + key={"guildGrid" + guild.id} 163 + variants={{ 164 + hidden: { y: 20, opacity: 0 }, 165 + visible: { 166 + y: 0, 167 + opacity: 1, 168 + transition: { 169 + type: "spring", 170 + bounce: guilds.length > 20 ? 0.2 : 0.4, 171 + duration: guilds.length > 20 ? 0.35 : 0.7 174 172 } 175 - }} 176 - className="dark:bg-wamellow bg-wamellow-100 p-4 flex items-center rounded-lg drop-shadow-md overflow-hidden relative h-24 duration-100 outline-violet-500 hover:outline group/card" 177 - > 178 - <ImageReduceMotion url={`https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}`} size={24} alt="" forceStatic={true} className="absolute top-[-48px] left-0 w-full z-0 blur-xl opacity-30" /> 173 + } 174 + }} 175 + className="dark:bg-wamellow bg-wamellow-100 p-4 flex items-center rounded-lg drop-shadow-md overflow-hidden relative h-24 duration-100 outline-violet-500 hover:outline group/card" 176 + > 177 + <ImageReduceMotion url={`https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}`} size={24} alt="" forceStatic={true} className="absolute top-[-48px] left-0 w-full z-0 blur-xl opacity-30" /> 179 178 180 - <ImageReduceMotion url={`https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}`} size={56} alt={`Server icon of @${guild.name}`} className="rounded-lg h-14 w-14 z-1 relative drop-shadow-md" /> 181 - <div className="ml-3 text-sm relative bottom-1"> 182 - <div className="text-lg dark:text-neutral-200 font-medium text-neutral-800 mb-1">{truncate(guild.name, 20)}</div> 183 - <span className="flex gap-2"> 184 - <Button 185 - as={Link} 186 - href={`/dashboard/${guild.id}${searchParams.get("to") ? `/${searchParams.get("to")}` : ""}`} 187 - className="default dark:bg-neutral-500/40 hover:dark:bg-neutral-500/20 bg-neutral-400/40 hover:bg-neutral-400/20 text-sm h-9" 188 - > 189 - Manage 190 - </Button> 191 - <Button 192 - as={Link} 193 - href={`/leaderboard/${guild.id}`} 194 - className="default dark:bg-neutral-500/40 hover:dark:bg-neutral-500/20 bg-neutral-400/40 hover:bg-neutral-400/20 text-sm h-9 opacity-0 group-hover/card:opacity-100" 195 - > 196 - Leaderboard 197 - </Button> 198 - </span> 199 - </div> 179 + <ImageReduceMotion url={`https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}`} size={56} alt={`Server icon of @${guild.name}`} className="rounded-lg h-14 w-14 z-1 relative drop-shadow-md" /> 180 + <div className="ml-3 text-sm relative bottom-1"> 181 + <div className="text-lg dark:text-neutral-200 font-medium text-neutral-800 mb-1">{truncate(guild.name, 20)}</div> 182 + <span className="flex gap-2"> 183 + <Button 184 + as={Link} 185 + href={`/dashboard/${guild.id}${searchParams.get("to") ? `/${searchParams.get("to")}` : ""}`} 186 + className="default dark:bg-neutral-500/40 hover:dark:bg-neutral-500/20 bg-neutral-400/40 hover:bg-neutral-400/20 text-sm h-9" 187 + > 188 + Manage 189 + </Button> 190 + <Button 191 + as={Link} 192 + href={`/leaderboard/${guild.id}`} 193 + className="default dark:bg-neutral-500/40 hover:dark:bg-neutral-500/20 bg-neutral-400/40 hover:bg-neutral-400/20 text-sm h-9 opacity-0 group-hover/card:opacity-100" 194 + > 195 + Leaderboard 196 + </Button> 197 + </span> 198 + </div> 200 199 201 - </motion.li> 202 - </> 200 + </motion.li> 203 201 ))} 204 202 205 203 <motion.a
+4 -5
app/leaderboard/[guildId]/side.component.tsx
··· 8 8 9 9 import { webStore } from "@/common/webstore"; 10 10 import Ad from "@/components/ad"; 11 - import { CopyToClipboardButton } from "@/components/copyToClipboard"; 11 + import { CopyToClipboardButton } from "@/components/copy-to-clipboard"; 12 12 import ErrorBanner from "@/components/Error"; 13 13 import Modal from "@/components/modal"; 14 14 import { ApiV1GuildsModulesLeaderboardGetResponse, ApiV1GuildsTopmembersPaginationGetResponse } from "@/typings"; 15 - import cn from "@/utils/cn"; 16 15 import { getCanonicalUrl } from "@/utils/urls"; 17 16 18 17 export default function Side({ ··· 36 35 <div className="flex flex-col gap-3"> 37 36 38 37 <CopyToClipboardButton 39 - className={cn("w-full !justify-start", design?.backgroundColor && "dark:bg-wamellow/60 bg-wamellow-100/60 dark:hover:bg-wamellow-light/70 hover:bg-wamellow-100-light/70")} 38 + className="w-full !justify-start" 40 39 text={getCanonicalUrl("leaderboard", guildId)} 41 40 icon={<HiShare />} 42 41 /> ··· 54 53 key="1" 55 54 aria-label="admin tools" 56 55 title="Admin tools" 57 - classNames={{ content: "flex flex-col gap-2 mb-2" }} 56 + classNames={{ content: "mb-2" }} 58 57 > 59 58 <Button 60 59 className="w-full !justify-start" ··· 65 64 </Button> 66 65 <Button 67 66 as={Link} 68 - className="w-full !justify-start" 67 + className="w-full !justify-start mt-2" 69 68 href={getCanonicalUrl("dashboard", guildId)} 70 69 startContent={<HiViewGridAdd />} 71 70 >
+43 -33
components/List.tsx
··· 1 1 "use client"; 2 2 3 + import { Tab, Tabs } from "@nextui-org/react"; 3 4 import { usePathname, useRouter, useSearchParams } from "next/navigation"; 4 5 import React from "react"; 5 6 6 - import cn from "@/utils/cn"; 7 7 import decimalToRgb from "@/utils/decimalToRgb"; 8 8 9 9 interface ListProps { ··· 20 20 } 21 21 22 22 export function ListTab({ tabs, url, searchParamName, disabled, children }: ListProps) { 23 - const path = usePathname().split(`${url}/`)[1]; 23 + const path = usePathname(); 24 24 const params = useSearchParams(); 25 25 const router = useRouter(); 26 26 27 - return ( 28 - <div className="text-sm font-medium text-center border-b dark:border-wamellow-light border-wamellow-100-light mt-2 mb-6 overflow-x-scroll scrollbar-none"> 29 - <ul className="flex"> 30 - {tabs.map((tab, i) => { 31 - let isCurrent = false; 32 - if (searchParamName) isCurrent = tab.value ? params.get(searchParamName) === tab.value : !tab.value && !params.get(searchParamName); 33 - isCurrent ||= (!path && tab.value === "/") || path?.startsWith(tab.value !== "/" ? tab.value.slice(1) : tab.value); 27 + function handleChange(key: unknown) { 28 + if (!key && typeof key !== "string") return; 34 29 35 - return ( 36 - <li className="mr-2" key={"tablist-" + url + tab.name + i}> 37 - <button 38 - className={cn( 39 - "inline-block p-3 pb-2 border-b-2 border-transparent rounded-t-lg font-medium hover:text-violet-400 duration-200", 40 - isCurrent && "text-violet-500 border-b-2 border-violet-500", 41 - disabled && "cursor-not-allowed opacity-75" 42 - )} 43 - onClick={() => { 44 - if (disabled) return; 45 - if (!searchParamName) return router.push(`${url}${tab.value}`); 30 + if (!searchParamName) { 31 + router.push(`${url}${key}`); 32 + return; 33 + } 46 34 47 - const newparams = new URLSearchParams(); 35 + const newparams = new URLSearchParams(); 48 36 49 - if (tab.value) newparams.append(searchParamName, tab.value); 50 - else newparams.delete(searchParamName); 37 + if (key) newparams.append(searchParamName, key as string); 38 + else newparams.delete(searchParamName); 51 39 52 - router.push(`${url}?${newparams.toString()}`); 53 - }} 54 - > 55 - <span dangerouslySetInnerHTML={{ __html: tab.name.replace(/ +/g, "&nbsp;") }} /> 56 - </button> 57 - </li> 58 - ); 40 + router.push(`${url}?${newparams.toString()}`); 41 + } 59 42 60 - })} 43 + return ( 44 + <div className="font-medium mt-2 mb-6"> 45 + <Tabs 46 + className="flex" 47 + classNames={{ 48 + tabList: "w-full relative rounded-none p-0 border-b border-divider", 49 + tab: "w-fit px-4 h-12 relative right-2.5" 50 + }} 51 + defaultSelectedKey={searchParamName 52 + ? (params.get(searchParamName) || "") 53 + : path.split(url)[1].split("/").slice(0, 2).join("/") 54 + } 55 + onSelectionChange={handleChange} 56 + variant="underlined" 57 + color="secondary" 58 + isDisabled={disabled} 59 + > 60 + {tabs.map((tab) => 61 + <Tab 62 + key={tab.value} 63 + title={ 64 + <div className="flex items-center space-x-2"> 65 + {tab.icon} 66 + <span>{tab.name}</span> 67 + </div> 68 + } 69 + /> 70 + )} 61 71 {children && <li className="ml-auto">{children}</li>} 62 - </ul> 63 - </div > 72 + </Tabs> 73 + </div> 64 74 ); 65 75 66 76 }
+15 -11
components/copyToClipboard.tsx components/copy-to-clipboard.tsx
··· 1 1 "use client"; 2 + 2 3 import { Button, ButtonGroup, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger } from "@nextui-org/react"; 3 - import { FunctionComponent, useRef, useState } from "react"; 4 + import { useRef, useState } from "react"; 4 5 import { HiChevronDown } from "react-icons/hi"; 5 - 6 - import cn from "@/utils/cn"; 7 6 8 7 export async function copyToClipboard(text: string, needsWait?: boolean) { 9 8 if (needsWait) await new Promise((r) => setTimeout(r, 600)); ··· 23 22 items?: { icon?: React.ReactNode, name: string, description?: string, text: string }[]; 24 23 } 25 24 26 - export const CopyToClipboardButton: FunctionComponent<Props> = ({ icon, text, title, className = "", items }) => { 25 + export function CopyToClipboardButton({ icon, text, title, className = "", items }: Props) { 27 26 const timeoutRef = useRef<NodeJS.Timeout | null>(null); 28 27 29 28 const [saved, setSaved] = useState<boolean>(false); ··· 40 39 <ButtonGroup> 41 40 42 41 <Button 43 - className={cn(saved ? "violet" : className + " ")} 42 + className={className} 43 + color={saved ? "secondary" : undefined} 44 44 onClick={() => handleCopy(text)} 45 45 startContent={icon} 46 46 > 47 - {saved ? "Copied to clipboard!" : (title || "Copy to clipboard")} 47 + {saved ? "Copied to clipboard" : (title || "Copy to clipboard")} 48 48 </Button> 49 49 50 50 {items && 51 51 <Dropdown placement="bottom-end"> 52 52 <DropdownTrigger> 53 - <Button className={saved ? "violet" : cn(className, "dark:hover:bg-wamellow-light hover:bg-wamellow-100-light")} isIconOnly><HiChevronDown /></Button> 53 + <Button 54 + color={saved ? "secondary" : undefined} 55 + className={className} 56 + isIconOnly 57 + > 58 + <HiChevronDown /> 59 + </Button> 54 60 </DropdownTrigger> 55 61 <DropdownMenu variant="faded"> 56 - 57 62 {items.map((item, i) => ( 58 63 <DropdownItem 59 64 onClick={() => handleCopy(item.text)} 60 65 showDivider={i !== (items?.length || 0) - 1} 61 - // closeOnSelect 66 + closeOnSelect 62 67 key={i} 63 68 description={item.description} 64 69 startContent={item.icon} ··· 66 71 {item.name} 67 72 </DropdownItem> 68 73 ))} 69 - 70 74 </DropdownMenu> 71 75 </Dropdown> 72 76 } 73 77 74 78 </ButtonGroup> 75 79 ); 76 - }; 80 + }