diff --git a/packages/app/.storybook/preview.tsx b/packages/app/.storybook/preview.tsx index 6a3076082..8a96bea44 100644 --- a/packages/app/.storybook/preview.tsx +++ b/packages/app/.storybook/preview.tsx @@ -1,5 +1,6 @@ import React from 'react'; import type { Preview } from '@storybook/react'; +import { QueryClient, QueryClientProvider } from 'react-query'; import '@mantine/core/styles.css'; import '@mantine/notifications/styles.css'; @@ -9,12 +10,16 @@ import '../src/LandingPage.scss'; import { ThemeWrapper } from '../src/ThemeWrapper'; +const queryClient = new QueryClient(); + const preview: Preview = { decorators: [ Story => ( - - - + + + + + ), ], }; diff --git a/packages/app/package.json b/packages/app/package.json index fbeeaf7b0..8980d6f24 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -23,6 +23,7 @@ "@hyperdx/lucene": "^3.1.1", "@lezer/highlight": "^1.2.0", "@mantine/core": "7.9.2", + "@mantine/form": "^7.10.1", "@mantine/hooks": "7.9.2", "@mantine/notifications": "^7.9.2", "@mantine/spotlight": "7.9.2", diff --git a/packages/app/pages/_app.tsx b/packages/app/pages/_app.tsx index 61442329d..9c92a5836 100644 --- a/packages/app/pages/_app.tsx +++ b/packages/app/pages/_app.tsx @@ -122,10 +122,10 @@ export default function MyApp({ Component, pageProps }: AppPropsWithLayout) { {getLayout()} + {confirmModal} + {background} - {confirmModal} - {background} diff --git a/packages/app/src/AppNav.tsx b/packages/app/src/AppNav.tsx index 747f63610..59cbfa0a9 100644 --- a/packages/app/src/AppNav.tsx +++ b/packages/app/src/AppNav.tsx @@ -3,7 +3,6 @@ import Link from 'next/link'; import Router, { useRouter } from 'next/router'; import cx from 'classnames'; import Fuse from 'fuse.js'; -import { Button } from 'react-bootstrap'; import { NumberParam, StringParam, @@ -14,7 +13,7 @@ import { import HyperDX from '@hyperdx/browser'; import { ActionIcon, - Button as MButton, + Button, CloseButton, Collapse, Input, @@ -939,16 +938,15 @@ export default function AppNav({ fixed = false }: { fixed?: boolean }) { )} - +
@@ -1328,14 +1326,14 @@ export default function AppNav({ fixed = false }: { fixed?: boolean }) { passHref legacyBehavior > - Get Started for Free - +
diff --git a/packages/app/src/Clipboard.tsx b/packages/app/src/Clipboard.tsx index 34b1f626a..e95aadfbe 100644 --- a/packages/app/src/Clipboard.tsx +++ b/packages/app/src/Clipboard.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import cx from 'classnames'; -import { Button } from 'react-bootstrap'; import { CopyToClipboard } from 'react-copy-to-clipboard'; +import { UnstyledButton } from '@mantine/core'; export default function Clipboard({ text, @@ -22,12 +22,9 @@ export default function Clipboard({ setTimeout(() => setIsCopied(false), 2000); }} > - + ); } diff --git a/packages/app/src/InstallInstructionsModal.stories.tsx b/packages/app/src/InstallInstructionsModal.stories.tsx new file mode 100644 index 000000000..77a3a5825 --- /dev/null +++ b/packages/app/src/InstallInstructionsModal.stories.tsx @@ -0,0 +1,13 @@ +import type { Meta } from '@storybook/react'; + +import InstallInstructionsModal from './InstallInstructionsModal'; + +const meta = { + component: InstallInstructionsModal, +} satisfies Meta; + +export default meta; + +export const Default = () => { + return {}} />; +}; diff --git a/packages/app/src/InstallInstructionsModal.tsx b/packages/app/src/InstallInstructionsModal.tsx index e99fda763..200380616 100644 --- a/packages/app/src/InstallInstructionsModal.tsx +++ b/packages/app/src/InstallInstructionsModal.tsx @@ -1,5 +1,5 @@ import cx from 'classnames'; -import { Button, Modal } from 'react-bootstrap'; +import { Button, Modal } from '@mantine/core'; import api from './api'; import Clipboard from './Clipboard'; @@ -56,156 +56,153 @@ export default function InstallInstructionModal({ return ( - -
Install HyperDX
- {team != null && ( -
- Your API Key: } - value={team.apiKey} - /> -
- )} -
- Click on a link below to view installation instructions for your - application. -
-
Backend
-
- - Node.js - - (Logs + Traces) -
-
- - Go - - (Logs + Traces) -
-
- - Python - - (Logs + Traces) -
-
- - Java - - (Logs + Traces) -
-
- - Elixir - - (Logs) -
-
- - Ruby on Rails - - (Traces) -
-
Platform
-
- - Fly.io - - (Logs) -
-
- - Cloudflare Workers - - (Logs + Traces) -
-
- - Kubernetes - - (Logs + Metrics) -
-
Browser
-
- - JavaScript/TypeScript - - (Logs + Traces) -
-
Data Collector
-
- - OpenTelemetry - - (Logs + Traces) -
-
- - Fluentd - - (Logs) -
-
- + {team != null && ( +
+ Your API Key: } + value={team.apiKey} + />
- + )} +
+ Click on a link below to view installation instructions for your + application. +
+
Backend
+
+ + Node.js + + (Logs + Traces) +
+
+ + Go + + (Logs + Traces) +
+
+ + Python + + (Logs + Traces) +
+
+ + Java + + (Logs + Traces) +
+
+ + Elixir + + (Logs) +
+
+ + Ruby on Rails + + (Traces) +
+
Platform
+
+ + Fly.io + + (Logs) +
+
+ + Cloudflare Workers + + (Logs + Traces) +
+
+ + Kubernetes + + (Logs + Metrics) +
+
Browser
+
+ + JavaScript/TypeScript + + (Logs + Traces) +
+
Data Collector
+
+ + OpenTelemetry + + (Logs + Traces) +
+
+ + Fluentd + + (Logs) +
+
+ +
); } diff --git a/packages/app/src/JoinTeamPage.stories.tsx b/packages/app/src/JoinTeamPage.stories.tsx new file mode 100644 index 000000000..43d8f30fc --- /dev/null +++ b/packages/app/src/JoinTeamPage.stories.tsx @@ -0,0 +1,13 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import JoinTeamPage from './JoinTeamPage'; + +const meta = { + component: JoinTeamPage, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/packages/app/src/JoinTeamPage.tsx b/packages/app/src/JoinTeamPage.tsx index 0cc9c13c9..cda6b769e 100644 --- a/packages/app/src/JoinTeamPage.tsx +++ b/packages/app/src/JoinTeamPage.tsx @@ -1,6 +1,13 @@ import { useRouter } from 'next/router'; import { NextSeo } from 'next-seo'; -import { Button, Form } from 'react-bootstrap'; +import { + Button, + Card, + Center, + PasswordInput, + Stack, + Text, +} from '@mantine/core'; import { SERVER_URL } from './config'; @@ -11,58 +18,30 @@ export default function JoinTeam() { return (
-
-
-
-

Join Team

-
-
-
-
- - Password - - - {err != null && ( -
- {err === 'invalid' +
+ + + + Join team + - )} -
- -
- -
-
-
-
+ : 'Unknown error occurred, please try again later.' + : null + } + /> + + + + +
); } diff --git a/packages/app/src/LandingHeader.stories.tsx b/packages/app/src/LandingHeader.stories.tsx new file mode 100644 index 000000000..93756aa39 --- /dev/null +++ b/packages/app/src/LandingHeader.stories.tsx @@ -0,0 +1,17 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import LandingHeader from './LandingHeader'; + +const meta = { + component: LandingHeader, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + activeKey: 'activeKey', + }, +}; diff --git a/packages/app/src/LandingHeader.tsx b/packages/app/src/LandingHeader.tsx index 3840e03d5..da1713359 100644 --- a/packages/app/src/LandingHeader.tsx +++ b/packages/app/src/LandingHeader.tsx @@ -3,7 +3,6 @@ import { Button, Container, Nav, Navbar, NavDropdown } from 'react-bootstrap'; import api from './api'; import Logo from './Logo'; -import NavHoverDropdown from './NavHoverDropdown'; export default function LandingHeader({ activeKey, diff --git a/packages/app/src/NavHoverDropdown.tsx b/packages/app/src/NavHoverDropdown.tsx deleted file mode 100644 index 57856d760..000000000 --- a/packages/app/src/NavHoverDropdown.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { useState } from 'react'; -import { NavDropdown } from 'react-bootstrap'; - -export default function NavHoverDropdown( - props: React.ComponentProps, -) { - const [show, setShow] = useState(false); - return ( - setShow(v => !v)} - onMouseEnter={() => setShow(true)} - onMouseLeave={() => setShow(false)} - /> - ); -} diff --git a/packages/app/src/PasswordResetPage.stories.tsx b/packages/app/src/PasswordResetPage.stories.tsx new file mode 100644 index 000000000..31b2f34fb --- /dev/null +++ b/packages/app/src/PasswordResetPage.stories.tsx @@ -0,0 +1,19 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import PasswordResetPage from './PasswordResetPage'; + +const meta = { + component: PasswordResetPage, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Forgot = () => { + return ; +}; + +export const ResetPassword = () => { + return ; +}; diff --git a/packages/app/src/SaveSearchModal.tsx b/packages/app/src/SaveSearchModal.tsx index 98a3efbab..de903b423 100644 --- a/packages/app/src/SaveSearchModal.tsx +++ b/packages/app/src/SaveSearchModal.tsx @@ -1,6 +1,6 @@ import { FormEvent, useEffect, useState } from 'react'; -import { Button, Form, Modal } from 'react-bootstrap'; -import { Button as MButton, Text } from '@mantine/core'; +import { Form, Modal } from 'react-bootstrap'; +import { Button as Button, Text } from '@mantine/core'; import { notifications } from '@mantine/notifications'; import api from './api'; @@ -115,9 +115,9 @@ export default function SaveSearchModal({ defaultValue={searchName} /> - + diff --git a/packages/app/src/SearchPage.tsx b/packages/app/src/SearchPage.tsx index d0e3fb606..f04d0029a 100644 --- a/packages/app/src/SearchPage.tsx +++ b/packages/app/src/SearchPage.tsx @@ -14,7 +14,6 @@ import { useRouter } from 'next/router'; import cx from 'classnames'; import { clamp, format, sub } from 'date-fns'; import { formatInTimeZone } from 'date-fns-tz'; -import { Button } from 'react-bootstrap'; import { ErrorBoundary } from 'react-error-boundary'; import { useHotkeys } from 'react-hotkeys-hook'; import { @@ -32,7 +31,7 @@ import { useQueryParams, withDefault, } from 'use-query-params'; -import { ActionIcon, Indicator } from '@mantine/core'; +import { ActionIcon, Button, Indicator } from '@mantine/core'; import { notifications } from '@mantine/notifications'; import api from './api'; @@ -984,13 +983,15 @@ function SearchPage() {
diff --git a/packages/app/src/TeamPage.tsx b/packages/app/src/TeamPage.tsx index 36f3f62a1..8594facc3 100644 --- a/packages/app/src/TeamPage.tsx +++ b/packages/app/src/TeamPage.tsx @@ -1,7 +1,6 @@ import { useCallback, useState } from 'react'; import Head from 'next/head'; import { - Button, Form, Modal, Row, @@ -41,6 +40,11 @@ import CodeMirror, { placeholder } from '@uiw/react-codemirror'; import api from './api'; import { withAppNav } from './layout'; import { WebhookFlatIcon } from './SVGIcons'; +import { + AddSlackWebhookModal, + ConfirmDeleteTeamMember, + ConfirmRotateAPIKeyModal, +} from './TeamPageComponents'; import { WebhookService } from './types'; import { truncateMiddle } from './utils'; import { isValidJson, isValidUrl } from './utils'; @@ -349,12 +353,18 @@ export default function TeamPage() { setTeamInviteModalShow(false); }; - const onSubmitAddWebhookForm = (e: any, service: WebhookService) => { - e.preventDefault(); - const name = e.target.name.value; - const description = e.target.description.value; - const url = e.target.url.value; - + const onSubmitAddWebhookForm = ( + { + name, + description, + url, + }: { + name: string; + description?: string; + url: string; + }, + service: WebhookService, + ) => { if (!name) { notifications.show({ color: 'red', @@ -526,42 +536,11 @@ export default function TeamPage() { )} -
- setRotateApiKeyConfirmationModalShow(false)} - show={rotateApiKeyConfirmationModalShow} - size="lg" - > - -

Rotate API Key

-
- Rotating the API key will invalidate your existing API - key and generate a new one for you. This action is not - reversible. -
- - -
-
-
+ setRotateApiKeyConfirmationModalShow(false)} + onConfirm={onConfirmUpdateTeamApiKey} + /> {!isLoadingMe && me != null && ( @@ -636,63 +615,13 @@ export default function TeamPage() { Add Slack Incoming Webhook - setAddSlackWebhookModalShow(false)} - show={addSlackWebhookModalShow} - size="lg" - > - -
Add Slack Incoming Webhook
-
- onSubmitAddWebhookForm(e, WebhookService.Slack) - } - > - - Webhook Name - - - - Webhook URL - - - - Webhook Description (optional) - - - - -
-
+ setAddSlackWebhookModalShow(false)} + onSubmit={webhook => + onSubmitAddWebhookForm(webhook, WebhookService.Slack) + } + />
@@ -768,9 +697,17 @@ export default function TeamPage() { Add Generic Incoming Webhook
- onSubmitAddWebhookForm(e, WebhookService.Generic) - } + onSubmit={(e: any) => { + e.preventDefault(); + onSubmitAddWebhookForm( + { + name: e.target.name.value, + url: e.target.url.value, + description: e.target.description.value, + }, + WebhookService.Generic, + ); + }} > Webhook Name @@ -904,56 +841,23 @@ export default function TeamPage() { )} )} - + setDeleteTeamMemberConfirmationModalData({ mode: null, id: null, email: null, }) } - show={deleteTeamMemberConfirmationModalData.id != null} - size="lg" - > - -

Delete Team Member

-

- Deleting this team member ( - {deleteTeamMemberConfirmationModalData.email}) will revoke - their access to the team's resources and services. This - action is not reversible. -

- - -
-
+ onConfirm={() => { + deleteTeamMemberConfirmationModalData.id && + onConfirmDeleteTeamMember( + deleteTeamMemberConfirmationModalData.id, + ); + }} + email={deleteTeamMemberConfirmationModalData.email} + />
Team
diff --git a/packages/app/src/TeamPageComponents.stories.tsx b/packages/app/src/TeamPageComponents.stories.tsx new file mode 100644 index 000000000..f536fe82e --- /dev/null +++ b/packages/app/src/TeamPageComponents.stories.tsx @@ -0,0 +1,27 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { + AddSlackWebhookModal, + ConfirmDeleteTeamMember, + ConfirmRotateAPIKeyModal, +} from './TeamPageComponents'; + +const meta = { + component: ConfirmRotateAPIKeyModal, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const ConfirmRotateAPIKeyModalStory = () => { + return ; +}; + +export const AddSlackWebhookModalStory = () => { + return ; +}; + +export const ConfirmDeleteTeamMemberStory = () => { + return ; +}; diff --git a/packages/app/src/TeamPageComponents.tsx b/packages/app/src/TeamPageComponents.tsx new file mode 100644 index 000000000..5ddd036cd --- /dev/null +++ b/packages/app/src/TeamPageComponents.tsx @@ -0,0 +1,136 @@ +import React from 'react'; +import { Button, Group, Modal, Stack, Text, TextInput } from '@mantine/core'; +import { useForm } from '@mantine/form'; + +export const ConfirmRotateAPIKeyModal = ({ + opened, + onClose, + onConfirm, +}: { + opened: boolean; + onClose: VoidFunction; + onConfirm: VoidFunction; +}) => ( + + + Rotating the API key will invalidate your existing API key and generate a + new one for you. This action is not reversible. + + + + + + +); + +export const AddSlackWebhookModal = ({ + opened, + onClose, + onSubmit = () => {}, +}: { + opened: boolean; + onClose: VoidFunction; + onSubmit?: (arg0: { + name: string; + description?: string; + url: string; + }) => void; +}) => { + const form = useForm<{ + name: string; + url: string; + description?: string; + }>({ + mode: 'uncontrolled', + validate: { + name: value => (value.trim().length > 0 ? null : 'Name is required'), + url: value => (value.startsWith('http') ? null : 'Invalid URL'), + }, + }); + + React.useEffect(() => { + form.reset(); + }, [opened]); + + return ( + + + + + + + + + + + + + + ); +}; + +export const ConfirmDeleteTeamMember = ({ + email, + opened, + onClose, + onConfirm, +}: { + email?: string | null; + opened: boolean; + onClose: VoidFunction; + onConfirm: VoidFunction; +}) => ( + + + Deleting this team member {email && ({email})} will + revoke their access to the team's resources and services. This action + is not reversible. + + + + + + +); diff --git a/packages/app/src/ThemeWrapper.tsx b/packages/app/src/ThemeWrapper.tsx index acb4b93ae..b00daa72d 100644 --- a/packages/app/src/ThemeWrapper.tsx +++ b/packages/app/src/ThemeWrapper.tsx @@ -3,7 +3,7 @@ import { MantineProvider, MantineThemeOverride } from '@mantine/core'; import { Notifications } from '@mantine/notifications'; const makeTheme = ({ - fontFamily = 'IBM Plex Sans, sans-serif', + fontFamily = '"IBM Plex Sans", monospace', }: { fontFamily?: string; }): MantineThemeOverride => ({ diff --git a/packages/app/src/useConfirm.tsx b/packages/app/src/useConfirm.tsx index 7b0ec6b66..8c9e41130 100644 --- a/packages/app/src/useConfirm.tsx +++ b/packages/app/src/useConfirm.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import { atom, useAtomValue, useSetAtom } from 'jotai'; -import Button from 'react-bootstrap/Button'; -import Modal from 'react-bootstrap/Modal'; +import { Button, Modal } from '@mantine/core'; type ConfirmAtom = { message: string; @@ -46,18 +45,15 @@ export const useConfirmModal = () => { }, [confirm, setConfirm]); return confirm ? ( - - - {confirm.message} -
- - -
-
+ +
+ + +
) : null; }; diff --git a/yarn.lock b/yarn.lock index 5ae7e0447..5d4fe4a56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3530,6 +3530,14 @@ react-textarea-autosize "8.5.3" type-fest "^4.12.0" +"@mantine/form@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@mantine/form/-/form-7.10.1.tgz#86b867454b7490f4ca58c924d85d4170520cec4d" + integrity sha512-mZwzg4GEWKEDKEIZu9FmSpGFzYYhFD2YArVOXUM0MMciUqX7yxSCon1PaPJxrV8ldc6FE+JLVI2+G2KVxJ3ZXA== + dependencies: + fast-deep-equal "^3.1.3" + klona "^2.0.6" + "@mantine/hooks@7.9.2": version "7.9.2" resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-7.9.2.tgz#5cb2abc5b293b6571456443cee858f98503cc7c5" @@ -6768,7 +6776,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@18.2.23", "@types/react@>=16", "@types/react@>=16.9.11": +"@types/react@*", "@types/react@18.2.23", "@types/react@>=16", "@types/react@>=16.9.11", "@types/react@^16.8.0 || ^17.0.0 || ^18.0.0": version "18.2.23" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.23.tgz#60ad6cf4895e93bed858db0e03bcc4ff97d0410e" integrity sha512-qHLW6n1q2+7KyBEYnrZpcsAmU/iiCh9WGCKgXvMxx89+TYdJWRjZohVIo9XTcoLhfX3+/hP0Pbulu3bCZQ9PSA== @@ -6777,14 +6785,6 @@ "@types/scheduler" "*" csstype "^3.0.2" -"@types/react@^16.8.0 || ^17.0.0 || ^18.0.0": - version "18.3.3" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.3.tgz#9679020895318b0915d7a3ab004d92d33375c45f" - integrity sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw== - dependencies: - "@types/prop-types" "*" - csstype "^3.0.2" - "@types/resolve@^1.20.2": version "1.20.6" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.6.tgz#e6e60dad29c2c8c206c026e6dd8d6d1bdda850b8" @@ -13075,7 +13075,7 @@ kleur@^4.0.3, kleur@^4.1.5: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -klona@^2.0.4: +klona@^2.0.4, klona@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22" integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== @@ -15635,6 +15635,7 @@ prelude-ls@~1.1.2: integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== "prettier-fallback@npm:prettier@^3", prettier@^3.1.1: + name prettier-fallback version "3.2.5" resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368" integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== @@ -16346,14 +16347,7 @@ react-useportal@^1.0.18: dependencies: use-ssr "^1.0.25" -"react@^16.8.0 || ^17.0.0 || ^18.0.0": - version "18.3.1" - resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" - integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== - dependencies: - loose-envify "^1.1.0" - -react@^18.2.0: +"react@^16.8.0 || ^17.0.0 || ^18.0.0", react@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==