Skip to content

Commit 9608b88

Browse files
committed
Add Nebula Chat UI
1 parent 4ae50f5 commit 9608b88

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+2870
-227
lines changed

apps/dashboard/.env.example

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,7 @@ NEXT_PUBLIC_TURNSTILE_SITE_KEY=""
9494
TURNSTILE_SECRET_KEY=""
9595
REDIS_URL=""
9696

97-
ANALYTICS_SERVICE_URL=""
97+
ANALYTICS_SERVICE_URL=""
98+
99+
# Required for Nebula Chat
100+
NEXT_PUBLIC_NEBULA_URL=""

apps/dashboard/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"color": "^4.2.3",
6161
"compare-versions": "^6.1.0",
6262
"date-fns": "4.1.0",
63+
"fetch-event-stream": "^0.1.5",
6364
"flat": "^6.0.1",
6465
"framer-motion": "11.11.17",
6566
"fuse.js": "7.0.0",

apps/dashboard/src/@/components/ui/ScrollShadow/ScrollShadow.tsx

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export function ScrollShadow(props: {
1111
scrollableClassName?: string;
1212
disableTopShadow?: boolean;
1313
shadowColor?: string;
14+
shadowClassName?: string;
1415
}) {
1516
const scrollableEl = useRef<HTMLDivElement>(null);
1617
const shadowTopEl = useRef<HTMLDivElement>(null);
@@ -94,29 +95,45 @@ export function ScrollShadow(props: {
9495
}
9596
>
9697
<div
97-
className={cn(styles.scrollShadowTop, styles.scrollShadowY)}
98+
className={cn(
99+
styles.scrollShadowTop,
100+
styles.scrollShadowY,
101+
props.shadowClassName,
102+
)}
98103
ref={shadowTopEl}
99104
style={{
100105
opacity: "0",
101106
display: props.disableTopShadow ? "none" : "block",
102107
}}
103108
/>
104109
<div
105-
className={cn(styles.scrollShadowBottom, styles.scrollShadowY)}
110+
className={cn(
111+
styles.scrollShadowBottom,
112+
styles.scrollShadowY,
113+
props.shadowClassName,
114+
)}
106115
ref={shadowBottomEl}
107116
style={{
108117
opacity: "0",
109118
}}
110119
/>
111120
<div
112-
className={cn(styles.scrollShadowLeft, styles.scrollShadowX)}
121+
className={cn(
122+
styles.scrollShadowLeft,
123+
styles.scrollShadowX,
124+
props.shadowClassName,
125+
)}
113126
ref={shadowLeftEl}
114127
style={{
115128
opacity: "0",
116129
}}
117130
/>
118131
<div
119-
className={cn(styles.scrollShadowRight, styles.scrollShadowX)}
132+
className={cn(
133+
styles.scrollShadowRight,
134+
styles.scrollShadowX,
135+
props.shadowClassName,
136+
)}
120137
ref={shadowRightEl}
121138
style={{
122139
opacity: "0",

apps/dashboard/src/@/components/ui/code/code.client.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export type CodeProps = {
1212
scrollableClassName?: string;
1313
keepPreviousDataOnCodeChange?: boolean;
1414
copyButtonClassName?: string;
15+
ignoreFormattingErrors?: boolean;
1516
};
1617

1718
export const CodeClient: React.FC<CodeProps> = ({
@@ -21,10 +22,14 @@ export const CodeClient: React.FC<CodeProps> = ({
2122
scrollableClassName,
2223
keepPreviousDataOnCodeChange = false,
2324
copyButtonClassName,
25+
ignoreFormattingErrors,
2426
}) => {
2527
const codeQuery = useQuery({
2628
queryKey: ["html", code],
27-
queryFn: () => getCodeHtml(code, lang),
29+
queryFn: () =>
30+
getCodeHtml(code, lang, {
31+
ignoreFormattingErrors: ignoreFormattingErrors,
32+
}),
2833
placeholderData: keepPreviousDataOnCodeChange
2934
? keepPreviousData
3035
: undefined,

apps/dashboard/src/@/components/ui/code/getCodeHtml.tsx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,28 @@ function isPrettierSupportedLang(lang: BundledLanguage) {
1616
);
1717
}
1818

19-
export async function getCodeHtml(code: string, lang: BundledLanguage) {
19+
export async function getCodeHtml(
20+
code: string,
21+
lang: BundledLanguage,
22+
options?: {
23+
ignoreFormattingErrors?: boolean;
24+
},
25+
) {
2026
const formattedCode = isPrettierSupportedLang(lang)
2127
? await format(code, {
2228
parser: "babel-ts",
2329
plugins: [parserBabel, estree],
2430
printWidth: 60,
2531
}).catch((e) => {
26-
console.error(e);
27-
console.error("Failed to format code");
28-
console.log({
29-
code,
30-
lang,
31-
});
32+
if (!options?.ignoreFormattingErrors) {
33+
console.error(e);
34+
console.error("Failed to format code");
35+
console.log({
36+
code,
37+
lang,
38+
});
39+
}
40+
3241
return code;
3342
})
3443
: code;

apps/dashboard/src/@/components/ui/inline-code.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export function InlineCode({
77
return (
88
<code
99
className={cn(
10-
"mx-0.5 inline rounded-lg border border-border px-1.5 py-[3px] font-mono text-[0.85em] text-foreground",
10+
"mx-0.5 inline rounded-lg border border-border bg-muted px-[0.4em] py-[0.25em] font-mono text-[0.85em] text-foreground",
1111
className,
1212
)}
1313
>

apps/dashboard/src/@/components/ui/tabs.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,22 @@ function useUnderline<El extends HTMLElement>() {
171171
}
172172

173173
update();
174+
let resizeObserver: ResizeObserver | undefined = undefined;
175+
176+
if (containerRef.current) {
177+
resizeObserver = new ResizeObserver(() => {
178+
setTimeout(() => {
179+
update();
180+
}, 100);
181+
});
182+
resizeObserver.observe(containerRef.current);
183+
}
184+
174185
// add event listener for resize
175186
window.addEventListener("resize", update);
176187
return () => {
177188
window.removeEventListener("resize", update);
189+
resizeObserver?.disconnect();
178190
};
179191
}, [activeTabEl]);
180192

apps/dashboard/src/@/components/ui/textarea.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,27 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
2222
Textarea.displayName = "Textarea";
2323

2424
export { Textarea };
25+
26+
export function AutoResizeTextarea(props: TextareaProps) {
27+
const textareaRef = React.useRef<HTMLTextAreaElement>(null);
28+
29+
// biome-ignore lint/correctness/useExhaustiveDependencies: value is needed in deps array
30+
React.useEffect(() => {
31+
const textarea = textareaRef.current;
32+
if (textarea) {
33+
textarea.style.height = "auto";
34+
textarea.style.height = `${textarea.scrollHeight}px`;
35+
}
36+
}, [props.value]);
37+
38+
return (
39+
<Textarea
40+
ref={textareaRef}
41+
{...props}
42+
style={{
43+
...props.style,
44+
overflowY: "hidden",
45+
}}
46+
/>
47+
);
48+
}

apps/dashboard/src/@/constants/env.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,5 @@ export const BASE_URL = isProd
3939
: (process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL
4040
? `https://${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL}`
4141
: "http://localhost:3000") || "https://thirdweb-dev.com";
42+
43+
export const NEXT_PUBLIC_NEBULA_URL = process.env.NEXT_PUBLIC_NEBULA_URL;

apps/dashboard/src/app/account/settings/getAccount.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export async function getRawAccount() {
3636
* If there's no account or account onboarding not complete, redirect to login page
3737
* @param pagePath - the path of the current page to redirect back to after login/onboarding
3838
*/
39-
export async function getValidAccount(pagePath: string) {
39+
export async function getValidAccount(pagePath?: string) {
4040
const account = await getRawAccount();
4141

4242
// enforce login & onboarding

0 commit comments

Comments
 (0)