diff --git a/apps/dashboard/.env.example b/apps/dashboard/.env.example index d0bd739a0f6..27bf2a4453f 100644 --- a/apps/dashboard/.env.example +++ b/apps/dashboard/.env.example @@ -94,4 +94,7 @@ NEXT_PUBLIC_TURNSTILE_SITE_KEY="" TURNSTILE_SECRET_KEY="" REDIS_URL="" -ANALYTICS_SERVICE_URL="" \ No newline at end of file +ANALYTICS_SERVICE_URL="" + +# Required for Nebula Chat +NEXT_PUBLIC_NEBULA_URL="" \ No newline at end of file diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index c2377dc61f8..71b75059aee 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -60,6 +60,7 @@ "color": "^4.2.3", "compare-versions": "^6.1.0", "date-fns": "4.1.0", + "fetch-event-stream": "0.1.5", "flat": "^6.0.1", "framer-motion": "11.13.3", "fuse.js": "7.0.0", diff --git a/apps/dashboard/src/@/api/team.ts b/apps/dashboard/src/@/api/team.ts index a7ac5fa0ed5..6569df0fa2c 100644 --- a/apps/dashboard/src/@/api/team.ts +++ b/apps/dashboard/src/@/api/team.ts @@ -2,6 +2,17 @@ import "server-only"; import { API_SERVER_URL } from "@/constants/env"; import { getAuthToken } from "../../app/api/lib/getAuthToken"; +type EnabledTeamScope = + | "pay" + | "storage" + | "rpc" + | "bundler" + | "insight" + | "embeddedWallets" + | "relayer" + | "chainsaw" + | "nebula"; + export type Team = { id: string; name: string; @@ -15,6 +26,7 @@ export type Team = { billingStatus: "validPayment" | (string & {}) | null; billingEmail: string | null; growthTrialEligible: boolean | null; + enabledScopes: EnabledTeamScope[]; }; export async function getTeamBySlug(slug: string) { diff --git a/apps/dashboard/src/@/components/blocks/DangerSettingCard.stories.tsx b/apps/dashboard/src/@/components/blocks/DangerSettingCard.stories.tsx index e7124337ae7..5db6a5eb906 100644 --- a/apps/dashboard/src/@/components/blocks/DangerSettingCard.stories.tsx +++ b/apps/dashboard/src/@/components/blocks/DangerSettingCard.stories.tsx @@ -28,36 +28,34 @@ export const Mobile: Story = { function Story() { return ( -
-
- - {}} - isPending={false} - confirmationDialog={{ - title: "This is confirmation title", - description: "This is confirmation description", - }} - /> - +
+ + {}} + isPending={false} + confirmationDialog={{ + title: "This is confirmation title", + description: "This is confirmation description", + }} + /> + - - {}} - isPending={true} - confirmationDialog={{ - title: "This is confirmation title", - description: "This is confirmation description", - }} - /> - -
+ + {}} + isPending={true} + confirmationDialog={{ + title: "This is confirmation title", + description: "This is confirmation description", + }} + /> +
); } diff --git a/apps/dashboard/src/@/components/blocks/NetworkSelectors.tsx b/apps/dashboard/src/@/components/blocks/NetworkSelectors.tsx index d8740d69d23..40cf87b008b 100644 --- a/apps/dashboard/src/@/components/blocks/NetworkSelectors.tsx +++ b/apps/dashboard/src/@/components/blocks/NetworkSelectors.tsx @@ -72,7 +72,10 @@ export function MultiNetworkSelector(props: { onSelectedValuesChange={(chainIds) => { props.onChange(chainIds.map(Number)); }} - placeholder="Select Chains" + placeholder={ + allChains.length === 0 ? "Loading Chains..." : "Select Chains" + } + disabled={allChains.length === 0} overrideSearchFn={searchFn} renderOption={renderOption} /> diff --git a/apps/dashboard/src/@/components/blocks/multi-select.tsx b/apps/dashboard/src/@/components/blocks/multi-select.tsx index 4ca98cb78e3..ae025280626 100644 --- a/apps/dashboard/src/@/components/blocks/multi-select.tsx +++ b/apps/dashboard/src/@/components/blocks/multi-select.tsx @@ -59,6 +59,9 @@ export const MultiSelect = forwardRef( maxCount = Number.POSITIVE_INFINITY, className, selectedValues, + overrideSearchFn, + renderOption, + searchPlaceholder, ...props }, ref, @@ -105,8 +108,6 @@ export const MultiSelect = forwardRef( // show 50 initially and then 20 more when reaching the end const { itemsToShow, lastItemRef } = useShowMore(50, 20); - const { overrideSearchFn } = props; - const optionsToShow = useMemo(() => { const filteredOptions: { label: string; @@ -152,7 +153,7 @@ export const MultiSelect = forwardRef( }, [searchValue]); return ( - + ); diff --git a/apps/dashboard/src/@/components/ui/ScrollShadow/ScrollShadow.tsx b/apps/dashboard/src/@/components/ui/ScrollShadow/ScrollShadow.tsx index a1e08d27cfa..85f605ce09a 100644 --- a/apps/dashboard/src/@/components/ui/ScrollShadow/ScrollShadow.tsx +++ b/apps/dashboard/src/@/components/ui/ScrollShadow/ScrollShadow.tsx @@ -11,6 +11,7 @@ export function ScrollShadow(props: { scrollableClassName?: string; disableTopShadow?: boolean; shadowColor?: string; + shadowClassName?: string; }) { const scrollableEl = useRef(null); const shadowTopEl = useRef(null); @@ -94,7 +95,11 @@ export function ScrollShadow(props: { } >
= ({ @@ -21,10 +22,14 @@ export const CodeClient: React.FC = ({ scrollableClassName, keepPreviousDataOnCodeChange = false, copyButtonClassName, + ignoreFormattingErrors, }) => { const codeQuery = useQuery({ queryKey: ["html", code], - queryFn: () => getCodeHtml(code, lang), + queryFn: () => + getCodeHtml(code, lang, { + ignoreFormattingErrors: ignoreFormattingErrors, + }), placeholderData: keepPreviousDataOnCodeChange ? keepPreviousData : undefined, diff --git a/apps/dashboard/src/@/components/ui/code/getCodeHtml.tsx b/apps/dashboard/src/@/components/ui/code/getCodeHtml.tsx index 43ea0dcd758..bcc6bf5780a 100644 --- a/apps/dashboard/src/@/components/ui/code/getCodeHtml.tsx +++ b/apps/dashboard/src/@/components/ui/code/getCodeHtml.tsx @@ -16,15 +16,28 @@ function isPrettierSupportedLang(lang: BundledLanguage) { ); } -export async function getCodeHtml(code: string, lang: BundledLanguage) { +export async function getCodeHtml( + code: string, + lang: BundledLanguage, + options?: { + ignoreFormattingErrors?: boolean; + }, +) { const formattedCode = isPrettierSupportedLang(lang) ? await format(code, { parser: "babel-ts", plugins: [parserBabel, estree], printWidth: 60, }).catch((e) => { - console.error(e); - console.error("Failed to format code"); + if (!options?.ignoreFormattingErrors) { + console.error(e); + console.error("Failed to format code"); + console.log({ + code, + lang, + }); + } + return code; }) : code; diff --git a/apps/dashboard/src/@/components/ui/inline-code.tsx b/apps/dashboard/src/@/components/ui/inline-code.tsx index 1426ede4d7f..e7e9765242e 100644 --- a/apps/dashboard/src/@/components/ui/inline-code.tsx +++ b/apps/dashboard/src/@/components/ui/inline-code.tsx @@ -7,7 +7,7 @@ export function InlineCode({ return ( diff --git a/apps/dashboard/src/@/components/ui/tabs.tsx b/apps/dashboard/src/@/components/ui/tabs.tsx index 58cde11e601..f74a9e74e6c 100644 --- a/apps/dashboard/src/@/components/ui/tabs.tsx +++ b/apps/dashboard/src/@/components/ui/tabs.tsx @@ -171,10 +171,22 @@ function useUnderline() { } update(); + let resizeObserver: ResizeObserver | undefined = undefined; + + if (containerRef.current) { + resizeObserver = new ResizeObserver(() => { + setTimeout(() => { + update(); + }, 100); + }); + resizeObserver.observe(containerRef.current); + } + // add event listener for resize window.addEventListener("resize", update); return () => { window.removeEventListener("resize", update); + resizeObserver?.disconnect(); }; }, [activeTabEl]); diff --git a/apps/dashboard/src/@/components/ui/textarea.tsx b/apps/dashboard/src/@/components/ui/textarea.tsx index 67ff936bcf6..4144b3d35d1 100644 --- a/apps/dashboard/src/@/components/ui/textarea.tsx +++ b/apps/dashboard/src/@/components/ui/textarea.tsx @@ -22,3 +22,27 @@ const Textarea = React.forwardRef( Textarea.displayName = "Textarea"; export { Textarea }; + +export function AutoResizeTextarea(props: TextareaProps) { + const textareaRef = React.useRef(null); + + // biome-ignore lint/correctness/useExhaustiveDependencies: value is needed in deps array + React.useEffect(() => { + const textarea = textareaRef.current; + if (textarea) { + textarea.style.height = "auto"; + textarea.style.height = `${textarea.scrollHeight}px`; + } + }, [props.value]); + + return ( +